diff --git a/datamodel/low/3.0/schema.go b/datamodel/low/3.0/schema.go index 6591850..970655b 100644 --- a/datamodel/low/3.0/schema.go +++ b/datamodel/low/3.0/schema.go @@ -70,8 +70,18 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error { level++ - if level > 10 { - return nil // we're done, son! too fricken deep. + if level > 30 { + return fmt.Errorf("schema is too nested to continue: %d levels deep, is too deep", level) // we're done, son! too fricken deep. + } + + if h, _, _ := utils.IsNodeRefValue(root); h { + ref := low.LocateRefNode(root, idx) + if ref != nil { + root = ref + } else { + return fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d", + root.Content[1].Value, root.Content[1].Line, root.Content[1].Column) + } } s.extractExtensions(root) @@ -102,10 +112,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er _, discLabel, discNode := utils.FindKeyNodeFull(DiscriminatorLabel, root.Content) if discNode != nil { var discriminator Discriminator - err := low.BuildModel(discNode, &discriminator) - if err != nil { - return err - } + _ = low.BuildModel(discNode, &discriminator) s.Discriminator = low.NodeReference[*Discriminator]{Value: &discriminator, KeyNode: discLabel, ValueNode: discNode} } @@ -113,14 +120,8 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er _, extDocLabel, extDocNode := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content) if extDocNode != nil { var exDoc ExternalDoc - err := low.BuildModel(extDocNode, &exDoc) - if err != nil { - return err - } - err = exDoc.Build(extDocNode, idx) - if err != nil { - return err - } + _ = low.BuildModel(extDocNode, &exDoc) + _ = exDoc.Build(extDocNode, idx) // throws no errors, can't check for one. s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode} } @@ -128,15 +129,9 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er _, xmlLabel, xmlNode := utils.FindKeyNodeFull(XMLLabel, root.Content) if xmlNode != nil { var xml XML - err := low.BuildModel(xmlNode, &xml) - if err != nil { - return err - } + _ = low.BuildModel(xmlNode, &xml) // extract extensions if set. - err = xml.Build(xmlNode) - if err != nil { - return err - } + _ = xml.Build(xmlNode) // returns no errors, can't check for one. s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode} } @@ -156,15 +151,15 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er ref := low.LocateRefNode(prop, idx) if ref != nil { prop = ref + } else { + return fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d", + prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line) } } var property Schema - err := low.BuildModel(prop, &property) - if err != nil { - return err - } - err = property.BuildLevel(prop, idx, level) + _ = low.BuildModel(prop, &property) + err := property.BuildLevel(prop, idx, level) if err != nil { return err } @@ -181,59 +176,60 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er KeyNode: propLabel, ValueNode: propsNode, } + } - // extract all sub-schemas - var errors []error + // extract all sub-schemas + var errors []error - var allOf, anyOf, oneOf, not, items []low.NodeReference[*Schema] + var allOf, anyOf, oneOf, not, items []low.NodeReference[*Schema] - // make this async at some point to speed things up. - allOfLabel, allOfValue := buildSchema(&allOf, AllOfLabel, root, level, &errors, idx) - anyOfLabel, anyOfValue := buildSchema(&anyOf, AnyOfLabel, root, level, &errors, idx) - oneOfLabel, oneOfValue := buildSchema(&oneOf, OneOfLabel, root, level, &errors, idx) - notLabel, notValue := buildSchema(¬, NotLabel, root, level, &errors, idx) - itemsLabel, itemsValue := buildSchema(&items, ItemsLabel, root, level, &errors, idx) + // make this async at some point to speed things up. + allOfLabel, allOfValue := buildSchema(&allOf, AllOfLabel, root, level, &errors, idx) + anyOfLabel, anyOfValue := buildSchema(&anyOf, AnyOfLabel, root, level, &errors, idx) + oneOfLabel, oneOfValue := buildSchema(&oneOf, OneOfLabel, root, level, &errors, idx) + notLabel, notValue := buildSchema(¬, NotLabel, root, level, &errors, idx) + itemsLabel, itemsValue := buildSchema(&items, ItemsLabel, root, level, &errors, idx) - if len(errors) > 0 { - // todo fix this - return errors[0] - } - if len(anyOf) > 0 { - s.AnyOf = low.NodeReference[[]low.NodeReference[*Schema]]{ - Value: anyOf, - KeyNode: anyOfLabel, - ValueNode: anyOfValue, - } - } - if len(oneOf) > 0 { - s.OneOf = low.NodeReference[[]low.NodeReference[*Schema]]{ - Value: oneOf, - KeyNode: oneOfLabel, - ValueNode: oneOfValue, - } - } - if len(allOf) > 0 { - s.AllOf = low.NodeReference[[]low.NodeReference[*Schema]]{ - Value: allOf, - KeyNode: allOfLabel, - ValueNode: allOfValue, - } - } - if len(not) > 0 { - s.Not = low.NodeReference[[]low.NodeReference[*Schema]]{ - Value: not, - KeyNode: notLabel, - ValueNode: notValue, - } - } - if len(items) > 0 { - s.Items = low.NodeReference[[]low.NodeReference[*Schema]]{ - Value: items, - KeyNode: itemsLabel, - ValueNode: itemsValue, - } + if len(errors) > 0 { + // todo fix this + return errors[0] + } + if len(anyOf) > 0 { + s.AnyOf = low.NodeReference[[]low.NodeReference[*Schema]]{ + Value: anyOf, + KeyNode: anyOfLabel, + ValueNode: anyOfValue, } } + if len(oneOf) > 0 { + s.OneOf = low.NodeReference[[]low.NodeReference[*Schema]]{ + Value: oneOf, + KeyNode: oneOfLabel, + ValueNode: oneOfValue, + } + } + if len(allOf) > 0 { + s.AllOf = low.NodeReference[[]low.NodeReference[*Schema]]{ + Value: allOf, + KeyNode: allOfLabel, + ValueNode: allOfValue, + } + } + if len(not) > 0 { + s.Not = low.NodeReference[[]low.NodeReference[*Schema]]{ + Value: not, + KeyNode: notLabel, + ValueNode: notValue, + } + } + if len(items) > 0 { + s.Items = low.NodeReference[[]low.NodeReference[*Schema]]{ + Value: items, + KeyNode: itemsLabel, + ValueNode: itemsValue, + } + } + return nil } @@ -246,15 +242,27 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo _, labelNode, valueNode = utils.FindKeyNodeFull(attribute, rootNode.Content) //wg.Add(1) + if valueNode == nil { + return nil, nil + } + if valueNode != nil { var build = func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] { var schema Schema - err := low.BuildModel(vn, &schema) - if err != nil { - *errors = append(*errors, err) - return nil + + if h, _, _ := utils.IsNodeRefValue(vn); h { + ref := low.LocateRefNode(vn, idx) + if ref != nil { + vn = ref + } else { + *errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d", + vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)) + return nil + } } - err = schema.BuildLevel(vn, idx, level) + + _ = low.BuildModel(vn, &schema) + err := schema.BuildLevel(vn, idx, level) if err != nil { *errors = append(*errors, err) return nil @@ -267,6 +275,17 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo } if utils.IsNodeMap(valueNode) { + if h, _, _ := utils.IsNodeRefValue(valueNode); h { + ref := low.LocateRefNode(valueNode, idx) + if ref != nil { + valueNode = ref + } else { + *errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d", + valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column)) + return + } + } + schema := build(labelNode, valueNode) if schema != nil { *schemas = append(*schemas, *schema) @@ -274,6 +293,17 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo } if utils.IsNodeArray(valueNode) { for _, vn := range valueNode.Content { + + if h, _, _ := utils.IsNodeRefValue(vn); h { + ref := low.LocateRefNode(vn, idx) + if ref != nil { + vn = ref + } else { + *errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d", + vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)) + } + } + schema := build(vn, vn) if schema != nil { *schemas = append(*schemas, *schema) @@ -316,11 +346,8 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S if schNode != nil { var schema Schema - err := low.BuildModel(schNode, &schema) - if err != nil { - return nil, err - } - err = schema.Build(schNode, idx) + _ = low.BuildModel(schNode, &schema) + err := schema.Build(schNode, idx) if err != nil { return nil, err } diff --git a/datamodel/low/3.0/schema_test.go b/datamodel/low/3.0/schema_test.go index bc63c5c..8e4cb5b 100644 --- a/datamodel/low/3.0/schema_test.go +++ b/datamodel/low/3.0/schema_test.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" "testing" @@ -218,3 +219,691 @@ additionalProperties: true ` mv = sch.Discriminator.Value.FindMappingValue("pizza") assert.Equal(t, "party", mv.Value) } + +func TestSchema_BuildLevel_TooDeep(t *testing.T) { + + // if you design data models like this, you're doing it fucking wrong. Seriously. why, what is so complex about a model + // that it needs to be 30+ levels deep? I have seen this shit in the wild, it's unreadable, un-parsable garbage. + yml := `type: object +properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object + properties: + aValue: + type: object` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + idx := index.NewSpecIndex(&idxNode) + + var n Schema + err := low.BuildModel(&idxNode, &n) + assert.NoError(t, err) + + err = n.Build(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func TestSchema_Build_ErrorAdditionalProps(t *testing.T) { + + yml := `additionalProperties: + $ref: #borko` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + idx := index.NewSpecIndex(&idxNode) + + var n Schema + err := low.BuildModel(&idxNode, &n) + assert.NoError(t, err) + + err = n.Build(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func TestSchema_Build_PropsLookup(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +properties: + aValue: + $ref: '#/components/schemas/Something'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + var n Schema + err := n.Build(idxNode.Content[0], idx) + assert.NoError(t, err) + assert.Equal(t, "this is something", n.FindProperty("aValue").Value.Description.Value) + +} + +func TestSchema_Build_PropsLookup_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +properties: + aValue: + $ref: '#/bork'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + var n Schema + err := n.Build(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func Test_Schema_Polymorphism_Array_Ref(t *testing.T) { + + doc := `components: + schemas: + Something: + type: object + description: poly thing + properties: + polyProp: + type: string + description: a property + example: anything` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + - $ref: '#/components/schemas/Something' +oneOf: + - $ref: '#/components/schemas/Something' +anyOf: + - $ref: '#/components/schemas/Something' +not: + - $ref: '#/components/schemas/Something' +items: + - $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.NoError(t, schErr) + + desc := "poly thing" + assert.Equal(t, desc, sch.OneOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.Not.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.Items.Value[0].Value.Description.Value) +} + +func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + type: object + description: poly thing + properties: + polyProp: + type: string + description: a property + example: anything` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + - $ref: '#/components/schemas/Missing' +oneOf: + - $ref: '#/components/schemas/Something' +anyOf: + - $ref: '#/components/schemas/Something' +not: + - $ref: '#/components/schemas/Something' +items: + - $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.Error(t, schErr) + +} + +func Test_Schema_Polymorphism_Map_Ref(t *testing.T) { + + doc := `components: + schemas: + Something: + type: object + description: poly thing + properties: + polyProp: + type: string + description: a property + example: anything` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: '#/components/schemas/Something' +oneOf: + $ref: '#/components/schemas/Something' +anyOf: + $ref: '#/components/schemas/Something' +not: + $ref: '#/components/schemas/Something' +items: + $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.NoError(t, schErr) + + desc := "poly thing" + assert.Equal(t, desc, sch.OneOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.Not.Value[0].Value.Description.Value) + assert.Equal(t, desc, sch.Items.Value[0].Value.Description.Value) +} + +func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + type: object + description: poly thing + properties: + polyProp: + type: string + description: a property + example: anything` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: '#/components/schemas/Missing' +oneOf: + $ref: '#/components/schemas/Something' +anyOf: + $ref: '#/components/schemas/Something' +not: + $ref: '#/components/schemas/Something' +items: + $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.Error(t, schErr) + +} + +func Test_Schema_Polymorphism_BorkParent(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: #borko` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.Error(t, schErr) + +} + +func Test_Schema_Polymorphism_BorkChild(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: #borko` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: #borko` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.Error(t, schErr) + +} + +func Test_Schema_Polymorphism_RefMadness(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: '#/components/schemas/Else' + Else: + description: madness` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.NoError(t, schErr) + + desc := "madness" + assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value) + +} + +func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: '#/components/schemas/Else' + Else: + $ref: #borko` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `type: object +allOf: + $ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.Error(t, schErr) + +} + +func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) { + + // this does not work, but it won't error out. + + doc := `components: + schemas: + Something: + $ref: '#/components/schemas/Else' + Else: + description: hey!` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `$ref: '#/components/schemas/Something'` + + var sch Schema + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + err := low.BuildModel(&idxNode, &sch) + assert.NoError(t, err) + + schErr := sch.Build(idxNode.Content[0], idx) + assert.NoError(t, schErr) + +} + +func TestExtractSchema(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `schema: + type: object + properties: + aValue: + $ref: '#/components/schemas/Something'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + res, err := ExtractSchema(idxNode.Content[0], idx) + assert.NoError(t, err) + assert.NotNil(t, res.Value) + assert.Equal(t, "this is something", res.Value.FindProperty("aValue").Value.Description.Value) +} + +func TestExtractSchema_Ref(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `schema: + $ref: '#/components/schemas/Something'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + res, err := ExtractSchema(idxNode.Content[0], idx) + assert.NoError(t, err) + assert.NotNil(t, res.Value) + assert.Equal(t, "this is something", res.Value.Description.Value) +} + +func TestExtractSchema_Ref_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `schema: + $ref: '#/components/schemas/Missing'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + _, err := ExtractSchema(idxNode.Content[0], idx) + assert.Error(t, err) +} + +func TestExtractSchema_RefRoot(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `$ref: '#/components/schemas/Something'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + res, err := ExtractSchema(idxNode.Content[0], idx) + assert.NoError(t, err) + assert.NotNil(t, res.Value) + assert.Equal(t, "this is something", res.Value.Description.Value) +} + +func TestExtractSchema_RefRoot_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `$ref: '#/components/schemas/Missing'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + _, err := ExtractSchema(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: #bork` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `$ref: '#/components/schemas/Something'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + _, err := ExtractSchema(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func TestExtractSchema_DoNothing(t *testing.T) { + + doc := `components: + schemas: + Something: + $ref: #bork` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(doc), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml := `please: do nothing.` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + res, err := ExtractSchema(idxNode.Content[0], idx) + assert.Nil(t, res) + assert.Nil(t, err) + +} diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 0e5fc6b..38338f6 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -43,6 +43,31 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node { idx.GetAllResponses, idx.GetAllSecuritySchemes, } + + // if there are any external indexes being used by remote + // documents, then we need to search through them also. + externalIndexes := idx.GetAllExternalIndexes() + if len(externalIndexes) > 0 { + var extCollection []func() map[string]*index.Reference + for _, extIndex := range externalIndexes { + extCollection = []func() map[string]*index.Reference{ + extIndex.GetAllSchemas, + extIndex.GetMappedReferences, + extIndex.GetAllExternalDocuments, + extIndex.GetAllParameters, + extIndex.GetAllHeaders, + extIndex.GetAllCallbacks, + extIndex.GetAllLinks, + extIndex.GetAllExternalDocuments, + extIndex.GetAllExamples, + extIndex.GetAllRequestBodies, + extIndex.GetAllResponses, + extIndex.GetAllSecuritySchemes, + } + collections = append(collections, extCollection...) + } + } + var found map[string]*index.Reference for _, collection := range collections { found = collection() @@ -69,6 +94,16 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node { } func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) { + + if h, _, _ := utils.IsNodeRefValue(root); h { + ref := LocateRefNode(root, idx) + if ref != nil { + root = ref + } else { + return nil, fmt.Errorf("object extraction failed: reference cannot be found: %s, line %d, col %d", + root.Content[1].Value, root.Content[1].Line, root.Content[1].Column) + } + } var n T = new(N) err := BuildModel(root, n) if err != nil { diff --git a/index/spec_index.go b/index/spec_index.go index 60b278d..c645cb3 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -495,6 +495,11 @@ func (index *SpecIndex) GetAllOperationsServers() map[string]map[string][]*Refer return index.opServersRefs } +// GetAllExternalIndexes will return all indexes for external documents +func (index *SpecIndex) GetAllExternalIndexes() map[string]*SpecIndex { + return index.externalSpecIndex +} + func (index *SpecIndex) checkPolymorphicNode(name string) (bool, string) { switch name { case "anyOf": diff --git a/openapi/create_document_test.go b/openapi/create_document_test.go index 2610e66..def6af3 100644 --- a/openapi/create_document_test.go +++ b/openapi/create_document_test.go @@ -13,7 +13,11 @@ var doc *v3.Document func init() { data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml") info, _ := datamodel.ExtractSpecInfo(data) - doc, _ = CreateDocument(info) + var err []error + doc, err = CreateDocument(info) + if err != nil { + panic("broken something") + } } func BenchmarkCreateDocument(b *testing.B) {