diff --git a/generated/kbapi/transform_schema.go b/generated/kbapi/transform_schema.go index 4f37e2707..71ab95b1b 100644 --- a/generated/kbapi/transform_schema.go +++ b/generated/kbapi/transform_schema.go @@ -338,10 +338,30 @@ func (m Map) MustDelete(key string) { } func (m Map) CreateRef(schema *Schema, name string, key string) Map { - refTarget := m.MustGet(key) // Check the full path refPath := fmt.Sprintf("schemas.%s", name) refValue := Map{"$ref": fmt.Sprintf("#/components/schemas/%s", name)} + // If the target path doesn't exist, the Kibana componentizer likely already + // extracted it to a $ref. Skip gracefully — the rename transform will + // align component names later if needed. + refTarget, ok := m.Get(key) + if !ok { + log.Printf("CreateRef: skipping %q — path not found (likely already componentized)", key) + return refValue + } + + // If the target is already a $ref, skip. + if targetMap, isMap := refTarget.(Map); isMap { + if _, hasRef := targetMap["$ref"]; hasRef { + return refValue + } + } + if targetMap, isMap := refTarget.(map[string]any); isMap { + if _, hasRef := targetMap["$ref"]; hasRef { + return refValue + } + } + // If the component schema already exists and is not the same, panic writeComponent := true if existing, ok := schema.Components.Get(refPath); ok { diff --git a/generated/kbapi/transform_schema_test.go b/generated/kbapi/transform_schema_test.go index 1e2cb94dd..e2024f7c2 100644 --- a/generated/kbapi/transform_schema_test.go +++ b/generated/kbapi/transform_schema_test.go @@ -310,6 +310,149 @@ func TestRemoveDuplicateOneOfRefsFromNode(t *testing.T) { } } +func newSchema() *Schema { + return &Schema{ + Components: Map{ + "schemas": Map{}, + }, + } +} + +func TestCreateRef_InlineSchema(t *testing.T) { + schema := newSchema() + root := Map{ + "requestBody": Map{ + "content": Map{ + "application/json": Map{ + "schema": Map{ + "properties": Map{ + "config": Map{ + "type": "object", + "properties": Map{ + "name": Map{"type": "string"}, + }, + }, + }, + }, + }, + }, + }, + } + + ref := root.CreateRef(schema, "my_config", "requestBody.content.application/json.schema.properties.config") + + if ref["$ref"] != "#/components/schemas/my_config" { + t.Errorf("expected $ref to my_config, got %v", ref) + } + + component, ok := schema.Components.Get("schemas.my_config") + if !ok { + t.Fatal("expected component to be registered") + } + componentMap := component.(Map) + if componentMap["type"] != "object" { + t.Errorf("expected extracted component type=object, got %v", componentMap["type"]) + } + + // Verify the original location was replaced with a $ref + replaced := root.MustGet("requestBody.content.application/json.schema.properties.config") + replacedMap := replaced.(Map) + if replacedMap["$ref"] != "#/components/schemas/my_config" { + t.Errorf("expected inline schema to be replaced with $ref, got %v", replacedMap) + } +} + +func TestCreateRef_AlreadyRef(t *testing.T) { + schema := newSchema() + root := Map{ + "responses": Map{ + "200": Map{ + "content": Map{ + "application/json": Map{ + "schema": Map{ + "$ref": "#/components/schemas/existing_component", + }, + }, + }, + }, + }, + } + + ref := root.CreateRef(schema, "my_component", "responses.200.content.application/json.schema") + + if ref["$ref"] != "#/components/schemas/my_component" { + t.Errorf("expected $ref return value, got %v", ref) + } + + // Component should NOT have been registered — target was already a $ref + if _, ok := schema.Components.Get("schemas.my_component"); ok { + t.Error("expected component to NOT be registered when target is already a $ref") + } +} + +func TestCreateRef_AlreadyRef_MapStringAny(t *testing.T) { + schema := newSchema() + // Use map[string]any instead of Map to test the second type check + root := Map{ + "responses": Map{ + "200": map[string]any{ + "schema": map[string]any{ + "$ref": "#/components/schemas/existing_component", + }, + }, + }, + } + + ref := root.CreateRef(schema, "my_component", "responses.200.schema") + + if ref["$ref"] != "#/components/schemas/my_component" { + t.Errorf("expected $ref return value, got %v", ref) + } + + if _, ok := schema.Components.Get("schemas.my_component"); ok { + t.Error("expected component to NOT be registered when target is already a $ref (map[string]any)") + } +} + +func TestCreateRef_PathNotFound(t *testing.T) { + schema := newSchema() + root := Map{ + "responses": Map{ + "200": Map{ + "content": Map{}, + }, + }, + } + + // This path doesn't exist — should return gracefully, not panic + ref := root.CreateRef(schema, "missing_schema", "responses.200.content.application/json.schema") + + if ref["$ref"] != "#/components/schemas/missing_schema" { + t.Errorf("expected $ref return value, got %v", ref) + } + + if _, ok := schema.Components.Get("schemas.missing_schema"); ok { + t.Error("expected component to NOT be registered when path doesn't exist") + } +} + +func TestCreateRef_DuplicateIdenticalComponent(t *testing.T) { + schema := newSchema() + inlineSchema := Map{"type": "string", "description": "A name"} + + // First call: register the component + root1 := Map{"props": Map{"name": deepCopyMap(inlineSchema)}} + root1.CreateRef(schema, "shared_name", "props.name") + + // Second call with identical schema: should not panic + root2 := Map{"props": Map{"name": deepCopyMap(inlineSchema)}} + ref := root2.CreateRef(schema, "shared_name", "props.name") + + if ref["$ref"] != "#/components/schemas/shared_name" { + t.Errorf("expected $ref return value, got %v", ref) + } +} + // deepCopyMap creates a deep copy of a Map for testing purposes func deepCopyMap(m Map) Map { result := make(Map)