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
4 changes: 4 additions & 0 deletions internal/test/issues/issue-2016/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package: issue2016
generate:
models: true
output: internal/test/issues/issue-2016/types.gen.go
146 changes: 146 additions & 0 deletions internal/test/issues/issue-2016/issue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package issue2016

import (
"encoding/json"
"testing"

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

// TestNullableArrayItemsMarshaling tests that nullable array items can handle nil values
func TestNullableArrayItemsMarshaling(t *testing.T) {
t.Run("Sample with nullable number items", func(t *testing.T) {
// Create a sample with nil values in the array
val1 := float32(1.5)
val3 := float32(3.5)

sample := Sample{
Metrics: []*float32{&val1, nil, &val3},
}

// Marshal to JSON
data, err := json.Marshal(sample)
require.NoError(t, err)

// Verify JSON contains null
assert.JSONEq(t, `{"metrics":[1.5,null,3.5]}`, string(data))

// Unmarshal back
var decoded Sample
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)

// Verify values
require.Len(t, decoded.Metrics, 3)
assert.Equal(t, float32(1.5), *decoded.Metrics[0])
assert.Nil(t, decoded.Metrics[1])
assert.Equal(t, float32(3.5), *decoded.Metrics[2])
})

t.Run("StringArrayNullable with nil values", func(t *testing.T) {
str1 := "hello"
str3 := "world"

obj := StringArrayNullable{
Values: &[]*string{&str1, nil, &str3},
}

data, err := json.Marshal(obj)
require.NoError(t, err)
assert.JSONEq(t, `{"values":["hello",null,"world"]}`, string(data))

var decoded StringArrayNullable
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)

require.NotNil(t, decoded.Values)
require.Len(t, *decoded.Values, 3)
assert.Equal(t, "hello", *(*decoded.Values)[0])
assert.Nil(t, (*decoded.Values)[1])
assert.Equal(t, "world", *(*decoded.Values)[2])
})

t.Run("BooleanArrayNullable with nil values", func(t *testing.T) {
valTrue := true
valFalse := false

obj := BooleanArrayNullable{
Flags: &[]*bool{&valTrue, nil, &valFalse},
}

data, err := json.Marshal(obj)
require.NoError(t, err)
assert.JSONEq(t, `{"flags":[true,null,false]}`, string(data))
})

t.Run("IntegerArrayNullable with nil values", func(t *testing.T) {
val1 := int64(100)
val3 := int64(300)

obj := IntegerArrayNullable{
Counts: &[]*int64{&val1, nil, &val3},
}

data, err := json.Marshal(obj)
require.NoError(t, err)
assert.JSONEq(t, `{"counts":[100,null,300]}`, string(data))
})

t.Run("NestedArrayNullable with nil values", func(t *testing.T) {
val1 := float32(1.0)
val3 := float32(3.0)

obj := NestedArrayNullable{
Matrix: &[][]*float32{
{&val1, nil, &val3},
{nil, nil, nil},
},
}

data, err := json.Marshal(obj)
require.NoError(t, err)
assert.JSONEq(t, `{"matrix":[[1.0,null,3.0],[null,null,null]]}`, string(data))
})

t.Run("NullableArrayWithNullableItems both null", func(t *testing.T) {
str1 := "test"

// Test with nil array
obj1 := NullableArrayWithNullableItems{
Data: nil,
}
data1, err := json.Marshal(obj1)
require.NoError(t, err)
assert.JSONEq(t, `{"data":null}`, string(data1))

// Test with array containing nil items
obj2 := NullableArrayWithNullableItems{
Data: &[]*string{&str1, nil},
}
data2, err := json.Marshal(obj2)
require.NoError(t, err)
assert.JSONEq(t, `{"data":["test",null]}`, string(data2))
})
}

// TestTypeCorrectness verifies that the types are generated correctly
func TestTypeCorrectness(t *testing.T) {
t.Run("Sample.Metrics should be []*float32", func(t *testing.T) {
var sample Sample
// This will fail to compile if the type is wrong
var _ []*float32 = sample.Metrics
})

t.Run("StringArrayNullable.Values should be *[]*string", func(t *testing.T) {
var obj StringArrayNullable
// This will fail to compile if the type is wrong
var _ *[]*string = obj.Values
})

t.Run("NestedArrayNullable.Matrix should be *[][]*float32", func(t *testing.T) {
var obj NestedArrayNullable
// This will fail to compile if the type is wrong
var _ *[][]*float32 = obj.Matrix
})
}
111 changes: 111 additions & 0 deletions internal/test/issues/issue-2016/spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
openapi: 3.0.0
info:
title: Issue 2016 - Nullable Array Items
version: 1.0.0
paths:
/test:
post:
operationId: testNullableArrayItems
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
sample:
$ref: "#/components/schemas/Sample"
strings:
$ref: "#/components/schemas/StringArrayNullable"
booleans:
$ref: "#/components/schemas/BooleanArrayNullable"
integers:
$ref: "#/components/schemas/IntegerArrayNullable"
objects:
$ref: "#/components/schemas/ObjectArrayNullable"
nested:
$ref: "#/components/schemas/NestedArrayNullable"
nullableArray:
$ref: "#/components/schemas/NullableArrayWithNullableItems"
responses:
'200':
description: OK
components:
schemas:
# Test case from the original issue
Sample:
type: object
required: [metrics]
properties:
metrics:
type: array
items:
nullable: true
type: number

# Additional test cases for various primitive types
StringArrayNullable:
type: object
properties:
values:
type: array
items:
nullable: true
type: string

BooleanArrayNullable:
type: object
properties:
flags:
type: array
items:
nullable: true
type: boolean

IntegerArrayNullable:
type: object
properties:
counts:
type: array
items:
nullable: true
type: integer
format: int64

# Test with object items
ObjectArrayNullable:
type: object
properties:
items:
type: array
items:
nullable: true
type: object
properties:
id:
type: integer
name:
type: string

# Test nested array with nullable items
NestedArrayNullable:
type: object
properties:
matrix:
type: array
items:
type: array
items:
nullable: true
type: number

# Test nullable array with nullable items
NullableArrayWithNullableItems:
type: object
properties:
data:
nullable: true
type: array
items:
nullable: true
type: string
56 changes: 56 additions & 0 deletions internal/test/issues/issue-2016/types.gen.go

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

9 changes: 8 additions & 1 deletion pkg/codegen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,14 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem
arrayType.RefType = typeName
}
outSchema.ArrayType = &arrayType
outSchema.GoType = "[]" + arrayType.TypeDecl()

// Handle nullable array items by adding pointer prefix
itemTypeDecl := arrayType.TypeDecl()
if arrayType.OAPISchema != nil && arrayType.OAPISchema.Nullable {
itemTypeDecl = "*" + itemTypeDecl
}

outSchema.GoType = "[]" + itemTypeDecl
outSchema.AdditionalTypes = arrayType.AdditionalTypes
outSchema.Properties = arrayType.Properties
outSchema.DefineViaAlias = true
Expand Down