mirror of
https://github.com/BishopFox/sj.git
synced 2026-11-07 10:26:01 -10:00
fix: Merge allOf subschemas into the schema node instead of replacing it
This commit is contained in:
1 parent
186841c2ba
commit
31aa3b3f2f
2 files changed
+146
-10
No files matched your search
+32
-10
@@ -490,21 +490,43 @@ func ExpandSchema(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle allOf (merge all schemas)
|
||||
// Handle allOf (merge every subschema into this node; the node's own values win)
|
||||
if allOf, ok := schema["allOf"].([]interface{}); ok {
|
||||
merged := &SchemaNode{Type: "object", Properties: map[string]*SchemaNode{}, Required: map[string]bool{}}
|
||||
for _, entry := range allOf {
|
||||
if m, ok := entry.(map[string]interface{}); ok {
|
||||
sub := ExpandSchema(spec, m, visited, contextSpec)
|
||||
for k, v := range sub.Properties {
|
||||
merged.Properties[k] = v
|
||||
}
|
||||
for k, v := range sub.Required {
|
||||
merged.Required[k] = v
|
||||
m, ok := entry.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sub := ExpandSchema(spec, m, visited, contextSpec)
|
||||
for k, v := range sub.Properties {
|
||||
if _, exists := node.Properties[k]; !exists {
|
||||
node.Properties[k] = v
|
||||
}
|
||||
}
|
||||
for k, v := range sub.Required {
|
||||
node.Required[k] = v
|
||||
}
|
||||
if node.Type == "" {
|
||||
node.Type = sub.Type
|
||||
}
|
||||
if node.Enum == nil {
|
||||
node.Enum = sub.Enum
|
||||
}
|
||||
if node.Example == nil {
|
||||
node.Example = sub.Example
|
||||
}
|
||||
if node.Items == nil {
|
||||
node.Items = sub.Items
|
||||
}
|
||||
if node.AdditionalProperties == nil {
|
||||
node.AdditionalProperties = sub.AdditionalProperties
|
||||
}
|
||||
node.OneOf = append(node.OneOf, sub.OneOf...)
|
||||
node.AnyOf = append(node.AnyOf, sub.AnyOf...)
|
||||
}
|
||||
if node.Type == "" {
|
||||
node.Type = "object"
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// Handle oneOf (expand all options)
|
||||
|
||||
@@ -721,3 +721,117 @@ func TestNormalizeBasePath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandSchemaAllOf(t *testing.T) {
|
||||
spec := map[string]interface{}{
|
||||
"components": map[string]interface{}{
|
||||
"schemas": map[string]interface{}{
|
||||
"Base": map[string]interface{}{
|
||||
"type": "object",
|
||||
"required": []interface{}{"id"},
|
||||
"properties": map[string]interface{}{
|
||||
"id": map[string]interface{}{"type": "integer"},
|
||||
},
|
||||
},
|
||||
"Extra": map[string]interface{}{
|
||||
"properties": map[string]interface{}{
|
||||
"note": map[string]interface{}{"type": "string"},
|
||||
},
|
||||
},
|
||||
"StrEnum": map[string]interface{}{
|
||||
"type": "string",
|
||||
"enum": []interface{}{"a", "b"},
|
||||
},
|
||||
"Dict": map[string]interface{}{
|
||||
"type": "object",
|
||||
"additionalProperties": map[string]interface{}{"type": "integer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ref := func(name string) map[string]interface{} {
|
||||
return map[string]interface{}{"$ref": "#/components/schemas/" + name}
|
||||
}
|
||||
expand := func(schema map[string]interface{}) *SchemaNode {
|
||||
return ExpandSchema(spec, schema, map[string]bool{}, spec)
|
||||
}
|
||||
|
||||
t.Run("sibling properties and required are merged", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{
|
||||
"allOf": []interface{}{ref("Base")},
|
||||
"required": []interface{}{"name"},
|
||||
"properties": map[string]interface{}{
|
||||
"name": map[string]interface{}{"type": "string"},
|
||||
},
|
||||
})
|
||||
if node.Type != "object" {
|
||||
t.Errorf("Type = %q, want object", node.Type)
|
||||
}
|
||||
for _, k := range []string{"id", "name"} {
|
||||
if node.Properties[k] == nil {
|
||||
t.Errorf("missing property %q", k)
|
||||
}
|
||||
if !node.Required[k] {
|
||||
t.Errorf("%q should be required", k)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sibling example and enum survive", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{
|
||||
"allOf": []interface{}{ref("StrEnum")},
|
||||
"enum": []interface{}{"x"},
|
||||
"example": "x",
|
||||
})
|
||||
if node.Example != "x" {
|
||||
t.Errorf("Example = %v, want x", node.Example)
|
||||
}
|
||||
if len(node.Enum) != 1 || node.Enum[0] != "x" {
|
||||
t.Errorf("Enum = %v, want [x]", node.Enum)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("primitive type and enum come from subschema", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{"allOf": []interface{}{ref("StrEnum")}})
|
||||
if node.Type != "string" {
|
||||
t.Errorf("Type = %q, want string", node.Type)
|
||||
}
|
||||
if len(node.Enum) != 2 {
|
||||
t.Errorf("Enum = %v, want [a b]", node.Enum)
|
||||
}
|
||||
if got := GenerateExample(node); got != "a" {
|
||||
t.Errorf("GenerateExample = %v, want a", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sibling items are kept", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{"type": "integer"},
|
||||
"allOf": []interface{}{map[string]interface{}{"description": "list"}},
|
||||
})
|
||||
if node.Type != "array" {
|
||||
t.Errorf("Type = %q, want array", node.Type)
|
||||
}
|
||||
if node.Items == nil || node.Items.Type != "integer" {
|
||||
t.Errorf("Items = %+v, want integer items", node.Items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("additionalProperties from subschema is carried over", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{"allOf": []interface{}{ref("Dict")}})
|
||||
if node.AdditionalProperties == nil || node.AdditionalProperties.Type != "integer" {
|
||||
t.Errorf("AdditionalProperties = %+v, want integer", node.AdditionalProperties)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("untyped composition defaults to object", func(t *testing.T) {
|
||||
node := expand(map[string]interface{}{"allOf": []interface{}{ref("Extra"), ref("Base")}})
|
||||
if node.Type != "object" {
|
||||
t.Errorf("Type = %q, want object", node.Type)
|
||||
}
|
||||
if node.Properties["id"] == nil || node.Properties["note"] == nil {
|
||||
t.Errorf("Properties = %v, want id and note", node.Properties)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user