Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Allow generating a list of strict operation IDs
The generated code presently passes a strict operation ID to every
`StrictMiddlewareFunc` you have configured for a server. This is great
for writing auth-like middleware: you can have a unified function that
handles authorization on the *operational* level, and test it by itself.

This is not so great if you're forgetful (like the author of this MR)
and sometimes forget to write those tests... or to update your handler
function.

By generating a list of all operation handlers, we make it much easier
to write a test that asserts that some `StrictMiddlewareFunc` has proper
behavior for *all* operation IDs.
  • Loading branch information
AnthonySuper committed Jun 18, 2026
commit 4382333914e3e88ee73c0a1214794beb2909ff97
4 changes: 4 additions & 0 deletions configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
"server-urls": {
"type": "boolean",
"description": "Generate types for the `Server` definitions' URLs, instead of needing to provide your own values"
},
"strict-operation-list": {
"type": "boolean",
"description": "Generate a var StrictOperationIDs []string containing the normalized operation name passed to every StrictMiddlewareFunc call. Makes it straightforward to write generic tests that assert a behaviour holds for every strict operation."
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions internal/test/operation-list/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# yaml-language-server: $schema=../../../configuration-schema.json
package: operationlist
generate:
strict-operation-list: true
output: operation_list.gen.go
3 changes: 3 additions & 0 deletions internal/test/operation-list/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package operationlist

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml
15 changes: 15 additions & 0 deletions internal/test/operation-list/operation_list.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions internal/test/operation-list/operation_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package operationlist

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestStrictOperationIDs(t *testing.T) {
expected := []string{"ListUsers", "CreateUser", "GetUser", "DeleteUser"}
assert.ElementsMatch(t, expected, StrictOperationIDs)
}
43 changes: 43 additions & 0 deletions internal/test/operation-list/spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
openapi: "3.0.1"
info:
version: 1.0.0
title: Operation List Test
paths:
/users:
get:
summary: List users
operationId: listUsers
responses:
'200':
description: OK
post:
summary: Create user
operationId: createUser
responses:
'201':
description: Created
/users/{id}:
get:
summary: Get user
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
delete:
summary: Delete user
operationId: deleteUser
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: No Content
31 changes: 31 additions & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
}
}

var operationListOut string
if opts.Generate.StrictOperationList {
operationListOut, err = GenerateStrictOperationList(t, ops)
if err != nil {
return "", fmt.Errorf("error generating operation list: %w", err)
}
}

var buf bytes.Buffer
w := bufio.NewWriter(&buf)

Expand Down Expand Up @@ -522,6 +530,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
}
}

if opts.Generate.StrictOperationList {
_, err = w.WriteString(operationListOut)
if err != nil {
return "", fmt.Errorf("error writing operation list: %w", err)
}
}

err = w.Flush()
if err != nil {
return "", fmt.Errorf("error flushing output buffer: %w", err)
Expand Down Expand Up @@ -664,6 +679,22 @@ func GenerateConstants(t *template.Template, ops []OperationDefinition) (string,
return GenerateTemplates([]string{"constants.tmpl"}, t, constants)
}

// StrictOperationList is the data model passed to the operation-list.tmpl template.
type StrictOperationList struct {
OperationIDs []string
}

// GenerateStrictOperationList generates a slice of the normalized operation IDs
// passed to StrictMiddlewareFunc at runtime. Each value is the Go-identifier form
// of the operation (e.g. "ListUsers"), matching what the strict handler emits verbatim.
func GenerateStrictOperationList(t *template.Template, ops []OperationDefinition) (string, error) {
ids := make([]string, 0, len(ops))
for _, op := range ops {
ids = append(ids, op.OperationId)
}
return GenerateTemplates([]string{"operation-list.tmpl"}, t, StrictOperationList{OperationIDs: ids})
}
Comment thread
AnthonySuper marked this conversation as resolved.

// GenerateTypesForSchemas generates type definitions for any custom types defined in the
// components/schemas section of the Swagger spec.
func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3.SchemaRef, excludeSchemas []string) ([]TypeDefinition, error) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ type GenerateOptions struct {
EmbeddedSpec bool `yaml:"embedded-spec,omitempty"`
// ServerURLs generates types for the `Server` definitions' URLs, instead of needing to provide your own values
ServerURLs bool `yaml:"server-urls,omitempty"`
// StrictOperationList generates a slice of all strict operation IDs in the spec, useful for writing generic strict middleware tests
StrictOperationList bool `yaml:"strict-operation-list,omitempty"`
}

// RouterImports returns the framework-specific and strict middleware imports
Expand Down
9 changes: 9 additions & 0 deletions pkg/codegen/templates/operation-list.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// StrictOperationIDs lists the operation name string passed as the second
// argument to every StrictMiddlewareFunc call. Each entry is the normalized
// Go identifier for the operation (e.g. "ListUsers"), matching exactly what
// the strict handler passes when it invokes the middleware chain.
var StrictOperationIDs = []string{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Global name can collide

StrictOperationIDs is emitted as a fixed top-level package symbol, but it is not checked against generated type names. If a spec already has a component schema named StrictOperationIDs, code generation can emit both type StrictOperationIDs ... and var StrictOperationIDs = []string{...} in the same package. Go rejects that as a redeclaration, so enabling this option can make an otherwise valid spec fail to compile.

{{- range .OperationIDs}}
"{{.}}",
{{- end}}
}