fix: handle multi-type arrays in JSON schema to prevent panic (#6495)

Signed-off-by: robert-cronin <robert.owen.cronin@gmail.com>
This commit is contained in:
Robbie Cronin
2025-10-17 20:07:37 +11:00
committed by GitHub
parent bfb0794f87
commit 639ecb59b3
2 changed files with 111 additions and 8 deletions

View File

@@ -61,8 +61,26 @@ func (sc *JSONSchemaConverter) addRule(name, rule string) string {
func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string, rootSchema map[string]interface{}) (string, error) {
st, existType := schema["type"]
var schemaType string
var schemaTypes []string
if existType {
schemaType = st.(string)
// Handle both single type strings and arrays of types (e.g., ["string", "null"])
switch v := st.(type) {
case string:
// Single type: "type": "string"
schemaType = v
schemaTypes = []string{v}
case []interface{}:
// Multiple types: "type": ["string", "null"]
for _, item := range v {
if typeStr, ok := item.(string); ok {
schemaTypes = append(schemaTypes, typeStr)
}
}
// Use the first type as the primary schema type for compatibility
if len(schemaTypes) > 0 {
schemaType = schemaTypes[0]
}
}
}
ruleName := name
if name == "" {
@@ -176,14 +194,30 @@ func (sc *JSONSchemaConverter) visit(schema map[string]interface{}, name string,
rule := `"{" space "}" space`
return sc.addRule(ruleName, rule), nil
} else {
primitiveRule, exists := PRIMITIVE_RULES[schemaType]
if !exists {
return "", fmt.Errorf("unrecognized schema: %v (type: %s)", schema, schemaType)
// Handle primitive types, including multi-type arrays like ["string", "null"]
if len(schemaTypes) > 1 {
// Generate a union of multiple primitive types
var typeRules []string
for _, t := range schemaTypes {
primitiveRule, exists := PRIMITIVE_RULES[t]
if !exists {
return "", fmt.Errorf("unrecognized type in multi-type schema: %s (schema: %v)", t, schema)
}
typeRules = append(typeRules, primitiveRule)
}
rule := "(" + strings.Join(typeRules, " | ") + ")"
return sc.addRule(ruleName, rule), nil
} else {
// Single type
primitiveRule, exists := PRIMITIVE_RULES[schemaType]
if !exists {
return "", fmt.Errorf("unrecognized schema: %v (type: %s)", schema, schemaType)
}
if ruleName == "root" {
schemaType = "root"
}
return sc.addRule(schemaType, primitiveRule), nil
}
if ruleName == "root" {
schemaType = "root"
}
return sc.addRule(schemaType, primitiveRule), nil
}
}
func (sc *JSONSchemaConverter) resolveReference(ref string, rootSchema map[string]interface{}) (map[string]interface{}, error) {

View File

@@ -476,5 +476,74 @@ realvalue
Expect(err).To(BeNil())
Expect(grammar).To(ContainSubstring(`root ::= "{" space "}" space`))
})
It("handles multi-type array definitions like [string, null]", func() {
// Type defined as an array should not panic
multiTypeSchema := `{
"type": "object",
"properties": {
"street": {
"description": "The given street name where the company resides.",
"type": ["string", "null"]
},
"city": {
"description": "The given city where the company resides.",
"type": ["string", "null"]
}
}
}`
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(multiTypeSchema))
Expect(err).To(BeNil())
// The grammar should contain rules for both string and null types
Expect(grammar).To(ContainSubstring("string"))
Expect(grammar).To(ContainSubstring("null"))
// Should not panic and should generate valid grammar
Expect(grammar).ToNot(BeEmpty())
})
It("handles complex nested schema with multi-type arrays (issue #5572)", func() {
complexSchema := `{
"type": "object",
"properties": {
"companylist": {
"type": "array",
"items": {
"type": "object",
"properties": {
"companyname": {
"description": "The given name of the company.",
"type": "string"
},
"street": {
"description": "The given street name where the company resides.",
"type": ["string", "null"]
},
"city": {
"description": "The given city where the company resides.",
"type": ["string", "null"]
}
},
"additionalProperties": false,
"required": ["companyname", "street", "city"]
}
},
"filter": {
"description": "The type we should filter the list of companies by.",
"type": "string"
}
},
"required": ["companylist", "filter"],
"additionalProperties": false
}`
grammar, err := NewJSONSchemaConverter("").GrammarFromBytes([]byte(complexSchema))
Expect(err).To(BeNil())
// The grammar should be generated without panic
Expect(grammar).ToNot(BeEmpty())
// Should contain object and array structures
Expect(grammar).To(ContainSubstring("{"))
Expect(grammar).To(ContainSubstring("["))
})
})
})