From 2bc8eddfc66d794710ac9670127dcc56dfb33ba9 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Fri, 12 Jul 2024 13:17:25 +0100 Subject: [PATCH] feat(extension): add `x-oapi-codegen-only-honour-go-name` Although originally to fix #1638, which isn't a very recommended approach as it may lead to a confusing behaviour for users, we can still add support. However, we should hide this behind both a global Compatibility Option, to ensure that it's clearly opted-in, as well as adding a per-field level to ensure that only the right fields are overridden. Closes #1638. --- README.md | 57 +++++++++++++++++++ configuration-schema.json | 4 ++ .../xoapicodegenonlyhonourgoname/api.yaml | 18 ++++++ .../xoapicodegenonlyhonourgoname/cfg.yaml | 10 ++++ .../xoapicodegenonlyhonourgoname/gen.go | 10 ++++ .../xoapicodegenonlyhonourgoname/gen_test.go | 18 ++++++ .../xoapicodegenonlyhonourgoname/generate.go | 3 + pkg/codegen/configuration.go | 15 +++++ pkg/codegen/extension.go | 11 ++++ pkg/codegen/schema.go | 10 ++++ 10 files changed, 156 insertions(+) create mode 100644 examples/extensions/xoapicodegenonlyhonourgoname/api.yaml create mode 100644 examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml create mode 100644 examples/extensions/xoapicodegenonlyhonourgoname/gen.go create mode 100644 examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go create mode 100644 examples/extensions/xoapicodegenonlyhonourgoname/generate.go diff --git a/README.md b/README.md index ab01d54e0b..4213e10a26 100644 --- a/README.md +++ b/README.md @@ -2743,6 +2743,63 @@ You can see this in more detail in [the example code](examples/extensions/xorder + + + +`x-oapi-codegen-only-honour-go-name` + + + +Only honour the `x-go-name` when generating field names + + +
+ +> [!WARNING] +> Using this option may lead to cases where `oapi-codegen`'s rewriting of field names to prevent clashes with other types, or to prevent including characters that may not be valid Go field names. + +In some cases, you may not want use the inbuilt options for converting an OpenAPI field name to a Go field name, such as the `name-normalizer: "ToCamelCaseWithInitialisms"`, and instead trust the name that you've defined for the type better. + +In this case, you can use `x-oapi-codegen-only-honour-go-name` to enforce this, alongside specifying the `allow-unexported-struct-field-names` compatibility option. + +This allows you to take a spec such as: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-only-honour-go-name +components: + schemas: + TypeWithUnexportedField: + description: A struct will be output where one of the fields is not exported + properties: + name: + type: string + id: + type: string + # NOTE that there is an explicit usage of a lowercase character + x-go-name: accountIdentifier + x-oapi-codegen-extra-tags: + json: "-" + x-oapi-codegen-only-honour-go-name: true +``` + +And we'll generate: + +```go +// TypeWithUnexportedField A struct will be output where one of the fields is not exported +type TypeWithUnexportedField struct { + accountIdentifier *string `json:"-"` + Name *string `json:"name,omitempty"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xoapicodegenonlyhonourgoname). + +
+ + diff --git a/configuration-schema.json b/configuration-schema.json index ce346d92e9..836d48ad61 100644 --- a/configuration-schema.json +++ b/configuration-schema.json @@ -99,6 +99,10 @@ "circular-reference-limit": { "type": "integer", "description": "DEPRECATED: No longer used.\nCircularReferenceLimit allows controlling the limit for circular reference checking. In some OpenAPI specifications, we have a higher number of circular references than is allowed out-of-the-box, but can be tuned to allow traversing them." + }, + "allow-unexported-struct-field-names": { + "type": "boolean", + "description": "AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported.\nThis is expected to be used in conjunction with an extension such as `x-go-name` to override the output name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`.\nNOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see it in the response, without understanding the nuance of how `oapi-codegen` generates the code." } } }, diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml b/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml new file mode 100644 index 0000000000..7a1fc9e3ce --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml @@ -0,0 +1,18 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-only-honour-go-name +components: + schemas: + TypeWithUnexportedField: + description: A struct will be output where one of the fields is not exported + properties: + name: + type: string + id: + type: string + # NOTE that there is an explicit usage of a lowercase character + x-go-name: accountIdentifier + x-oapi-codegen-extra-tags: + json: "-" + x-oapi-codegen-only-honour-go-name: true diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml b/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml new file mode 100644 index 0000000000..222b37e2ef --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xoapicodegenonlyhonourgoname +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true +compatibility: + allow-unexported-struct-field-names: true diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/gen.go b/examples/extensions/xoapicodegenonlyhonourgoname/gen.go new file mode 100644 index 0000000000..71e3cca4b5 --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/gen.go @@ -0,0 +1,10 @@ +// Package xoapicodegenonlyhonourgoname provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xoapicodegenonlyhonourgoname + +// TypeWithUnexportedField A struct will be output where one of the fields is not exported +type TypeWithUnexportedField struct { + accountIdentifier *string `json:"-"` + Name *string `json:"name,omitempty"` +} diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go b/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go new file mode 100644 index 0000000000..3e616bf99c --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go @@ -0,0 +1,18 @@ +package xoapicodegenonlyhonourgoname + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTypeWithUnexportedField(t *testing.T) { + var v TypeWithUnexportedField + + err := json.Unmarshal([]byte(`{"id": "some-id"}`), &v) + require.NoError(t, err) + + // this field will never be unmarshaled + require.Nil(t, v.accountIdentifier) +} diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/generate.go b/examples/extensions/xoapicodegenonlyhonourgoname/generate.go new file mode 100644 index 0000000000..938d489126 --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/generate.go @@ -0,0 +1,3 @@ +package xoapicodegenonlyhonourgoname + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 89c950ae81..c26be2d36f 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -103,6 +103,21 @@ type CompatibilityOptions struct { // traversing them. // Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts. CircularReferenceLimit int `yaml:"circular-reference-limit"` + // AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported. + // + // This is expected to be used in conjunction with `x-go-name` and `x-oapi-codegen-only-honour-go-name` to override the resulting output field name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`, such as: + // + // ```yaml + // id: + // type: string + // x-go-name: accountIdentifier + // x-oapi-codegen-extra-tags: + // json: "-" + // x-oapi-codegen-only-honour-go-name: true + // ``` + // + // NOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see/use it in the request/response, without understanding the nuance of how `oapi-codegen` generates the code. + AllowUnexportedStructFieldNames bool `yaml:"allow-unexported-struct-field-names"` } // OutputOptions are used to modify the output code in some way. diff --git a/pkg/codegen/extension.go b/pkg/codegen/extension.go index 03b726fba7..f5ef5ef4d2 100644 --- a/pkg/codegen/extension.go +++ b/pkg/codegen/extension.go @@ -23,6 +23,9 @@ const ( extEnumNames = "x-enumNames" extDeprecationReason = "x-deprecated-reason" extOrder = "x-order" + // extOapiCodegenOnlyHonourGoName is to be used to explicitly enforce the generation of a field as the `x-go-name` extension has describe it. + // This is intended to be used alongside the `allow-unexported-struct-field-names` Compatibility option + extOapiCodegenOnlyHonourGoName = "x-oapi-codegen-only-honour-go-name" ) func extString(extPropValue interface{}) (string, error) { @@ -100,3 +103,11 @@ func extParseEnumVarNames(extPropValue interface{}) ([]string, error) { func extParseDeprecationReason(extPropValue interface{}) (string, error) { return extString(extPropValue) } + +func extParseOapiCodegenOnlyHonourGoName(extPropValue interface{}) (bool, error) { + onlyHonourGoName, ok := extPropValue.(bool) + if !ok { + return false, fmt.Errorf("failed to convert type: %T", extPropValue) + } + return onlyHonourGoName, nil +} diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index f734517a9a..3d4bdbbddb 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -100,6 +100,16 @@ func (p Property) GoFieldName() string { } } + if globalState.options.Compatibility.AllowUnexportedStructFieldNames { + if extension, ok := p.Extensions[extOapiCodegenOnlyHonourGoName]; ok { + if extOapiCodegenOnlyHonourGoName, err := extParseOapiCodegenOnlyHonourGoName(extension); err == nil { + if extOapiCodegenOnlyHonourGoName { + return goFieldName + } + } + } + } + return SchemaNameToTypeName(goFieldName) }