diff --git a/index/index_model.go b/index/index_model.go index c237838..6e0485d 100644 --- a/index/index_model.go +++ b/index/index_model.go @@ -98,96 +98,96 @@ func CreateClosedAPIIndexConfig() *SpecIndexConfig { // quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules, // everything is pre-walked if you need it. type SpecIndex struct { - allRefs map[string]*Reference // all (deduplicated) refs - rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped. - linesWithRefs map[int]bool // lines that link to references. - allMappedRefs map[string]*Reference // these are the located mapped refs - allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs - refsByLine map[string]map[int]bool // every reference and the lines it's referenced from - pathRefs map[string]map[string]*Reference // all path references - paramOpRefs map[string]map[string]map[string]*Reference // params in operations. - paramCompRefs map[string]*Reference // params in components - paramAllRefs map[string]*Reference // combined components and ops - paramInlineDuplicates map[string][]*Reference // inline params all with the same name - globalTagRefs map[string]*Reference // top level global tags - securitySchemeRefs map[string]*Reference // top level security schemes - requestBodiesRefs map[string]*Reference // top level request bodies - responsesRefs map[string]*Reference // top level responses - headersRefs map[string]*Reference // top level responses - examplesRefs map[string]*Reference // top level examples - securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements - callbacksRefs map[string]map[string][]*Reference // all links - linksRefs map[string]map[string][]*Reference // all callbacks - operationTagsRefs map[string]map[string][]*Reference // tags found in operations - operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations. - operationSummaryRefs map[string]map[string]*Reference // summaries in operations - callbackRefs map[string]*Reference // top level callback refs - serversRefs []*Reference // all top level server refs - rootServersNode *yaml.Node // servers root node - opServersRefs map[string]map[string][]*Reference // all operation level server overrides. - polymorphicRefs map[string]*Reference // every reference to a polymorphic ref - polymorphicAllOfRefs []*Reference // every reference to 'allOf' references - polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references - polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references - externalDocumentsRef []*Reference // all external documents in spec - rootSecurity []*Reference // root security definitions. - rootSecurityNode *yaml.Node // root security node. - refsWithSiblings map[string]Reference // references with sibling elements next to them - pathRefsLock sync.Mutex // create lock for all refs maps, we want to build data as fast as we can - externalDocumentsCount int // number of externalDocument nodes found - operationTagsCount int // number of unique tags in operations - globalTagsCount int // number of global tags defined - totalTagsCount int // number unique tags in spec - securitySchemesCount int // security schemes - globalRequestBodiesCount int // component request bodies - globalResponsesCount int // component responses - globalHeadersCount int // component headers - globalExamplesCount int // component examples - globalLinksCount int // component links - globalCallbacksCount int // component callbacks - globalCallbacks int // component callbacks. - pathCount int // number of paths - operationCount int // number of operations - operationParamCount int // number of params defined in operations - componentParamCount int // number of params defined in components - componentsInlineParamUniqueCount int // number of inline params with unique names - componentsInlineParamDuplicateCount int // number of inline params with duplicate names - schemaCount int // number of schemas - refCount int // total ref count - root *yaml.Node // the root document - pathsNode *yaml.Node // paths node - tagsNode *yaml.Node // tags node - componentsNode *yaml.Node // components node - parametersNode *yaml.Node // components/parameters node - allParametersNode map[string]*Reference // all parameters node - allParameters map[string]*Reference // all parameters (components/defs) - schemasNode *yaml.Node // components/schemas node - allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger). - allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger). - allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger). - securitySchemesNode *yaml.Node // components/securitySchemes node - allSecuritySchemes map[string]*Reference // all security schemes / definitions. - requestBodiesNode *yaml.Node // components/requestBodies node - allRequestBodies map[string]*Reference // all request bodies - responsesNode *yaml.Node // components/responses node - allResponses map[string]*Reference // all responses - headersNode *yaml.Node // components/headers node - allHeaders map[string]*Reference // all headers - examplesNode *yaml.Node // components/examples node - allExamples map[string]*Reference // all components examples - linksNode *yaml.Node // components/links node - allLinks map[string]*Reference // all links - callbacksNode *yaml.Node // components/callbacks node - allCallbacks map[string]*Reference // all components examples - externalDocumentsNode *yaml.Node // external documents node - allExternalDocuments map[string]*Reference // all external documents - externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds - refErrors []error // errors when indexing references - operationParamErrors []error // errors when indexing parameters - allDescriptions []*DescriptionReference // every single description found in the spec. - allSummaries []*DescriptionReference // every single summary found in the spec. - allEnums []*EnumReference // every single enum found in the spec. - allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec. + allRefs map[string]*Reference // all (deduplicated) refs + rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped. + linesWithRefs map[int]bool // lines that link to references. + allMappedRefs map[string]*Reference // these are the located mapped refs + allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs + refsByLine map[string]map[int]bool // every reference and the lines it's referenced from + pathRefs map[string]map[string]*Reference // all path references + paramOpRefs map[string]map[string]map[string][]*Reference // params in operations. + paramCompRefs map[string]*Reference // params in components + paramAllRefs map[string]*Reference // combined components and ops + paramInlineDuplicateNames map[string][]*Reference // inline params all with the same name + globalTagRefs map[string]*Reference // top level global tags + securitySchemeRefs map[string]*Reference // top level security schemes + requestBodiesRefs map[string]*Reference // top level request bodies + responsesRefs map[string]*Reference // top level responses + headersRefs map[string]*Reference // top level responses + examplesRefs map[string]*Reference // top level examples + securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements + callbacksRefs map[string]map[string][]*Reference // all links + linksRefs map[string]map[string][]*Reference // all callbacks + operationTagsRefs map[string]map[string][]*Reference // tags found in operations + operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations. + operationSummaryRefs map[string]map[string]*Reference // summaries in operations + callbackRefs map[string]*Reference // top level callback refs + serversRefs []*Reference // all top level server refs + rootServersNode *yaml.Node // servers root node + opServersRefs map[string]map[string][]*Reference // all operation level server overrides. + polymorphicRefs map[string]*Reference // every reference to a polymorphic ref + polymorphicAllOfRefs []*Reference // every reference to 'allOf' references + polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references + polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references + externalDocumentsRef []*Reference // all external documents in spec + rootSecurity []*Reference // root security definitions. + rootSecurityNode *yaml.Node // root security node. + refsWithSiblings map[string]Reference // references with sibling elements next to them + pathRefsLock sync.Mutex // create lock for all refs maps, we want to build data as fast as we can + externalDocumentsCount int // number of externalDocument nodes found + operationTagsCount int // number of unique tags in operations + globalTagsCount int // number of global tags defined + totalTagsCount int // number unique tags in spec + securitySchemesCount int // security schemes + globalRequestBodiesCount int // component request bodies + globalResponsesCount int // component responses + globalHeadersCount int // component headers + globalExamplesCount int // component examples + globalLinksCount int // component links + globalCallbacksCount int // component callbacks + globalCallbacks int // component callbacks. + pathCount int // number of paths + operationCount int // number of operations + operationParamCount int // number of params defined in operations + componentParamCount int // number of params defined in components + componentsInlineParamUniqueCount int // number of inline params with unique names + componentsInlineParamDuplicateCount int // number of inline params with duplicate names + schemaCount int // number of schemas + refCount int // total ref count + root *yaml.Node // the root document + pathsNode *yaml.Node // paths node + tagsNode *yaml.Node // tags node + componentsNode *yaml.Node // components node + parametersNode *yaml.Node // components/parameters node + allParametersNode map[string]*Reference // all parameters node + allParameters map[string]*Reference // all parameters (components/defs) + schemasNode *yaml.Node // components/schemas node + allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger). + allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger). + allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger). + securitySchemesNode *yaml.Node // components/securitySchemes node + allSecuritySchemes map[string]*Reference // all security schemes / definitions. + requestBodiesNode *yaml.Node // components/requestBodies node + allRequestBodies map[string]*Reference // all request bodies + responsesNode *yaml.Node // components/responses node + allResponses map[string]*Reference // all responses + headersNode *yaml.Node // components/headers node + allHeaders map[string]*Reference // all headers + examplesNode *yaml.Node // components/examples node + allExamples map[string]*Reference // all components examples + linksNode *yaml.Node // components/links node + allLinks map[string]*Reference // all links + callbacksNode *yaml.Node // components/callbacks node + allCallbacks map[string]*Reference // all components examples + externalDocumentsNode *yaml.Node // external documents node + allExternalDocuments map[string]*Reference // all external documents + externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds + refErrors []error // errors when indexing references + operationParamErrors []error // errors when indexing parameters + allDescriptions []*DescriptionReference // every single description found in the spec. + allSummaries []*DescriptionReference // every single summary found in the spec. + allEnums []*EnumReference // every single enum found in the spec. + allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec. enumCount int descriptionCount int summaryCount int diff --git a/index/index_utils.go b/index/index_utils.go index 44dc4d3..a1c0fd0 100644 --- a/index/index_utils.go +++ b/index/index_utils.go @@ -52,13 +52,13 @@ func boostrapIndexCollections(rootNode *yaml.Node, index *SpecIndex) { index.refsByLine = make(map[string]map[int]bool) index.linesWithRefs = make(map[int]bool) index.pathRefs = make(map[string]map[string]*Reference) - index.paramOpRefs = make(map[string]map[string]map[string]*Reference) + index.paramOpRefs = make(map[string]map[string]map[string][]*Reference) index.operationTagsRefs = make(map[string]map[string][]*Reference) index.operationDescriptionRefs = make(map[string]map[string]*Reference) index.operationSummaryRefs = make(map[string]map[string]*Reference) index.paramCompRefs = make(map[string]*Reference) index.paramAllRefs = make(map[string]*Reference) - index.paramInlineDuplicates = make(map[string][]*Reference) + index.paramInlineDuplicateNames = make(map[string][]*Reference) index.globalTagRefs = make(map[string]*Reference) index.securitySchemeRefs = make(map[string]*Reference) index.requestBodiesRefs = make(map[string]*Reference) diff --git a/index/spec_index.go b/index/spec_index.go index 9657842..9bd0057 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -202,7 +202,7 @@ func (index *SpecIndex) GetMappedReferencesSequenced() []*ReferenceMapped { } // GetOperationParameterReferences will return all references to operation parameters -func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]map[string]*Reference { +func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]map[string][]*Reference { return index.paramOpRefs } @@ -310,7 +310,7 @@ func (index *SpecIndex) GetAllCallbacks() map[string]*Reference { // GetInlineOperationDuplicateParameters will return a map of duplicates located in operation parameters. func (index *SpecIndex) GetInlineOperationDuplicateParameters() map[string][]*Reference { - return index.paramInlineDuplicates + return index.paramInlineDuplicateNames } // GetReferencesWithSiblings will return a map of all the references with sibling nodes (illegal) @@ -359,7 +359,7 @@ func (index *SpecIndex) GetOperationTags() map[string]map[string][]*Reference { } // GetAllParametersFromOperations will return all paths indexed in the document -func (index *SpecIndex) GetAllParametersFromOperations() map[string]map[string]map[string]*Reference { +func (index *SpecIndex) GetAllParametersFromOperations() map[string]map[string]map[string][]*Reference { return index.paramOpRefs } @@ -1104,14 +1104,23 @@ func (index *SpecIndex) GetOperationsParameterCount() int { for mName, mValue := range params { for pName, pValue := range mValue { if !strings.HasPrefix(pName, "#") { - index.paramInlineDuplicates[pName] = append(index.paramInlineDuplicates[pName], pValue) - index.paramAllRefs[fmt.Sprintf("%s:::%s", path, mName)] = pValue + index.paramInlineDuplicateNames[pName] = append(index.paramInlineDuplicateNames[pName], pValue...) + for i := range pValue { + if pValue[i] != nil { + _, in := utils.FindKeyNodeTop("in", pValue[i].Node.Content) + if in != nil { + index.paramAllRefs[fmt.Sprintf("%s:::%s:::%s", path, mName, in.Value)] = pValue[i] + } else { + index.paramAllRefs[fmt.Sprintf("%s:::%s", path, mName)] = pValue[i] + } + } + } } } } } - index.operationParamCount = len(index.paramCompRefs) + len(index.paramInlineDuplicates) + index.operationParamCount = len(index.paramCompRefs) + len(index.paramInlineDuplicateNames) return index.operationParamCount } @@ -1120,7 +1129,7 @@ func (index *SpecIndex) GetInlineDuplicateParamCount() int { if index.componentsInlineParamDuplicateCount > 0 { return index.componentsInlineParamDuplicateCount } - dCount := len(index.paramInlineDuplicates) - index.countUniqueInlineDuplicates() + dCount := len(index.paramInlineDuplicateNames) - index.countUniqueInlineDuplicates() index.componentsInlineParamDuplicateCount = dCount return dCount } diff --git a/index/spec_index_test.go b/index/spec_index_test.go index 9585616..cf04699 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -661,7 +661,7 @@ paths: index := NewSpecIndex(&rootNode) - assert.NotNil(t, index.GetAllParametersFromOperations()["/cakes"]["post"]["coffee-time.yaml"].Node) + assert.NotNil(t, index.GetAllParametersFromOperations()["/cakes"]["post"]["coffee-time.yaml"][0].Node) } func TestSpecIndex_lookupRemoteReference_SeenSourceSimulation_BadJSON(t *testing.T) { @@ -775,18 +775,18 @@ components: if assert.Contains(t, params, "/") { if assert.Contains(t, params["/"], "top") { if assert.Contains(t, params["/"]["top"], "#/components/parameters/param1") { - assert.Equal(t, "$.components.parameters.param1", params["/"]["top"]["#/components/parameters/param1"].Path) + assert.Equal(t, "$.components.parameters.param1", params["/"]["top"]["#/components/parameters/param1"][0].Path) } if assert.Contains(t, params["/"]["top"], "paramour.yaml#/components/parameters/param3") { - assert.Equal(t, "$.components.parameters.param3", params["/"]["top"]["paramour.yaml#/components/parameters/param3"].Path) + assert.Equal(t, "$.components.parameters.param3", params["/"]["top"]["paramour.yaml#/components/parameters/param3"][0].Path) } } if assert.Contains(t, params["/"], "get") { if assert.Contains(t, params["/"]["get"], "#/components/parameters/param2") { - assert.Equal(t, "$.components.parameters.param2", params["/"]["get"]["#/components/parameters/param2"].Path) + assert.Equal(t, "$.components.parameters.param2", params["/"]["get"]["#/components/parameters/param2"][0].Path) } if assert.Contains(t, params["/"]["get"], "test") { - assert.Equal(t, "$.paths./.get.parameters[2]", params["/"]["get"]["test"].Path) + assert.Equal(t, "$.paths./.get.parameters[2]", params["/"]["get"]["test"][0].Path) } } } @@ -854,6 +854,103 @@ func TestSpecIndex_schemaComponentsHaveParentsAndPaths(t *testing.T) { } } +func TestSpecIndex_ParamsWithDuplicateNamesButUniqueInTypes(t *testing.T) { + yml := `openapi: 3.1.0 +info: + title: Test + version: 0.0.1 +servers: + - url: http://localhost:35123 +paths: + /example/{action}: + parameters: + - name: fastAction + in: path + required: true + schema: + type: string + - name: fastAction + in: query + required: true + schema: + type: string + get: + operationId: example + parameters: + - name: action + in: path + required: true + schema: + type: string + - name: action + in: query + required: true + schema: + type: string + responses: + "200": + description: OK` + + var rootNode yaml.Node + yaml.Unmarshal([]byte(yml), &rootNode) + + idx := NewSpecIndex(&rootNode) + + assert.Len(t, idx.paramAllRefs, 4) + assert.Len(t, idx.paramInlineDuplicateNames, 2) + assert.Len(t, idx.operationParamErrors, 0) + assert.Len(t, idx.refErrors, 0) + +} + +func TestSpecIndex_ParamsWithDuplicateNamesAndSameInTypes(t *testing.T) { + yml := `openapi: 3.1.0 +info: + title: Test + version: 0.0.1 +servers: + - url: http://localhost:35123 +paths: + /example/{action}: + parameters: + - name: fastAction + in: path + required: true + schema: + type: string + - name: fastAction + in: path + required: true + schema: + type: string + get: + operationId: example + parameters: + - name: action + in: path + required: true + schema: + type: string + - name: action + in: query + required: true + schema: + type: string + responses: + "200": + description: OK` + + var rootNode yaml.Node + yaml.Unmarshal([]byte(yml), &rootNode) + + idx := NewSpecIndex(&rootNode) + + assert.Len(t, idx.paramAllRefs, 3) + assert.Len(t, idx.paramInlineDuplicateNames, 2) + assert.Len(t, idx.operationParamErrors, 1) + assert.Len(t, idx.refErrors, 0) +} + func TestSpecIndex_foundObjectsWithProperties(t *testing.T) { yml := `paths: /test: diff --git a/index/utility_methods.go b/index/utility_methods.go index b22ba7c..0576495 100644 --- a/index/utility_methods.go +++ b/index/utility_methods.go @@ -261,7 +261,7 @@ func (index *SpecIndex) countUniqueInlineDuplicates() int { return index.componentsInlineParamUniqueCount } unique := 0 - for _, p := range index.paramInlineDuplicates { + for _, p := range index.paramInlineDuplicateNames { if len(p) == 1 { unique++ } @@ -279,13 +279,13 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y paramRef := index.allMappedRefs[paramRefName] if index.paramOpRefs[pathItemNode.Value] == nil { - index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string]*Reference) - index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference) + index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) } // if we know the path, but it's a new method if index.paramOpRefs[pathItemNode.Value][method] == nil { - index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) } // if this is a duplicate, add an error and ignore it @@ -302,7 +302,8 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y Path: path, }) } else { - index.paramOpRefs[pathItemNode.Value][method][paramRefName] = paramRef + index.paramOpRefs[pathItemNode.Value][method][paramRefName] = + append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef) } continue @@ -334,30 +335,47 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y Path: path, } if index.paramOpRefs[pathItemNode.Value] == nil { - index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string]*Reference) - index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference) + index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) } // if we know the path but this is a new method. if index.paramOpRefs[pathItemNode.Value][method] == nil { - index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) } - // if this is a duplicate, add an error and ignore it - if index.paramOpRefs[pathItemNode.Value][method][ref.Name] != nil { - path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i) - if method == "top" { - path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i) - } + // if this is a duplicate name, check if the `in` type is also the same, if so, it's a duplicate. + if len(index.paramOpRefs[pathItemNode.Value][method][ref.Name]) > 0 { - index.operationParamErrors = append(index.operationParamErrors, &IndexingError{ - Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+ - "index %d has a duplicate name `%s`", method, pathItemNode.Value, i, vn.Value), - Node: param, - Path: path, - }) + currentNode := ref.Node + checkNodes := index.paramOpRefs[pathItemNode.Value][method][ref.Name] + _, currentIn := utils.FindKeyNodeTop("in", currentNode.Content) + + for _, checkNode := range checkNodes { + + _, checkIn := utils.FindKeyNodeTop("in", checkNode.Node.Content) + + if currentIn != nil && checkIn != nil && currentIn.Value == checkIn.Value { + + path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i) + if method == "top" { + path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i) + } + + index.operationParamErrors = append(index.operationParamErrors, &IndexingError{ + Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+ + "index %d has a duplicate name `%s` and `in` type", method, pathItemNode.Value, i, vn.Value), + Node: param, + Path: path, + }) + } else { + index.paramOpRefs[pathItemNode.Value][method][ref.Name] = + append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref) + } + } } else { - index.paramOpRefs[pathItemNode.Value][method][ref.Name] = ref + index.paramOpRefs[pathItemNode.Value][method][ref.Name] = + append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref) } continue }