Skip to content

Commit 4730eda

Browse files
gabor-borosalexandear
authored andcommitted
feat: implement skipping pointer type (oapi-codegen#1102)
* feat: implement skipping pointer type Signed-off-by: Gabor Boros <gabor.brs@gmail.com> * docs: apply Go language naming Co-authored-by: Oleksandr Redko <oleksandr.red+github@gmail.com> --------- Signed-off-by: Gabor Boros <gabor.brs@gmail.com> Co-authored-by: Oleksandr Redko <oleksandr.red+github@gmail.com>
1 parent c6b11ac commit 4730eda

File tree

6 files changed

+195
-4
lines changed

6 files changed

+195
-4
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,20 @@ which help you to use the various OpenAPI 3 Authentication mechanism.
568568
will override any default value. This extended property isn't supported in all parts of
569569
OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will
570570
flag incorrect usage of this property.
571+
- `x-go-type-skip-optional-pointer`: specifies if the Go type should or should not be a pointer
572+
when the property is optional. If set to true, the type will not be a pointer if the field is
573+
optional or nullable. If set to false, the type will be a pointer.
574+
575+
```yaml
576+
properties:
577+
field:
578+
type: string
579+
x-go-type-skip-optional-pointer: true
580+
```
581+
582+
In the example above, the `field` field will be of type `string` instead of `*string`. This is
583+
useful when you want to handle the case of an empty string differently than a null value.
584+
571585
- `x-go-name`: specifies Go field name. It allows you to specify the field name for a schema, and
572586
will override any default value. This extended property isn't supported in all parts of
573587
OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will

pkg/codegen/codegen_test.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import (
1010
"net/http"
1111
"testing"
1212

13-
examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded"
14-
examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
15-
"github.com/deepmap/oapi-codegen/pkg/util"
1613
"github.com/getkin/kin-openapi/openapi3"
1714
"github.com/golangci/lint-1"
1815
"github.com/stretchr/testify/assert"
1916
"github.com/stretchr/testify/require"
17+
18+
examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded"
19+
examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
20+
"github.com/deepmap/oapi-codegen/pkg/util"
2021
)
2122

2223
const (
@@ -156,7 +157,7 @@ func TestExamplePetStoreCodeGenerationWithHTTPUserTemplates(t *testing.T) {
156157
defer ln.Close()
157158

158159
//nolint:errcheck
159-
//Does not matter if the server returns an error on close etc.
160+
// Does not matter if the server returns an error on close etc.
160161
go http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
161162
_, writeErr := w.Write([]byte("//blah"))
162163
assert.NoError(t, writeErr)
@@ -288,6 +289,44 @@ type GetTestByNameResponse struct {
288289
checkLint(t, "test.gen.go", []byte(code))
289290
}
290291

292+
func TestExtPropGoTypeSkipOptionalPointer(t *testing.T) {
293+
packageName := "api"
294+
opts := Configuration{
295+
PackageName: packageName,
296+
Generate: GenerateOptions{
297+
EchoServer: true,
298+
Models: true,
299+
EmbeddedSpec: true,
300+
Strict: true,
301+
},
302+
}
303+
spec := "test_specs/x-go-type-skip-optional-pointer.yaml"
304+
swagger, err := util.LoadSwagger(spec)
305+
require.NoError(t, err)
306+
307+
// Run our code generation:
308+
code, err := Generate(swagger, opts)
309+
assert.NoError(t, err)
310+
assert.NotEmpty(t, code)
311+
312+
// Check that we have valid (formattable) code:
313+
_, err = format.Source([]byte(code))
314+
assert.NoError(t, err)
315+
316+
// Check that optional pointer fields are skipped if requested
317+
assert.Contains(t, code, "NullableFieldSkipFalse *string `json:\"nullableFieldSkipFalse\"`")
318+
assert.Contains(t, code, "NullableFieldSkipTrue string `json:\"nullableFieldSkipTrue\"`")
319+
assert.Contains(t, code, "OptionalField *string `json:\"optionalField,omitempty\"`")
320+
assert.Contains(t, code, "OptionalFieldSkipFalse *string `json:\"optionalFieldSkipFalse,omitempty\"`")
321+
assert.Contains(t, code, "OptionalFieldSkipTrue string `json:\"optionalFieldSkipTrue,omitempty\"`")
322+
323+
// Check that the extension applies on custom types as well
324+
assert.Contains(t, code, "CustomTypeWithSkipTrue string `json:\"customTypeWithSkipTrue,omitempty\"`")
325+
326+
// Check that the extension has no effect on required fields
327+
assert.Contains(t, code, "RequiredField string `json:\"requiredField\"`")
328+
}
329+
291330
func TestGoTypeImport(t *testing.T) {
292331
packageName := "api"
293332
opts := Configuration{

pkg/codegen/extension.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
const (
88
// extPropGoType overrides the generated type definition.
99
extPropGoType = "x-go-type"
10+
// extPropGoTypeSkipOptionalPointer specifies that optional fields should
11+
// be the type itself instead of a pointer to the type.
12+
extPropGoTypeSkipOptionalPointer = "x-go-type-skip-optional-pointer"
1013
// extPropGoImport specifies the module to import which provides above type
1114
extPropGoImport = "x-go-type-import"
1215
// extGoName is used to override a field name
@@ -29,10 +32,19 @@ func extString(extPropValue interface{}) (string, error) {
2932
}
3033
return str, nil
3134
}
35+
3236
func extTypeName(extPropValue interface{}) (string, error) {
3337
return extString(extPropValue)
3438
}
3539

40+
func extParsePropGoTypeSkipOptionalPointer(extPropValue interface{}) (bool, error) {
41+
goTypeSkipOptionalPointer, ok := extPropValue.(bool)
42+
if !ok {
43+
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
44+
}
45+
return goTypeSkipOptionalPointer, nil
46+
}
47+
3648
func extParseGoFieldName(extPropValue interface{}) (string, error) {
3749
return extString(extPropValue)
3850
}

pkg/codegen/extension_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,57 @@ func Test_extTypeName(t *testing.T) {
5454
})
5555
}
5656
}
57+
58+
func Test_extParsePropGoTypeSkipOptionalPointer(t *testing.T) {
59+
type args struct {
60+
extPropValue json.RawMessage
61+
}
62+
tests := []struct {
63+
name string
64+
args args
65+
want bool
66+
wantErr bool
67+
}{
68+
{
69+
name: "success when set to true",
70+
args: args{json.RawMessage(`true`)},
71+
want: true,
72+
wantErr: false,
73+
},
74+
{
75+
name: "success when set to false",
76+
args: args{json.RawMessage(`false`)},
77+
want: false,
78+
wantErr: false,
79+
},
80+
{
81+
name: "nil conversion error",
82+
args: args{nil},
83+
want: false,
84+
wantErr: true,
85+
},
86+
{
87+
name: "type conversion error",
88+
args: args{json.RawMessage(`"true"`)},
89+
want: false,
90+
wantErr: true,
91+
},
92+
}
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
// kin-openapi no longer returns these as RawMessage
96+
var extPropValue interface{}
97+
if tt.args.extPropValue != nil {
98+
err := json.Unmarshal(tt.args.extPropValue, &extPropValue)
99+
assert.NoError(t, err)
100+
}
101+
got, err := extParsePropGoTypeSkipOptionalPointer(extPropValue)
102+
if tt.wantErr {
103+
assert.Error(t, err)
104+
return
105+
}
106+
assert.NoError(t, err)
107+
assert.Equal(t, tt.want, got)
108+
})
109+
}
110+
}

pkg/codegen/schema.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,16 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
286286
return outSchema, nil
287287
}
288288

289+
// Check x-go-type-skip-optional-pointer, which will override if the type
290+
// should be a pointer or not when the field is optional.
291+
if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok {
292+
skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension)
293+
if err != nil {
294+
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoTypeSkipOptionalPointer, err)
295+
}
296+
outSchema.SkipOptionalPointer = skipOptionalPointer
297+
}
298+
289299
// Schema type and format, eg. string / binary
290300
t := schema.Type
291301
// Handle objects and empty schemas first as a special case
@@ -672,6 +682,14 @@ func GenStuctFieldsFromSchema(schema Schema) []string {
672682
field += fmt.Sprintf("%s\n", DeprecationComment(deprecationReason))
673683
}
674684

685+
// Check x-go-type-skip-optional-pointer, which will override if the type
686+
// should be a pointer or not when the field is optional.
687+
if extension, ok := p.Extensions[extPropGoTypeSkipOptionalPointer]; ok {
688+
if skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension); err == nil {
689+
p.Schema.SkipOptionalPointer = skipOptionalPointer
690+
}
691+
}
692+
675693
field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef())
676694

677695
omitEmpty := !p.Nullable &&
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
openapi: "3.0.0"
2+
info:
3+
version: 1.0.0
4+
title: Example with x-go-type-skip-optional-pointer
5+
paths:
6+
/check:
7+
get:
8+
summary: Return example
9+
responses:
10+
'200':
11+
description: Ok
12+
content:
13+
application/json:
14+
schema:
15+
required:
16+
- requiredField
17+
properties:
18+
# Optional field where the type is a pointer to a string, and
19+
# the x-go-type-skip-optional-pointer is not set.
20+
optionalField:
21+
type: string
22+
# Optional field where the type is a pointer to a string, and
23+
# the x-go-type-skip-optional-pointer is set to false.
24+
optionalFieldSkipFalse:
25+
type: string
26+
x-go-type-skip-optional-pointer: false
27+
# Optional field where the type is a pointer to a string, and
28+
# the x-go-type-skip-optional-pointer is set to true.
29+
optionalFieldSkipTrue:
30+
type: string
31+
x-go-type-skip-optional-pointer: true
32+
# Optional field where the type is a pointer to a string, and
33+
# the x-go-type-skip-optional-pointer is set to true.
34+
customTypeWithSkipTrue:
35+
type: string
36+
x-go-type: string
37+
x-go-type-skip-optional-pointer: true
38+
# Nullable field where the type is a pointer to a string, and
39+
# the x-go-type-skip-optional-pointer is set to false.
40+
nullableFieldSkipFalse:
41+
type: string
42+
nullable: true
43+
x-go-type-skip-optional-pointer: false
44+
# Nullable field where the type is a pointer to a string, and
45+
# the x-go-type-skip-optional-pointer is set to true.
46+
nullableFieldSkipTrue:
47+
type: string
48+
nullable: true
49+
x-go-type-skip-optional-pointer: true
50+
# Check that x-go-type-skip-optional-pointer has no effect on
51+
# required fields.
52+
requiredField:
53+
type: string
54+
x-go-type-skip-optional-pointer: false

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy