Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion configuration-schema.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we please revert the unrelated changes? Although those full stops at the end may be reasonable, I'd prefer to remove them from this PR to keep it atomic. Thanks!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sure, that's done. I did leave a typo fix.

Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@
},
"preserve-original-operation-id-casing-in-embedded-spec": {
"type": "boolean",
"description": "When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from.\nHowever, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation.\nTo ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this. NOTE that this will not impact generated code.\nNOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct."
"description": "When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from.\nHowever, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specification.\nTo ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this. NOTE that this will not impact generated code.\nNOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct."
},
"enum-server-variables-conflict": {
"type": "boolean",
"description": "Enum server variable values can generate conflicting typenames when `default` is used as a value and there's a default value. Set to `true` to avoid such conflicts. Caveat, it will result in some enum types being renamed in existing code. Please see: Please see https://github.com/oapi-codegen/oapi-codegen/issues/2003."
}
}
},
Expand Down
11 changes: 5 additions & 6 deletions examples/generate/serverurls/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ servers:
default: v2
# an example of a type that's defined, but doesn't have a default
noDefault: {}
# # TODO this conflict will cause broken generated code https://github.com/oapi-codegen/oapi-codegen/issues/2003
# conflicting:
# enum:
# - 'default'
# - '443'
# default: 'default'
conflicting:
enum:
- 'default'
- '443'
default: 'default'
# clash with the previous definition of `Development server` to trigger a new name
- url: http://localhost:80
description: Development server
Expand Down
2 changes: 2 additions & 0 deletions examples/generate/serverurls/cfg.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# yaml-language-server: $schema=../../../configuration-schema.json
package: serverurls
output: gen.go
compatibility:
enum-server-variables-conflict: true
generate:
server-urls: true
output-options:
Expand Down
26 changes: 20 additions & 6 deletions examples/generate/serverurls/gen.go

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

4 changes: 3 additions & 1 deletion examples/generate/serverurls/gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestServerUrlTheProductionAPIServer(t *testing.T) {
t.Run("when no values are provided, it does not error", func(t *testing.T) {
serverUrl, err := NewServerUrlTheProductionAPIServer("", "", "", "")
serverUrl, err := NewServerUrlTheProductionAPIServer("", "", "", "", "")
require.NoError(t, err)

assert.Equal(t, "https://.gigantic-server.com:/", serverUrl)
Expand All @@ -25,6 +25,7 @@ func TestServerUrlTheProductionAPIServer(t *testing.T) {
invalidPort := ServerUrlTheProductionAPIServerPortVariable("12345")
serverUrl, err := NewServerUrlTheProductionAPIServer(
ServerUrlTheProductionAPIServerBasePathVariableDefault,
ServerUrlTheProductionAPIServerConflictingVariableDefault,
ServerUrlTheProductionAPIServerNoDefaultVariable(""),
invalidPort,
ServerUrlTheProductionAPIServerUsernameVariableDefault,
Expand All @@ -37,6 +38,7 @@ func TestServerUrlTheProductionAPIServer(t *testing.T) {
t.Run("when default values are provided, it does not error", func(t *testing.T) {
serverUrl, err := NewServerUrlTheProductionAPIServer(
ServerUrlTheProductionAPIServerBasePathVariableDefault,
ServerUrlTheProductionAPIServerConflictingVariableDefault,
ServerUrlTheProductionAPIServerNoDefaultVariable(""),
ServerUrlTheProductionAPIServerPortVariableDefault,
ServerUrlTheProductionAPIServerUsernameVariableDefault,
Expand Down
13 changes: 9 additions & 4 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,14 @@ func constructImportMapping(importMapping map[string]string) importMap {
return result
}

// Generate uses the Go templating engine to generate all of our server wrappers from
// the descriptions we've built up above from the schema objects.
// opts defines
// Generate creates all server wrappers and related code using the Go templating engine.
// It processes the provided OpenAPI schema (spec) and generation options (opts), producing code for
// clients, servers, models, and other components as specified in opts.
//
// Parameters:
//
// spec - the OpenAPI specification describing the API schema
// opts - options controlling what code to generate (client, server, etc.)
func Generate(spec *openapi3.T, opts Configuration) (string, error) {
// This is global state
globalState.options = opts
Expand Down Expand Up @@ -204,7 +209,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {

var serverURLsDefinitions string
if opts.Generate.ServerURLs {
serverURLsDefinitions, err = GenerateServerURLs(t, spec)
serverURLsDefinitions, err = GenerateServerURLs(t, spec, globalState.options.Compatibility.EnumServerVariablesConflict)
if err != nil {
return "", fmt.Errorf("error generating Server URLs: %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ type CompatibilityOptions struct {
// NOTE that this will not impact generated code.
// NOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct.
PreserveOriginalOperationIdCasingInEmbeddedSpec bool `yaml:"preserve-original-operation-id-casing-in-embedded-spec"`

// EnumServerVariablesConflict controls whether the code generator should handle conflicts
// between enum values and server variable names. When set to true, the generator will
// apply logic to avoid naming collisions between enum types and server variables in the
// generated code.
//
// Corresponds to the `enum-server-variables-conflict` property in the configuration schema.
EnumServerVariablesConflict bool `yaml:"enum-server-variables-conflict,omitempty"`
}

func (co CompatibilityOptions) Validate() map[string]string {
Expand Down
10 changes: 7 additions & 3 deletions pkg/codegen/server_urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ type ServerObjectDefinition struct {

// OAPISchema is the underlying OpenAPI representation of the Server
OAPISchema *openapi3.Server

// EnumServerVariablesConflict indicates whether the server variables conflict should be avoided
EnumServerVariablesConflict bool
}

func GenerateServerURLs(t *template.Template, spec *openapi3.T) (string, error) {
func GenerateServerURLs(t *template.Template, spec *openapi3.T, enumServerVariablesConflict bool) (string, error) {
names := make(map[string]*openapi3.Server)

for _, server := range spec.Servers {
Expand Down Expand Up @@ -71,8 +74,9 @@ func GenerateServerURLs(t *template.Template, spec *openapi3.T) (string, error)
i := 0
for _, k := range keys {
servers[i] = ServerObjectDefinition{
GoName: k,
OAPISchema: names[k],
GoName: k,
OAPISchema: names[k],
EnumServerVariablesConflict: enumServerVariablesConflict,
}
i++
}
Expand Down
86 changes: 47 additions & 39 deletions pkg/codegen/templates/server-urls.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,66 @@
// {{ .GoName }} defines the Server URL for {{ .OAPISchema.Description }}
const {{ .GoName}} = "{{ .OAPISchema.URL }}"
{{ else }}
{{/* URLs with variables are not straightforward, as we may need multiple types, and so will model them as a function */}}

{{/* first, we'll start by generating requisite types */}}

{{/*
URLs with variables are not straightforward, as we may need multiple types, and so will model them as a function
first, we'll start by generating requisite types
*/}}
{{ $goName := .GoName }}
{{ $enumServerVariablesConflict := .EnumServerVariablesConflict }}

{{ range $k, $v := .OAPISchema.Variables }}
{{ $prefix := printf "%s%sVariable" $goName ($k | ucFirst) }}
// {{ $prefix }} is the `{{ $k }}` variable for {{ $goName }}
type {{ $prefix }} string
{{ range $v.Enum }}
{{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}}
// {{ $prefix }}{{ . | ucFirst }} is one of the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}{{ . | ucFirst }} {{ $prefix }} = "{{ . }}"
{{ end }}

{{/* TODO we should introduce a `Valid() error` method to enums https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}

{{ if $v.Default }}
{{ if gt (len $v.Enum) 0 }}
{{/* if we have an enum, we should use the type defined for it for its default value
and reference the constant we've already defined for the value */}}
{{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}}
{{/* TODO this may result in broken generated code if the `default` isn't found in `enum` (which is an issue with the spec) https://github.com/oapi-codegen/oapi-codegen/issues/2007 */}}
// {{ $prefix }}Default is the default choice, for the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default {{ $prefix }} = {{ $prefix }}{{ $v.Default | ucFirst }}
{{ else }}
// {{ $prefix }}Default is the default value for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default = "{{ $v.Default }}"
{{ end }}
{{ end }}
{{ end }}
{{ $prefix := printf "%s%sVariable" $goName ($k | ucFirst) }}
{{ $enumVarPrefix := $prefix }}
{{ if $enumServerVariablesConflict }}
{{ $enumVarPrefix = printf "%sEnum" $prefix }}
{{ end }} {{/* if $enumServerVariablesConflict */}}

// {{ $prefix }} is the `{{ $k }}` variable for {{ $goName }}
type {{ $prefix }} string

{{ range $v.Enum }}
// {{ $enumVarPrefix }}{{ . | ucFirst }} is one of the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $enumVarPrefix }}{{ . | ucFirst }} {{ $prefix }} = "{{ . }}"
{{ end }} {{/* range $v.Enum */}}

{{/* TODO we should introduce a `Valid() error` method to enums https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}

{{ if $v.Default }}
{{ if gt (len $v.Enum) 0 }}
{{/*
if we have an enum, we should use the type defined for it for its default value
and reference the constant we've already defined for the value
*/}}
{{/* TODO this may result in broken generated code if the `default` isn't found in `enum` (which is an issue with the spec) https://github.com/oapi-codegen/oapi-codegen/issues/2007 */}}
// {{ $prefix }}Default is the default choice, for the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default {{ $prefix }} = {{ $enumVarPrefix }}{{ $v.Default | ucFirst }}
{{ else }}
// {{ $prefix }}Default is the default value for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default = "{{ $v.Default }}"
{{ end }} {{/* if gt (len $v.Enum) 0 */}}
{{ end }} {{/* if $v.Default */}}
{{ end }} {{/* range $k, $v := .OAPISchema.Variables */}}


// New{{ .GoName }} constructs the Server URL for {{ .OAPISchema.Description }}, with the provided variables.
func New{{ .GoName }}({{ genServerURLWithVariablesFunctionParams .GoName .OAPISchema.Variables }}) (string, error) {
u := "{{ .OAPISchema.URL }}"

{{ range $k, $v := .OAPISchema.Variables }}
{{- $placeholder := printf "{%s}" $k -}}
{{- if gt (len $v.Enum) 0 -}}
{{/* TODO https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}
// TODO in the future, this will validate that the value is part of the {{ printf "%s%sVariable" $goName ($k | ucFirst) }} enum
{{ end -}}
u = strings.ReplaceAll(u, "{{ $placeholder }}", string({{ $k }}))
{{ end }}
{{- $placeholder := printf "{%s}" $k }}
{{- if gt (len $v.Enum) 0 -}}
{{/* TODO https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}
// TODO in the future, this will validate that the value is part of the {{ printf "%s%sVariable" $goName ($k | ucFirst) }} enum
{{- end }} {{/* if gt (len $v.Enum) 0 */}}
u = strings.ReplaceAll(u, "{{ $placeholder }}", string({{ $k }}))
{{- end }} {{/* range $k, $v := .OAPISchema.Variables */}}

if strings.Contains(u, "{") || strings.Contains(u, "}") {
return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u)
return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u)
}

return u, nil
}

{{ end }}
{{ end }}
{{ end }} {{/* if eq 0 (len .OAPISchema.Variables) */}}
{{ end }} {{/* range . */}}