Skip to content

Commit

Permalink
feat: filter by operation ids
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneswuerbach committed Aug 2, 2023
1 parent fb652f7 commit 7a85238
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 33 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -761,13 +761,13 @@ So, for example, if you would like to produce only the server code, you could
run `oapi-codegen -generate types,server`. You could generate `types` and
`server` into separate files, but both are required for the server code.

`oapi-codegen` can filter paths base on their tags in the openapi definition.
Use either `-include-tags` or `-exclude-tags` followed by a comma-separated list
of tags. For instance, to generate a server that serves all paths except those
tagged with `auth` or `admin`, use the argument, `-exclude-tags="auth,admin"`.
`oapi-codegen` can filter paths base on their tags or operationId in the openapi definition.
Use either `-include-tags`, `include-operation-ids` or `-exclude-tags`, `-exclude-operation-ids`
followed by a comma-separated list of tags or operation ids. For instance, to generate a server
that serves all paths except those tagged with `auth` or `admin`, use the argument, `-exclude-tags="auth,admin"`.
To generate a server that only handles `admin` paths, use the argument
`-include-tags="admin"`. When neither of these arguments is present, all paths
are generated.
`-include-tags="admin"` or use `-include-operation-ids="getPets"` to include this specific operation.
When neither of these arguments is present, all paths are generated.

`oapi-codegen` can filter schemas based on the option `--exclude-schemas`, which is
a comma separated list of schema names. For instance, `--exclude-schemas=Pet,NewPet`
Expand Down
24 changes: 14 additions & 10 deletions cmd/oapi-codegen/oapi-codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,18 @@ type configuration struct {
// oldConfiguration is deprecated. Please add no more flags here. It is here
// for backwards compatibility, and it will be removed in the future.
type oldConfiguration struct {
PackageName string `yaml:"package"`
GenerateTargets []string `yaml:"generate"`
OutputFile string `yaml:"output"`
IncludeTags []string `yaml:"include-tags"`
ExcludeTags []string `yaml:"exclude-tags"`
TemplatesDir string `yaml:"templates"`
ImportMapping map[string]string `yaml:"import-mapping"`
ExcludeSchemas []string `yaml:"exclude-schemas"`
ResponseTypeSuffix string `yaml:"response-type-suffix"`
Compatibility codegen.CompatibilityOptions `yaml:"compatibility"`
PackageName string `yaml:"package"`
GenerateTargets []string `yaml:"generate"`
OutputFile string `yaml:"output"`
IncludeTags []string `yaml:"include-tags"`
ExcludeTags []string `yaml:"exclude-tags"`
IncludeOperationIDs []string `yaml:"include-operation-ids"`
ExcludeOperationIDs []string `yaml:"exclude-operation-ids"`
TemplatesDir string `yaml:"templates"`
ImportMapping map[string]string `yaml:"import-mapping"`
ExcludeSchemas []string `yaml:"exclude-schemas"`
ResponseTypeSuffix string `yaml:"response-type-suffix"`
Compatibility codegen.CompatibilityOptions `yaml:"compatibility"`
}

func main() {
Expand All @@ -94,6 +96,8 @@ func main() {
`Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune", "fiber".`)
flag.StringVar(&flagIncludeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.")
flag.StringVar(&flagExcludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.")
flag.StringVar(&flagIncludeTags, "include-operation-ids", "", "Only include operations with the given operation-ids. Comma-separated list of operation-ids.")
flag.StringVar(&flagExcludeTags, "exclude-operation-ids", "", "Exclude operations with the given operation-ids. Comma-separated list of operation-ids.")
flag.StringVar(&flagTemplatesDir, "templates", "", "Path to directory containing user templates.")
flag.StringVar(&flagImportMapping, "import-mapping", "", "A dict from the external reference to golang package path.")
flag.StringVar(&flagExcludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation.")
Expand Down
1 change: 1 addition & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
globalState.importMapping = constructImportMapping(opts.ImportMapping)

filterOperationsByTag(spec, opts)
filterOperationsByOperationID(spec, opts)
if !opts.OutputOptions.SkipPrune {
pruneUnusedComponents(spec)
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,13 @@ type CompatibilityOptions struct {

// OutputOptions are used to modify the output code in some way.
type OutputOptions struct {
SkipFmt bool `yaml:"skip-fmt,omitempty"` // Whether to skip go imports on the generated code
SkipPrune bool `yaml:"skip-prune,omitempty"` // Whether to skip pruning unused components on the generated code
IncludeTags []string `yaml:"include-tags,omitempty"` // Only include operations that have one of these tags. Ignored when empty.
ExcludeTags []string `yaml:"exclude-tags,omitempty"` // Exclude operations that have one of these tags. Ignored when empty.
UserTemplates map[string]string `yaml:"user-templates,omitempty"` // Override built-in templates from user-provided files
SkipFmt bool `yaml:"skip-fmt,omitempty"` // Whether to skip go imports on the generated code
SkipPrune bool `yaml:"skip-prune,omitempty"` // Whether to skip pruning unused components on the generated code
IncludeTags []string `yaml:"include-tags,omitempty"` // Only include operations that have one of these tags. Ignored when empty.
ExcludeTags []string `yaml:"exclude-tags,omitempty"` // Exclude operations that have one of these tags. Ignored when empty.
IncludeOperationIDs []string `yaml:"include-operation-ids,omitempty"` // Only include operations that have one of these operation-ids. Ignored when empty.
ExcludeOperationIDs []string `yaml:"exclude-operation-ids,omitempty"` // Exclude operations that have one of these operation-ids. Ignored when empty.
UserTemplates map[string]string `yaml:"user-templates,omitempty"` // Override built-in templates from user-provided files

ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` // Exclude from generation schemas with given names. Ignored when empty.
ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types
Expand Down
58 changes: 46 additions & 12 deletions pkg/codegen/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ package codegen

import "github.com/getkin/kin-openapi/openapi3"

func sliceToMap(items []string) map[string]bool {
m := make(map[string]bool, len(items))
for _, item := range items {
m[item] = true
}
return m
}

func filterOperationsByTag(swagger *openapi3.T, opts Configuration) {
if len(opts.OutputOptions.ExcludeTags) > 0 {
excludeOperationsWithTags(swagger.Paths, opts.OutputOptions.ExcludeTags)
operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeTags), true)
}
if len(opts.OutputOptions.IncludeTags) > 0 {
includeOperationsWithTags(swagger.Paths, opts.OutputOptions.IncludeTags, false)
operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeTags), false)
}
}

func excludeOperationsWithTags(paths openapi3.Paths, tags []string) {
includeOperationsWithTags(paths, tags, true)
}

func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool) {
func operationsWithTags(paths openapi3.Paths, tags map[string]bool, exclude bool) {
for _, pathItem := range paths {
ops := pathItem.Operations()
names := make([]string, 0, len(ops))
Expand All @@ -31,16 +35,46 @@ func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool
}

// operationHasTag returns true if the operation is tagged with any of tags
func operationHasTag(op *openapi3.Operation, tags []string) bool {
func operationHasTag(op *openapi3.Operation, tags map[string]bool) bool {
if op == nil {
return false
}
for _, hasTag := range op.Tags {
for _, wantTag := range tags {
if hasTag == wantTag {
return true
}
if tags[hasTag] {
return true
}
}
return false
}

func filterOperationsByOperationID(swagger *openapi3.T, opts Configuration) {
if len(opts.OutputOptions.ExcludeOperationIDs) > 0 {
operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeOperationIDs), true)
}
if len(opts.OutputOptions.IncludeOperationIDs) > 0 {
operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeOperationIDs), false)
}
}

func operationsWithOperationIDs(paths openapi3.Paths, operationIDs map[string]bool, exclude bool) {
for _, pathItem := range paths {
ops := pathItem.Operations()
names := make([]string, 0, len(ops))
for name, op := range ops {
if operationHasOperationID(op, operationIDs) == exclude {
names = append(names, name)
}
}
for _, name := range names {
pathItem.SetOperation(name, nil)
}
}
}

// operationHasOperationID returns true if the operation has operation id is included in operation ids
func operationHasOperationID(op *openapi3.Operation, operationIDs map[string]bool) bool {
if op == nil {
return false
}
return operationIDs[op.OperationID]
}
61 changes: 61 additions & 0 deletions pkg/codegen/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,64 @@ func TestFilterOperationsByTag(t *testing.T) {
assert.NotContains(t, code, `"/cat"`)
})
}

func TestFilterOperationsByOperationID(t *testing.T) {
packageName := "testswagger"
t.Run("include operation ids", func(t *testing.T) {
opts := Configuration{
PackageName: packageName,
Generate: GenerateOptions{
EchoServer: true,
Client: true,
Models: true,
EmbeddedSpec: true,
},
OutputOptions: OutputOptions{
IncludeOperationIDs: []string{"getCatStatus"},
},
}

loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true

// Get a spec from the test definition in this file:
swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition))
assert.NoError(t, err)

// Run our code generation:
code, err := Generate(swagger, opts)
assert.NoError(t, err)
assert.NotEmpty(t, code)
assert.NotContains(t, code, `"/test/:name"`)
assert.Contains(t, code, `"/cat"`)
})

t.Run("exclude operation ids", func(t *testing.T) {
opts := Configuration{
PackageName: packageName,
Generate: GenerateOptions{
EchoServer: true,
Client: true,
Models: true,
EmbeddedSpec: true,
},
OutputOptions: OutputOptions{
ExcludeOperationIDs: []string{"getCatStatus"},
},
}

loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true

// Get a spec from the test definition in this file:
swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition))
assert.NoError(t, err)

// Run our code generation:
code, err := Generate(swagger, opts)
assert.NoError(t, err)
assert.NotEmpty(t, code)
assert.Contains(t, code, `"/test/:name"`)
assert.NotContains(t, code, `"/cat"`)
})
}

0 comments on commit 7a85238

Please sign in to comment.