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
20 changes: 20 additions & 0 deletions helpers/schema_compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,26 @@ func transformNullableSchema(schema map[string]interface{}) map[string]interface
schema["oneOf"] = oneOfSlice
}

// Handle enum values - add null if nullable but not already in enum
enum, hasEnum := schema["enum"]
if hasEnum {
if enumSlice, ok := enum.([]interface{}); ok {
// Check if null is already in enum
hasNull := false
for _, v := range enumSlice {
if v == nil {
hasNull = true
break
}
}
// Add null if not present
if !hasNull {
enumSlice = append(enumSlice, nil)
schema["enum"] = enumSlice
}
}
}

return schema
}

Expand Down
95 changes: 95 additions & 0 deletions helpers/schema_compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,98 @@ func TestTransformTypeForCoercion_EdgeCases(t *testing.T) {
result = transformTypeForCoercion([]interface{}{"string"})
assert.Equal(t, []interface{}{"string"}, result)
}

func TestTransformNullableSchema_EnumWithoutNull(t *testing.T) {
// Test case: nullable: true with enum that doesn't contain null
// Expected: null should be automatically added to the enum
schema := map[string]interface{}{
"type": "string",
"enum": []interface{}{
"active",
"inactive",
"pending",
"archived",
},
"nullable": true,
}

result := transformNullableSchema(schema)

// nullable keyword should be removed
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)

// type should be converted to array including null
schemaType, ok := result["type"]
require.True(t, ok)

typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")

// enum should contain null
enum, ok := result["enum"]
require.True(t, ok)

enumSlice, ok := enum.([]interface{})
require.True(t, ok)
assert.Len(t, enumSlice, 5) // original 4 values + null
assert.Contains(t, enumSlice, "active")
assert.Contains(t, enumSlice, "inactive")
assert.Contains(t, enumSlice, "pending")
assert.Contains(t, enumSlice, "archived")
assert.Contains(t, enumSlice, nil)
}

func TestTransformNullableSchema_EnumWithNull(t *testing.T) {
// Test case: nullable: true with enum that already contains null
// Expected: null should NOT be added twice
schema := map[string]interface{}{
"type": "string",
"enum": []interface{}{
"active",
"inactive",
"pending",
"archived",
nil,
},
"nullable": true,
}

result := transformNullableSchema(schema)

// nullable keyword should be removed
_, hasNullable := result["nullable"]
assert.False(t, hasNullable)

// type should be converted to array including null
schemaType, ok := result["type"]
require.True(t, ok)

typeArray, ok := schemaType.([]interface{})
require.True(t, ok)
assert.Contains(t, typeArray, "string")
assert.Contains(t, typeArray, "null")

// enum should still contain only one null (not duplicated)
enum, ok := result["enum"]
require.True(t, ok)

enumSlice, ok := enum.([]interface{})
require.True(t, ok)
assert.Len(t, enumSlice, 5) // original 5 values (no duplication)
assert.Contains(t, enumSlice, "active")
assert.Contains(t, enumSlice, "inactive")
assert.Contains(t, enumSlice, "pending")
assert.Contains(t, enumSlice, "archived")

// Count how many nulls are in the enum
nullCount := 0
for _, v := range enumSlice {
if v == nil {
nullCount++
}
}
assert.Equal(t, 1, nullCount, "enum should contain exactly one null value")
}
131 changes: 131 additions & 0 deletions test_specs/nullable_enum.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
openapi: 3.0.2
info:
title: Nullable Enum Test API
version: 1.0.0
description: Test specification for nullable enum validation
paths:
/status:
get:
summary: Get status with nullable enum
operationId: getStatus
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/StatusResponse'
post:
summary: Create status
operationId: createStatus
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/StatusRequest'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/StatusResponse'
/items:
get:
summary: Get items with nullable enum in array
operationId: getItems
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Item'
components:
schemas:
StatusResponse:
type: object
required:
- id
properties:
id:
type: integer
format: int64
status:
type: string
description: Status field with nullable enum (no null in enum)
enum:
- active
- inactive
- pending
- archived
nullable: true
priority:
type: string
description: Priority field with nullable enum (null already in enum)
enum:
- high
- medium
- low
- null
nullable: true
category:
type: string
description: Non-nullable enum
enum:
- public
- private
- internal
StatusRequest:
type: object
required:
- status
properties:
status:
type: string
enum:
- active
- inactive
- pending
- archived
nullable: true
priority:
type: string
enum:
- high
- medium
- low
- null
nullable: true
Item:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
status:
type: string
description: Nested nullable enum
enum:
- available
- sold
- reserved
nullable: true
metadata:
type: object
properties:
visibility:
type: string
description: Deeply nested nullable enum
enum:
- visible
- hidden
nullable: true
Loading