Skip to content
Merged
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
4 changes: 4 additions & 0 deletions internal/test/issues/issue-2031/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package: issue2031
generate:
models: true
output: issue2031.gen.go
3 changes: 3 additions & 0 deletions internal/test/issues/issue-2031/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package issue2031

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml
83 changes: 83 additions & 0 deletions internal/test/issues/issue-2031/issue2031.gen.go

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

18 changes: 18 additions & 0 deletions internal/test/issues/issue-2031/issue2031_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package issue2031

import (
"encoding/json"
"testing"

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

func TestMarshal(t *testing.T) {
value := ArrayContainer{}
content, err := json.Marshal(value)
require.NoError(t, err)
// the _optional array_ should be _omitted_ when null, not marshaled as null
// (which is not valid per the schema)
assert.Equal(t, "{}", string(content))
}
27 changes: 27 additions & 0 deletions internal/test/issues/issue-2031/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Issue 2031
paths:
/test:
get:
responses:
"200":
description: A list of strings
content:
application/json:
schema:
$ref: "#/components/schemas/ArrayContainer"
components:
schemas:
ArrayContainer:
type: object
# enabling additionalProperties is required to expose one variant of the bug
additionalProperties: true
properties:
# NOTE: the array property is NOT required and NOT nullable
values:
x-go-type-skip-optional-pointer: true
type: array
items:
type: string
17 changes: 17 additions & 0 deletions pkg/codegen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,29 @@ func (p Property) GoTypeDef() string {
return typeDef
}

// RequiresNilCheck indicates whether the generated property should have a nil check performed on it before other checks.
// This should be used in templates when performing `nil` checks, but NOT when i.e. determining if there should be an optional pointer given to the type - in that case, use `HasOptionalPointer`
func (p Property) RequiresNilCheck() bool {
return p.ZeroValueIsNil() || p.HasOptionalPointer()
}

// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it.
// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped.
func (p Property) HasOptionalPointer() bool {
return p.Required == false && p.Schema.SkipOptionalPointer == false //nolint:staticcheck
}

// ZeroValueIsNil is a helper function to determine if the given Go type used for this property
// Will return true if the OpenAPI `type` is:
// - `array`
func (p Property) ZeroValueIsNil() bool {
if p.Schema.OAPISchema == nil {
return false
}

return p.Schema.OAPISchema.Type.Is("array")
}

// EnumDefinition holds type information for enum
type EnumDefinition struct {
// Schema is the scheme of a type which has a list of enum values, eg, the
Expand Down
65 changes: 65 additions & 0 deletions pkg/codegen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package codegen
import (
"testing"

"github.com/getkin/kin-openapi/openapi3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestProperty_GoTypeDef(t *testing.T) {
Expand Down Expand Up @@ -454,3 +456,66 @@ func TestProperty_GoTypeDef_nullable(t *testing.T) {
})
}
}

func TestProperty_ZeroValueIsNil(t *testing.T) {
newType := func(typ string) *openapi3.Types {
return &openapi3.Types{typ}
}

tests := []struct {
name string
oapiSchema *openapi3.Schema
expectIsNil bool
}{
{
name: "when an array, returns true",
oapiSchema: &openapi3.Schema{Type: newType("array")},
expectIsNil: true,
},
{
name: "when an object, returns false",
oapiSchema: &openapi3.Schema{Type: newType("object")},
expectIsNil: false,
},
{
name: "when a string, returns false",
oapiSchema: &openapi3.Schema{Type: newType("string")},
expectIsNil: false,
},
{
name: "when an integer, returns false",
oapiSchema: &openapi3.Schema{Type: newType("integer")},
expectIsNil: false,
},
{
name: "when a number, returns false",
oapiSchema: &openapi3.Schema{Type: newType("number")},
expectIsNil: false,
},
{
name: "when OAPISchema is nil, returns false",
oapiSchema: nil,
expectIsNil: false,
},
{
name: "when OAPISchema is zero value, returns false",
oapiSchema: &openapi3.Schema{},
expectIsNil: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prop := Property{
Schema: Schema{
OAPISchema: tt.oapiSchema,
},
}
if tt.expectIsNil {
require.True(t, prop.ZeroValueIsNil())
} else {
require.False(t, prop.ZeroValueIsNil())
}
})
}
}
4 changes: 2 additions & 2 deletions pkg/codegen/templates/additional-properties.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
{{range .Schema.Properties}}
{{if .HasOptionalPointer}}if a.{{.GoFieldName}} != nil { {{end}}
{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if .HasOptionalPointer}} }{{end}}
{{if .RequiresNilCheck}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
Expand Down
4 changes: 2 additions & 2 deletions pkg/codegen/templates/union-and-additional-properties.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
}
}
{{range .Schema.Properties}}
{{if .HasOptionalPointer}}if a.{{.GoFieldName}} != nil { {{end}}
{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if .HasOptionalPointer}} }{{end}}
{{if .RequiresNilCheck}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
Expand Down
4 changes: 2 additions & 2 deletions pkg/codegen/templates/union.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@
}
}
{{range .Schema.Properties}}
{{if .HasOptionalPointer}}if t.{{.GoFieldName}} != nil { {{end}}
{{if .RequiresNilCheck}}if t.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if .HasOptionalPointer}} }{{end}}
{{if .RequiresNilCheck}} }{{end}}
{{end -}}
b, err = json.Marshal(object)
{{end -}}
Expand Down
Loading