diff --git a/.golangci.yml b/.golangci.yml index 687da08..05808d5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,9 +26,11 @@ linters: min-len: 2 min-occurrences: 3 cyclop: - max-complexity: 20 + max-complexity: 25 gocyclo: - min-complexity: 20 + min-complexity: 25 + gocognit: + min-complexity: 35 exhaustive: default-signifies-exhaustive: true default-case-required: true diff --git a/analyzer.go b/analyzer.go index 4870ad0..af50a6f 100644 --- a/analyzer.go +++ b/analyzer.go @@ -164,13 +164,13 @@ func New(doc *spec.Swagger) *Spec { return a } -// SecurityRequirement is a representation of a security requirement for an operation +// SecurityRequirement is a representation of a security requirement for an operation. type SecurityRequirement struct { Name string Scopes []string } -// SecurityRequirementsFor gets the security requirements for the operation +// SecurityRequirementsFor gets the security requirements for the operation. func (s *Spec) SecurityRequirementsFor(operation *spec.Operation) [][]SecurityRequirement { if s.spec.Security == nil && operation.Security == nil { return nil @@ -204,7 +204,7 @@ func (s *Spec) SecurityRequirementsFor(operation *spec.Operation) [][]SecurityRe return result } -// SecurityDefinitionsForRequirements gets the matching security definitions for a set of requirements +// SecurityDefinitionsForRequirements gets the matching security definitions for a set of requirements. func (s *Spec) SecurityDefinitionsForRequirements(requirements []SecurityRequirement) map[string]spec.SecurityScheme { result := make(map[string]spec.SecurityScheme) @@ -219,7 +219,7 @@ func (s *Spec) SecurityDefinitionsForRequirements(requirements []SecurityRequire return result } -// SecurityDefinitionsFor gets the matching security definitions for a set of requirements +// SecurityDefinitionsFor gets the matching security definitions for a set of requirements. func (s *Spec) SecurityDefinitionsFor(operation *spec.Operation) map[string]spec.SecurityScheme { requirements := s.SecurityRequirementsFor(operation) if len(requirements) == 0 { @@ -250,7 +250,7 @@ func (s *Spec) SecurityDefinitionsFor(operation *spec.Operation) map[string]spec return result } -// ConsumesFor gets the mediatypes for the operation +// ConsumesFor gets the mediatypes for the operation. func (s *Spec) ConsumesFor(operation *spec.Operation) []string { if len(operation.Consumes) == 0 { cons := make(map[string]struct{}, len(s.spec.Consumes)) @@ -269,7 +269,7 @@ func (s *Spec) ConsumesFor(operation *spec.Operation) []string { return s.structMapKeys(cons) } -// ProducesFor gets the mediatypes for the operation +// ProducesFor gets the mediatypes for the operation. func (s *Spec) ProducesFor(operation *spec.Operation) []string { if len(operation.Produces) == 0 { prod := make(map[string]struct{}, len(s.spec.Produces)) @@ -400,7 +400,7 @@ func (s *Spec) SafeParamsFor(method, path string, callmeOnError ErrorOnParamFunc return res } -// OperationForName gets the operation for the given id +// OperationForName gets the operation for the given id. func (s *Spec) OperationForName(operationID string) (string, string, *spec.Operation, bool) { for method, pathItem := range s.operations { for path, op := range pathItem { @@ -413,7 +413,7 @@ func (s *Spec) OperationForName(operationID string) (string, string, *spec.Opera return "", "", nil, false } -// OperationFor the given method and path +// OperationFor the given method and path. func (s *Spec) OperationFor(method, path string) (*spec.Operation, bool) { if mp, ok := s.operations[strings.ToUpper(method)]; ok { op, fn := mp[path] @@ -424,12 +424,12 @@ func (s *Spec) OperationFor(method, path string) (*spec.Operation, bool) { return nil, false } -// Operations gathers all the operations specified in the spec document +// Operations gathers all the operations specified in the spec document. func (s *Spec) Operations() map[string]map[string]*spec.Operation { return s.operations } -// AllPaths returns all the paths in the swagger spec +// AllPaths returns all the paths in the swagger spec. func (s *Spec) AllPaths() map[string]spec.PathItem { if s.spec == nil || s.spec.Paths == nil { return nil @@ -438,7 +438,7 @@ func (s *Spec) AllPaths() map[string]spec.PathItem { return s.spec.Paths.Paths } -// OperationIDs gets all the operation ids based on method an dpath +// OperationIDs gets all the operation ids based on method an dpath. func (s *Spec) OperationIDs() []string { if len(s.operations) == 0 { return nil @@ -458,7 +458,7 @@ func (s *Spec) OperationIDs() []string { return result } -// OperationMethodPaths gets all the operation ids based on method an dpath +// OperationMethodPaths gets all the operation ids based on method an dpath. func (s *Spec) OperationMethodPaths() []string { if len(s.operations) == 0 { return nil @@ -474,22 +474,22 @@ func (s *Spec) OperationMethodPaths() []string { return result } -// RequiredConsumes gets all the distinct consumes that are specified in the specification document +// RequiredConsumes gets all the distinct consumes that are specified in the specification document. func (s *Spec) RequiredConsumes() []string { return s.structMapKeys(s.consumes) } -// RequiredProduces gets all the distinct produces that are specified in the specification document +// RequiredProduces gets all the distinct produces that are specified in the specification document. func (s *Spec) RequiredProduces() []string { return s.structMapKeys(s.produces) } -// RequiredSecuritySchemes gets all the distinct security schemes that are specified in the swagger spec +// RequiredSecuritySchemes gets all the distinct security schemes that are specified in the swagger spec. func (s *Spec) RequiredSecuritySchemes() []string { return s.structMapKeys(s.authSchemes) } -// SchemaRef is a reference to a schema +// SchemaRef is a reference to a schema. type SchemaRef struct { Name string Ref spec.Ref @@ -498,7 +498,7 @@ type SchemaRef struct { } // SchemasWithAllOf returns schema references to all schemas that are defined -// with an allOf key +// with an allOf key. func (s *Spec) SchemasWithAllOf() (result []SchemaRef) { for _, v := range s.allOfs { result = append(result, v) @@ -507,7 +507,7 @@ func (s *Spec) SchemasWithAllOf() (result []SchemaRef) { return } -// AllDefinitions returns schema references for all the definitions that were discovered +// AllDefinitions returns schema references for all the definitions that were discovered. func (s *Spec) AllDefinitions() (result []SchemaRef) { for _, v := range s.allSchemas { result = append(result, v) @@ -516,7 +516,7 @@ func (s *Spec) AllDefinitions() (result []SchemaRef) { return } -// AllDefinitionReferences returns json refs for all the discovered schemas +// AllDefinitionReferences returns json refs for all the discovered schemas. func (s *Spec) AllDefinitionReferences() (result []string) { for _, v := range s.references.schemas { result = append(result, v.String()) @@ -525,7 +525,7 @@ func (s *Spec) AllDefinitionReferences() (result []string) { return } -// AllParameterReferences returns json refs for all the discovered parameters +// AllParameterReferences returns json refs for all the discovered parameters. func (s *Spec) AllParameterReferences() (result []string) { for _, v := range s.references.parameters { result = append(result, v.String()) @@ -534,7 +534,7 @@ func (s *Spec) AllParameterReferences() (result []string) { return } -// AllResponseReferences returns json refs for all the discovered responses +// AllResponseReferences returns json refs for all the discovered responses. func (s *Spec) AllResponseReferences() (result []string) { for _, v := range s.references.responses { result = append(result, v.String()) @@ -543,7 +543,7 @@ func (s *Spec) AllResponseReferences() (result []string) { return } -// AllPathItemReferences returns the references for all the items +// AllPathItemReferences returns the references for all the items. func (s *Spec) AllPathItemReferences() (result []string) { for _, v := range s.references.pathItems { result = append(result, v.String()) @@ -564,7 +564,7 @@ func (s *Spec) AllItemsReferences() (result []string) { return } -// AllReferences returns all the references found in the document, with possible duplicates +// AllReferences returns all the references found in the document, with possible duplicates. func (s *Spec) AllReferences() (result []string) { for _, v := range s.references.allRefs { result = append(result, v.String()) @@ -573,7 +573,7 @@ func (s *Spec) AllReferences() (result []string) { return } -// AllRefs returns all the unique references found in the document +// AllRefs returns all the unique references found in the document. func (s *Spec) AllRefs() (result []spec.Ref) { set := make(map[string]struct{}) for _, v := range s.references.allRefs { @@ -592,61 +592,61 @@ func (s *Spec) AllRefs() (result []spec.Ref) { } // ParameterPatterns returns all the patterns found in parameters -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) ParameterPatterns() map[string]string { return cloneStringMap(s.patterns.parameters) } // HeaderPatterns returns all the patterns found in response headers -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) HeaderPatterns() map[string]string { return cloneStringMap(s.patterns.headers) } // ItemsPatterns returns all the patterns found in simple array items -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) ItemsPatterns() map[string]string { return cloneStringMap(s.patterns.items) } // SchemaPatterns returns all the patterns found in schemas -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) SchemaPatterns() map[string]string { return cloneStringMap(s.patterns.schemas) } // AllPatterns returns all the patterns found in the spec -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) AllPatterns() map[string]string { return cloneStringMap(s.patterns.allPatterns) } // ParameterEnums returns all the enums found in parameters -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) ParameterEnums() map[string][]any { return cloneEnumMap(s.enums.parameters) } // HeaderEnums returns all the enums found in response headers -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) HeaderEnums() map[string][]any { return cloneEnumMap(s.enums.headers) } // ItemsEnums returns all the enums found in simple array items -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) ItemsEnums() map[string][]any { return cloneEnumMap(s.enums.items) } // SchemaEnums returns all the enums found in schemas -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) SchemaEnums() map[string][]any { return cloneEnumMap(s.enums.schemas) } // AllEnums returns all the enums found in the spec -// the map is cloned to avoid accidental changes +// the map is cloned to avoid accidental changes. func (s *Spec) AllEnums() map[string][]any { return cloneEnumMap(s.enums.allEnums) } diff --git a/debug.go b/debug.go index d490eab..8e777c4 100644 --- a/debug.go +++ b/debug.go @@ -9,4 +9,4 @@ import ( "github.com/go-openapi/analysis/internal/debug" ) -var debugLog = debug.GetLogger("analysis", os.Getenv("SWAGGER_DEBUG") != "") +var debugLog = debug.GetLogger("analysis", os.Getenv("SWAGGER_DEBUG") != "") //nolint:gochecknoglobals // it's okay to use a private global for logging diff --git a/flatten.go b/flatten.go index 1c7a49c..e225b71 100644 --- a/flatten.go +++ b/flatten.go @@ -21,7 +21,7 @@ import ( const definitionsPath = "#/definitions" -// newRef stores information about refs created during the flattening process +// newRef stores information about refs created during the flattening process. type newRef struct { key string newName string @@ -32,7 +32,7 @@ type newRef struct { parents []string } -// context stores intermediary results from flatten +// context stores intermediary results from flatten. type context struct { newRefs map[string]*newRef warnings []string @@ -169,7 +169,7 @@ func expand(opts *FlattenOpts) error { } // normalizeRef strips the current file from any absolute file $ref. This works around issue go-openapi/spec#76: -// leading absolute file in $ref is stripped +// leading absolute file in $ref is stripped. func normalizeRef(opts *FlattenOpts) error { debugLog("normalizeRef") @@ -521,7 +521,7 @@ func stripOAIGen(opts *FlattenOpts) (bool, error) { return replacedWithComplex, nil } -// updateRefParents updates all parents of an updated $ref +// updateRefParents updates all parents of an updated $ref. func updateRefParents(allRefs map[string]spec.Ref, r *newRef) { if !r.isOAIGen || r.resolved { // bail on already resolved entries (avoid looping) return diff --git a/flatten_name.go b/flatten_name.go index 475b33c..922cae5 100644 --- a/flatten_name.go +++ b/flatten_name.go @@ -17,7 +17,7 @@ import ( "github.com/go-openapi/swag/mangling" ) -// InlineSchemaNamer finds a new name for an inlined type +// InlineSchemaNamer finds a new name for an inlined type. type InlineSchemaNamer struct { Spec *spec.Swagger Operations map[string]operations.OpRef @@ -25,7 +25,7 @@ type InlineSchemaNamer struct { opts *FlattenOpts } -// Name yields a new name for the inline schema +// Name yields a new name for the inline schema. func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *AnalyzedSchema) error { debugLog("naming inlined schema at %s", key) @@ -108,7 +108,7 @@ func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *Ana return nil } -// uniqifyName yields a unique name for a definition +// uniqifyName yields a unique name for a definition. func uniqifyName(definitions spec.Definitions, name string) (string, bool) { isOAIGen := false if name == "" { @@ -244,7 +244,7 @@ func namesForDefinition(parts sortref.SplitKey) ([][]string, int) { return [][]string{}, 0 } -// partAdder knows how to interpret a schema when it comes to build a name from parts +// partAdder knows how to interpret a schema when it comes to build a name from parts. func partAdder(aschema *AnalyzedSchema) sortref.PartAdder { return func(part string) []string { segments := make([]string, 0, minSegments) diff --git a/flatten_name_test.go b/flatten_name_test.go index f023247..592a936 100644 --- a/flatten_name_test.go +++ b/flatten_name_test.go @@ -64,8 +64,10 @@ func TestName_Definition(t *testing.T) { {"http://somewhere.com/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)}, {"#/definitions/errorModel", "errorModel", map[string]spec.Schema{"apples": *spec.StringProperty()}}, {"#/definitions/errorModel", "errorModelOAIGen", map[string]spec.Schema{"errorModel": *spec.StringProperty()}}, - {"#/definitions/errorModel", "errorModelOAIGen1", - map[string]spec.Schema{"errorModel": *spec.StringProperty(), "errorModelOAIGen": *spec.StringProperty()}}, + { + "#/definitions/errorModel", "errorModelOAIGen1", + map[string]spec.Schema{"errorModel": *spec.StringProperty(), "errorModelOAIGen": *spec.StringProperty()}, + }, {"#", "oaiGen", nil}, } @@ -244,8 +246,10 @@ func TestName_NamesFromKey(t *testing.T) { Key string Names []string }{ - {"#/paths/~1some~1where~1{id}/parameters/1/schema", - []string{"GetSomeWhereID params body", "PostSomeWhereID params body"}}, + { + "#/paths/~1some~1where~1{id}/parameters/1/schema", + []string{"GetSomeWhereID params body", "PostSomeWhereID params body"}, + }, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", []string{"GetSomeWhereID params body"}}, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", []string{"GetSomeWhereID Default body"}}, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", []string{"GetSomeWhereID OK body"}}, @@ -290,8 +294,10 @@ func TestName_BuildNameWithReservedKeyWord(t *testing.T) { newName := s.BuildName(segments, startIdx, partAdder(nil)) assert.Equal(t, "fullview properties", newName) - s = sortref.SplitKey([]string{"definitions", "fullview", - "properties", "properties", "properties", "properties", "properties", "properties"}) + s = sortref.SplitKey([]string{ + "definitions", "fullview", + "properties", "properties", "properties", "properties", "properties", "properties", + }) newName = s.BuildName(segments, startIdx, partAdder(nil)) assert.Equal(t, "fullview"+strings.Repeat(" properties", 3), newName) } @@ -302,66 +308,79 @@ func TestName_InlinedSchemas(t *testing.T) { Location string Ref spec.Ref }{ - {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2/properties/name", + { + "#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"), }, - {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/1", + { + "#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/1", "#/definitions/getSomeWhereIdParamsBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"), }, - {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2", + { + "#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2", "#/definitions/getSomeWhereIdParamsBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"), }, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2/properties/name", + { + "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdOKBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2Name"), }, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/1", + { + "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/1", "#/definitions/getSomeWhereIdOKBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems1"), }, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2", + { + "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2", "#/definitions/getSomeWhereIdOKBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2"), }, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record", + { + "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record", "#/definitions/getSomeWhereIdOKBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecord"), }, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema", + { + "#/paths/~1some~1where~1{id}/get/responses/200/schema", "#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"), }, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2/properties/name", + { + "#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdDefaultBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2Name"), }, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/1", + { + "#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/1", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems1"), }, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2", + { + "#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2"), }, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record", + { + "#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record", "#/definitions/getSomeWhereIdDefaultBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecord"), }, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema", + { + "#/paths/~1some~1where~1{id}/get/responses/default/schema", "#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBody"), }, @@ -375,57 +394,68 @@ func TestName_InlinedSchemas(t *testing.T) { // "#/definitions/nestedThingRecordItems2/allOf/1", // spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1"), // }, - {"#/definitions/nestedThing/properties/record/items/2/properties/name", + { + "#/definitions/nestedThing/properties/record/items/2/properties/name", "#/definitions/nestedThingRecordItems2/properties/name", spec.MustCreateRef("#/definitions/nestedThingRecordItems2Name"), }, - {"#/definitions/nestedThing/properties/record/items/1", + { + "#/definitions/nestedThing/properties/record/items/1", "#/definitions/nestedThingRecord/items/1", spec.MustCreateRef("#/definitions/nestedThingRecordItems1"), }, - {"#/definitions/nestedThing/properties/record/items/2", + { + "#/definitions/nestedThing/properties/record/items/2", "#/definitions/nestedThingRecord/items/2", spec.MustCreateRef("#/definitions/nestedThingRecordItems2"), }, - {"#/definitions/datedRecords/items/1", + { + "#/definitions/datedRecords/items/1", "#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/datedRecordsItems1"), }, - {"#/definitions/datedTaggedRecords/items/1", + { + "#/definitions/datedTaggedRecords/items/1", "#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/datedTaggedRecordsItems1"), }, - {"#/definitions/namedThing/properties/name", + { + "#/definitions/namedThing/properties/name", "#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/namedThingName"), }, - {"#/definitions/nestedThing/properties/record", + { + "#/definitions/nestedThing/properties/record", "#/definitions/nestedThing/properties/record", spec.MustCreateRef("#/definitions/nestedThingRecord"), }, - {"#/definitions/records/items/0", + { + "#/definitions/records/items/0", "#/definitions/records/items/0", spec.MustCreateRef("#/definitions/recordsItems0"), }, - {"#/definitions/datedTaggedRecords/additionalItems", + { + "#/definitions/datedTaggedRecords/additionalItems", "#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/datedTaggedRecordsItemsAdditionalItems"), }, - {"#/definitions/otherRecords/items", + { + "#/definitions/otherRecords/items", "#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/otherRecordsItems"), }, - {"#/definitions/tags/additionalProperties", + { + "#/definitions/tags/additionalProperties", "#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tagsAdditionalProperties"), }, diff --git a/flatten_options.go b/flatten_options.go index d8fc25c..a9e54a8 100644 --- a/flatten_options.go +++ b/flatten_options.go @@ -44,13 +44,13 @@ func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *spec.ExpandOptions { } } -// Swagger gets the swagger specification for this flatten operation +// Swagger gets the swagger specification for this flatten operation. func (f *FlattenOpts) Swagger() *spec.Swagger { return f.Spec.spec } // croak logs notifications and warnings about valid, but possibly unwanted constructs resulting -// from flattening a spec +// from flattening a spec. func (f *FlattenOpts) croak() { if !f.Verbose { return diff --git a/flatten_test.go b/flatten_test.go index 8ddac1b..cb10b95 100644 --- a/flatten_test.go +++ b/flatten_test.go @@ -915,7 +915,7 @@ func TestFlatten_Pointers(t *testing.T) { } } -// unit test guards in flatten not easily testable with actual specs +// unit test guards in flatten not easily testable with actual specs. func TestFlatten_ErrorHandling(t *testing.T) { log.SetOutput(io.Discard) defer log.SetOutput(os.Stdout) @@ -1057,8 +1057,10 @@ func TestFlatten_Issue_1614(t *testing.T) { bp := filepath.Join("fixtures", "bugs", "1614", "gitea.yaml") sp := antest.LoadOrFail(t, bp) an := New(sp) - require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, - RemoveUnused: false})) + require.NoError(t, Flatten(FlattenOpts{ + Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, + RemoveUnused: false, + })) // check that responses subject to warning have been expanded jazon := antest.AsJSON(t, sp) diff --git a/internal/antest/helpers.go b/internal/antest/helpers.go index 658006d..0b55da0 100644 --- a/internal/antest/helpers.go +++ b/internal/antest/helpers.go @@ -17,17 +17,17 @@ import ( ) var ( - oncePathLoader sync.Once - enableLongTests bool + oncePathLoader sync.Once //nolint:gochecknoglobals // it's okay to use sync.Once as a global + enableLongTests bool //nolint:gochecknoglobals // it's okay to store test flags as a global ) -func init() { +func init() { //nolint:gochecknoinits // it's okay to call init() to register go test extra flags if flag.Lookup("enable-long") == nil { flag.BoolVar(&enableLongTests, "enable-long", false, "enable long runnning tests") } } -// LongTestsEnabled returns the CLI flag +// LongTestsEnabled returns the CLI flag. func LongTestsEnabled() bool { return enableLongTests } @@ -48,7 +48,7 @@ func initPathLoader() { } } -// LoadSpec loads a json or a yaml spec +// LoadSpec loads a json or a yaml spec. func LoadSpec(path string) (*spec.Swagger, error) { oncePathLoader.Do(initPathLoader) @@ -65,7 +65,7 @@ func LoadSpec(path string) (*spec.Swagger, error) { return &sw, nil } -// LoadOrFail fetches a spec from a relative path or dies if the spec cannot be loaded properly +// LoadOrFail fetches a spec from a relative path or dies if the spec cannot be loaded properly. func LoadOrFail(t testing.TB, relative string) *spec.Swagger { cwd, _ := os.Getwd() sp, err := LoadSpec(filepath.Join(cwd, relative)) @@ -74,7 +74,7 @@ func LoadOrFail(t testing.TB, relative string) *spec.Swagger { return sp } -// AsJSON unmarshals anything as JSON or dies +// AsJSON unmarshals anything as JSON or dies. func AsJSON(t testing.TB, in any) string { bbb, err := json.MarshalIndent(in, "", " ") require.NoError(t, err) diff --git a/internal/antest/helpers_test.go b/internal/antest/helpers_test.go index 5452212..7358785 100644 --- a/internal/antest/helpers_test.go +++ b/internal/antest/helpers_test.go @@ -101,7 +101,7 @@ info: } require.NoError(t, - os.WriteFile(file, data, 0600), + os.WriteFile(file, data, 0o600), ) return file, func() {} diff --git a/internal/flatten/normalize/normalize.go b/internal/flatten/normalize/normalize.go index 320a50b..c04b6b4 100644 --- a/internal/flatten/normalize/normalize.go +++ b/internal/flatten/normalize/normalize.go @@ -18,7 +18,7 @@ import ( // // NOTE(windows): // * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec) -// * "/ in paths may appear as escape sequences +// * "/ in paths may appear as escape sequences. func RebaseRef(baseRef string, ref string) string { baseRef, _ = url.PathUnescape(baseRef) ref, _ = url.PathUnescape(ref) @@ -70,7 +70,7 @@ func RebaseRef(baseRef string, ref string) string { // // NOTE(windows): // * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec) -// * "/ in paths may appear as escape sequences +// * "/ in paths may appear as escape sequences. func Path(ref spec.Ref, basePath string) string { uri, _ := url.PathUnescape(ref.String()) if ref.HasFragmentOnly || filepath.IsAbs(uri) { diff --git a/internal/flatten/normalize/normalize_test.go b/internal/flatten/normalize/normalize_test.go index d13877d..eb5b7c0 100644 --- a/internal/flatten/normalize/normalize_test.go +++ b/internal/flatten/normalize/normalize_test.go @@ -46,10 +46,22 @@ func TestNormalize_RebaseRef(t *testing.T) { assert.Equal(t, definitionABC, RebaseRef("", definitionABC)) assert.Equal(t, definitionABC, RebaseRef(".", definitionABC)) assert.Equal(t, "otherfile"+definitionABC, RebaseRef("file"+definitionBase, "otherfile"+definitionABC)) - assert.Equal(t, wrapWindowsPath("../otherfile")+definitionABC, RebaseRef(wrapWindowsPath("../file")+definitionBase, wrapWindowsPath("./otherfile")+definitionABC)) - assert.Equal(t, wrapWindowsPath("../otherfile")+definitionABC, RebaseRef(wrapWindowsPath("../file")+definitionBase, wrapWindowsPath("otherfile")+definitionABC)) - assert.Equal(t, wrapWindowsPath("local/remote/otherfile")+definitionABC, RebaseRef(wrapWindowsPath("local/file")+definitionBase, wrapWindowsPath("remote/otherfile")+definitionABC)) - assert.Equal(t, wrapWindowsPath("local/remote/otherfile.yaml"), RebaseRef(wrapWindowsPath("local/file.yaml"), wrapWindowsPath("remote/otherfile.yaml"))) + assert.Equal(t, + wrapWindowsPath("../otherfile")+definitionABC, + RebaseRef(wrapWindowsPath("../file")+definitionBase, wrapWindowsPath("./otherfile")+definitionABC), + ) + assert.Equal(t, + wrapWindowsPath("../otherfile")+definitionABC, + RebaseRef(wrapWindowsPath("../file")+definitionBase, wrapWindowsPath("otherfile")+definitionABC), + ) + assert.Equal(t, + wrapWindowsPath("local/remote/otherfile")+definitionABC, + RebaseRef(wrapWindowsPath("local/file")+definitionBase, wrapWindowsPath("remote/otherfile")+definitionABC), + ) + assert.Equal(t, + wrapWindowsPath("local/remote/otherfile.yaml"), + RebaseRef(wrapWindowsPath("local/file.yaml"), wrapWindowsPath("remote/otherfile.yaml")), + ) assert.Equal(t, "file#/definitions/abc", RebaseRef("file#/definitions/base", definitionABC)) @@ -62,7 +74,7 @@ func TestNormalize_RebaseRef(t *testing.T) { assert.Equal(t, "https://example.com/dir/definitions/abc", RebaseRef(exampleBase, "dir/definitions/abc")) } -// wrapWindowsPath adapts path expectations for tests running on windows +// wrapWindowsPath adapts path expectations for tests running on windows. func wrapWindowsPath(p string) string { if runtime.GOOS != "windows" { return p diff --git a/internal/flatten/operations/operations.go b/internal/flatten/operations/operations.go index 940c46a..325e275 100644 --- a/internal/flatten/operations/operations.go +++ b/internal/flatten/operations/operations.go @@ -14,12 +14,12 @@ import ( "github.com/go-openapi/swag/mangling" ) -// AllOpRefsByRef returns an index of sortable operations +// AllOpRefsByRef returns an index of sortable operations. func AllOpRefsByRef(specDoc Provider, operationIDs []string) map[string]OpRef { return OpRefsByRef(GatherOperations(specDoc, operationIDs)) } -// OpRefsByRef indexes a map of sortable operations +// OpRefsByRef indexes a map of sortable operations. func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef { result := make(map[string]OpRef, len(oprefs)) for _, v := range oprefs { @@ -29,7 +29,7 @@ func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef { return result } -// OpRef is an indexable, sortable operation +// OpRef is an indexable, sortable operation. type OpRef struct { Method string Path string @@ -39,19 +39,19 @@ type OpRef struct { Ref spec.Ref } -// OpRefs is a sortable collection of operations +// OpRefs is a sortable collection of operations. type OpRefs []OpRef func (o OpRefs) Len() int { return len(o) } func (o OpRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o OpRefs) Less(i, j int) bool { return o[i].Key < o[j].Key } -// Provider knows how to collect operations from a spec +// Provider knows how to collect operations from a spec. type Provider interface { Operations() map[string]map[string]*spec.Operation } -// GatherOperations builds a map of sorted operations from a spec +// GatherOperations builds a map of sorted operations from a spec. func GatherOperations(specDoc Provider, operationIDs []string) map[string]OpRef { var oprefs OpRefs mangler := mangling.NewNameMangler() diff --git a/internal/flatten/replace/errors.go b/internal/flatten/replace/errors.go index d7c28b8..b2a8a93 100644 --- a/internal/flatten/replace/errors.go +++ b/internal/flatten/replace/errors.go @@ -58,7 +58,7 @@ func ErrCyclicChain(key string) error { } func ErrInvalidPointerType(key string, value any, err error) error { - return fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v): %w", + return fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%w): %w", key, value, err, ErrReplace, ) } diff --git a/internal/flatten/replace/replace.go b/internal/flatten/replace/replace.go index 61c13f7..e8365f3 100644 --- a/internal/flatten/replace/replace.go +++ b/internal/flatten/replace/replace.go @@ -22,9 +22,10 @@ const ( allocMediumMap = 64 ) +//nolint:gochecknoglobals // it's okay to use a private global for logging var debugLog = debug.GetLogger("analysis/flatten/replace", os.Getenv("SWAGGER_DEBUG") != "") -// RewriteSchemaToRef replaces a schema with a Ref +// RewriteSchemaToRef replaces a schema with a Ref. func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error { debugLog("rewriting schema to ref for %s with %s", key, ref.String()) _, value, err := getPointerFromKey(sp, key) @@ -142,7 +143,7 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error { return nil } -// getPointerFromKey retrieves the content of the JSON pointer "key" +// getPointerFromKey retrieves the content of the JSON pointer "key". func getPointerFromKey(sp any, key string) (string, any, error) { switch sp.(type) { case *spec.Schema: @@ -154,7 +155,10 @@ func getPointerFromKey(sp any, key string) (string, any, error) { return "", sp, nil } // unescape chars in key, e.g. "{}" from path params - pth, _ := url.PathUnescape(key[1:]) + pth, err := url.PathUnescape(key[1:]) + if err != nil { + return "", nil, errors.Join(err, ErrReplace) + } ptr, err := jsonpointer.New(pth) if err != nil { return "", nil, errors.Join(err, ErrReplace) @@ -170,7 +174,7 @@ func getPointerFromKey(sp any, key string) (string, any, error) { return pth, value, nil } -// getParentFromKey retrieves the container of the JSON pointer "key" +// getParentFromKey retrieves the container of the JSON pointer "key". func getParentFromKey(sp any, key string) (string, string, any, error) { switch sp.(type) { case *spec.Schema: @@ -196,7 +200,7 @@ func getParentFromKey(sp any, key string) (string, string, any, error) { return parent, entry, pvalue, nil } -// UpdateRef replaces a ref by another one +// UpdateRef replaces a ref by another one. func UpdateRef(sp any, key string, ref spec.Ref) error { switch sp.(type) { case *spec.Schema: @@ -265,7 +269,7 @@ func UpdateRef(sp any, key string, ref spec.Ref) error { return nil } -// UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema) +// UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema). func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error { debugLog("updating ref for %s with schema", key) pth, value, err := getPointerFromKey(sp, key) @@ -324,7 +328,7 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error { return nil } -// DeepestRefResult holds the results from DeepestRef analysis +// DeepestRefResult holds the results from DeepestRef analysis. type DeepestRefResult struct { Ref spec.Ref Schema *spec.Schema @@ -336,6 +340,8 @@ type DeepestRefResult struct { // - pointers to external files are expanded // // NOTE: all external $ref's are assumed to be already expanded at this stage. +// +//nolint:gocognit,cyclop,gocyclo // this is the most complex method in this package and we'll have to break it down some day func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) { if !ref.HasFragmentOnly { // we found an external $ref, which is odd at this stage: @@ -392,11 +398,13 @@ DOWNREF: case spec.Response: // a pointer points to a schema initially marshalled in responses section... // Attempt to convert this to a schema. If this fails, the spec is invalid - asJSON, _ := refable.MarshalJSON() + asJSON, err := refable.MarshalJSON() + if err != nil { + return nil, ErrInvalidPointerType(currentRef.String(), value, err) + } var asSchema spec.Schema - err := asSchema.UnmarshalJSON(asJSON) - if err != nil { + if err = asSchema.UnmarshalJSON(asJSON); err != nil { return nil, ErrInvalidPointerType(currentRef.String(), value, err) } warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String())) @@ -409,9 +417,12 @@ DOWNREF: case spec.Parameter: // a pointer points to a schema initially marshalled in parameters section... // Attempt to convert this to a schema. If this fails, the spec is invalid - asJSON, _ := refable.MarshalJSON() + asJSON, err := refable.MarshalJSON() + if err != nil { + return nil, ErrInvalidPointerType(currentRef.String(), value, err) + } var asSchema spec.Schema - if err := asSchema.UnmarshalJSON(asJSON); err != nil { + if err = asSchema.UnmarshalJSON(asJSON); err != nil { return nil, ErrInvalidPointerType(currentRef.String(), value, err) } @@ -428,9 +439,12 @@ DOWNREF: break DOWNREF } - asJSON, _ := json.Marshal(refable) + asJSON, err := json.Marshal(refable) + if err != nil { + return nil, ErrInvalidPointerType(currentRef.String(), value, err) + } var asSchema spec.Schema - if err := asSchema.UnmarshalJSON(asJSON); err != nil { + if err = asSchema.UnmarshalJSON(asJSON); err != nil { return nil, ErrInvalidPointerType(currentRef.String(), value, err) } warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable)) diff --git a/internal/flatten/replace/replace_test.go b/internal/flatten/replace/replace_test.go index dfcd92d..3afa6c0 100644 --- a/internal/flatten/replace/replace_test.go +++ b/internal/flatten/replace/replace_test.go @@ -14,33 +14,13 @@ import ( "github.com/go-openapi/testify/v2/require" ) -var refFixtures = []struct { - Key string - Ref spec.Ref -}{ - {"#/parameters/someParam/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/paths/~1some~1where~1{id}/parameters/1/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/responses/someResponse/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/record")}, - {"#/definitions/namedAgain", spec.MustCreateRef("#/definitions/named")}, - {"#/definitions/datedTag/allOf/1", spec.MustCreateRef("#/definitions/tag")}, - {"#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, - {"#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, - {"#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/tag")}, - {"#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/record")}, - {"#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tag")}, - {"#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/named")}, -} - func TestUpdateRef(t *testing.T) { t.Parallel() bp := filepath.Join("..", "..", "..", "fixtures", "external_definitions.yml") sp := antest.LoadOrFail(t, bp) - for _, v := range refFixtures { + for _, v := range refFixtures() { err := UpdateRef(sp, v.Key, v.Ref) require.NoError(t, err) @@ -71,7 +51,7 @@ func TestRewriteSchemaRef(t *testing.T) { bp := filepath.Join("..", "..", "..", "fixtures", "inline_schemas.yml") sp := antest.LoadOrFail(t, bp) - for i, v := range refFixtures { + for i, v := range refFixtures() { err := RewriteSchemaToRef(sp, v.Key, v.Ref) require.NoError(t, err) @@ -157,3 +137,27 @@ func TestReplace_ErrorHandling(t *testing.T) { }) }) } + +type refFixture struct { + Key string + Ref spec.Ref +} + +func refFixtures() []refFixture { + return []refFixture{ + {"#/parameters/someParam/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/paths/~1some~1where~1{id}/parameters/1/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/responses/someResponse/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/record")}, + {"#/definitions/namedAgain", spec.MustCreateRef("#/definitions/named")}, + {"#/definitions/datedTag/allOf/1", spec.MustCreateRef("#/definitions/tag")}, + {"#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, + {"#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, + {"#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/tag")}, + {"#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/record")}, + {"#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tag")}, + {"#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/named")}, + } +} diff --git a/internal/flatten/schutils/flatten_schema.go b/internal/flatten/schutils/flatten_schema.go index 7e9fb9f..59855ef 100644 --- a/internal/flatten/schutils/flatten_schema.go +++ b/internal/flatten/schutils/flatten_schema.go @@ -12,7 +12,7 @@ import ( const allocLargeMap = 150 -// Save registers a schema as an entry in spec #/definitions +// Save registers a schema as an entry in spec #/definitions. func Save(sp *spec.Swagger, name string, schema *spec.Schema) { if schema == nil { return @@ -25,7 +25,7 @@ func Save(sp *spec.Swagger, name string, schema *spec.Schema) { sp.Definitions[name] = *schema } -// Clone deep-clones a schema +// Clone deep-clones a schema. func Clone(schema *spec.Schema) *spec.Schema { var sch spec.Schema _ = jsonutils.FromDynamicJSON(schema, &sch) diff --git a/internal/flatten/sortref/keys.go b/internal/flatten/sortref/keys.go index a5db024..7ab8e4e 100644 --- a/internal/flatten/sortref/keys.go +++ b/internal/flatten/sortref/keys.go @@ -20,12 +20,8 @@ const ( definitions = "definitions" ) +//nolint:gochecknoglobals // it's okay to store small indexes like this as private globals var ( - ignoredKeys map[string]struct{} - validMethods map[string]struct{} -) - -func init() { ignoredKeys = map[string]struct{}{ "schema": {}, "properties": {}, @@ -43,15 +39,15 @@ func init() { "PUT": {}, "DELETE": {}, } -} +) -// Key represent a key item constructed from /-separated segments +// Key represent a key item constructed from /-separated segments. type Key struct { Segments int Key string } -// Keys is a sortable collable collection of Keys +// Keys is a sortable collable collection of Keys. type Keys []Key func (k Keys) Len() int { return len(k) } @@ -75,12 +71,12 @@ func KeyParts(key string) SplitKey { // SplitKey holds of the parts of a /-separated key, so that their location may be determined. type SplitKey []string -// IsDefinition is true when the split key is in the #/definitions section of a spec +// IsDefinition is true when the split key is in the #/definitions section of a spec. func (s SplitKey) IsDefinition() bool { return len(s) > 1 && s[0] == definitions } -// DefinitionName yields the name of the definition +// DefinitionName yields the name of the definition. func (s SplitKey) DefinitionName() string { if !s.IsDefinition() { return "" @@ -89,10 +85,10 @@ func (s SplitKey) DefinitionName() string { return s[1] } -// PartAdder know how to construct the components of a new name +// PartAdder know how to construct the components of a new name. type PartAdder func(string) []string -// BuildName builds a name from segments +// BuildName builds a name from segments. func (s SplitKey) BuildName(segments []string, startIndex int, adder PartAdder) string { for i, part := range s[startIndex:] { if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) { @@ -103,42 +99,42 @@ func (s SplitKey) BuildName(segments []string, startIndex int, adder PartAdder) return strings.Join(segments, " ") } -// IsOperation is true when the split key is in the operations section +// IsOperation is true when the split key is in the operations section. func (s SplitKey) IsOperation() bool { return len(s) > 1 && s[0] == paths } -// IsSharedOperationParam is true when the split key is in the parameters section of a path +// IsSharedOperationParam is true when the split key is in the parameters section of a path. func (s SplitKey) IsSharedOperationParam() bool { return len(s) > 2 && s[0] == paths && s[2] == parameters } -// IsSharedParam is true when the split key is in the #/parameters section of a spec +// IsSharedParam is true when the split key is in the #/parameters section of a spec. func (s SplitKey) IsSharedParam() bool { return len(s) > 1 && s[0] == parameters } -// IsOperationParam is true when the split key is in the parameters section of an operation +// IsOperationParam is true when the split key is in the parameters section of an operation. func (s SplitKey) IsOperationParam() bool { return len(s) > 3 && s[0] == paths && s[3] == parameters } -// IsOperationResponse is true when the split key is in the responses section of an operation +// IsOperationResponse is true when the split key is in the responses section of an operation. func (s SplitKey) IsOperationResponse() bool { return len(s) > 3 && s[0] == paths && s[3] == responses } -// IsSharedResponse is true when the split key is in the #/responses section of a spec +// IsSharedResponse is true when the split key is in the #/responses section of a spec. func (s SplitKey) IsSharedResponse() bool { return len(s) > 1 && s[0] == responses } -// IsDefaultResponse is true when the split key is the default response for an operation +// IsDefaultResponse is true when the split key is the default response for an operation. func (s SplitKey) IsDefaultResponse() bool { return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default" } -// IsStatusCodeResponse is true when the split key is an operation response with a status code +// IsStatusCodeResponse is true when the split key is an operation response with a status code. func (s SplitKey) IsStatusCodeResponse() bool { isInt := func() bool { _, err := strconv.Atoi(s[4]) @@ -149,7 +145,7 @@ func (s SplitKey) IsStatusCodeResponse() bool { return len(s) > 4 && s[0] == paths && s[3] == responses && isInt() } -// ResponseName yields either the status code or "Default" for a response +// ResponseName yields either the status code or "Default" for a response. func (s SplitKey) ResponseName() string { if s.IsStatusCodeResponse() { code, _ := strconv.Atoi(s[4]) @@ -164,7 +160,7 @@ func (s SplitKey) ResponseName() string { return "" } -// PathItemRef constructs a $ref object from a split key of the form /{path}/{method} +// PathItemRef constructs a $ref object from a split key of the form /{path}/{method}. func (s SplitKey) PathItemRef() spec.Ref { const minValidPathItems = 3 if len(s) < minValidPathItems { @@ -179,7 +175,7 @@ func (s SplitKey) PathItemRef() spec.Ref { return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method))) } -// PathRef constructs a $ref object from a split key of the form /paths/{reference} +// PathRef constructs a $ref object from a split key of the form /paths/{reference}. func (s SplitKey) PathRef() spec.Ref { if !s.IsOperation() { return spec.Ref{} diff --git a/internal/flatten/sortref/sort_ref.go b/internal/flatten/sortref/sort_ref.go index ceac713..e4ad07b 100644 --- a/internal/flatten/sortref/sort_ref.go +++ b/internal/flatten/sortref/sort_ref.go @@ -4,7 +4,9 @@ package sortref import ( + "iter" "reflect" + "slices" "sort" "strings" @@ -12,10 +14,6 @@ import ( "github.com/go-openapi/spec" ) -var depthGroupOrder = []string{ - "sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition", -} - type mapIterator struct { len int mapIter *reflect.MapIter @@ -42,7 +40,7 @@ func mustMapIterator(anyMap any) *mapIterator { // DepthFirst sorts a map of anything. It groups keys by category // (shared params, op param, statuscode response, default response, definitions) // sort groups internally by number of parts in the key and lexical names -// flatten groups into a single list of keys +// flatten groups into a single list of keys. func DepthFirst(in any) []string { iterator := mustMapIterator(in) sorted := make([]string, 0, iterator.Len()) @@ -77,7 +75,7 @@ func DepthFirst(in any) []string { grouped[pk] = append(grouped[pk], Key{Segments: len(split), Key: k}) } - for _, pk := range depthGroupOrder { + for pk := range depthGroupOrder() { res := grouped[pk] sort.Sort(res) @@ -89,6 +87,12 @@ func DepthFirst(in any) []string { return sorted } +func depthGroupOrder() iter.Seq[string] { + return slices.Values([]string{ + "sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition", + }) +} + // topMostRefs is able to sort refs by hierarchical then lexicographic order, // yielding refs ordered breadth-first. type topmostRefs []string @@ -104,7 +108,7 @@ func (k topmostRefs) Less(i, j int) bool { return li < lj } -// TopmostFirst sorts references by depth +// TopmostFirst sorts references by depth. func TopmostFirst(refs []string) []string { res := topmostRefs(refs) sort.Sort(res) @@ -112,13 +116,13 @@ func TopmostFirst(refs []string) []string { return res } -// RefRevIdx is a reverse index for references +// RefRevIdx is a reverse index for references. type RefRevIdx struct { Ref spec.Ref Keys []string } -// ReverseIndex builds a reverse index for references in schemas +// ReverseIndex builds a reverse index for references in schemas. func ReverseIndex(schemas map[string]spec.Ref, basePath string) map[string]RefRevIdx { collected := make(map[string]RefRevIdx) for key, schRef := range schemas { diff --git a/schema.go b/schema.go index 039dac1..bedea65 100644 --- a/schema.go +++ b/schema.go @@ -8,7 +8,7 @@ import ( "github.com/go-openapi/strfmt" ) -// SchemaOpts configures the schema analyzer +// SchemaOpts configures the schema analyzer. type SchemaOpts struct { Schema *spec.Schema Root any @@ -52,7 +52,7 @@ func Schema(opts SchemaOpts) (*AnalyzedSchema, error) { return a, nil } -// AnalyzedSchema indicates what the schema represents +// AnalyzedSchema indicates what the schema represents. type AnalyzedSchema struct { schema *spec.Schema root any @@ -78,7 +78,7 @@ type AnalyzedSchema struct { IsEnum bool } -// Inherits copies value fields from other onto this schema +// Inherits copies value fields from other onto this schema. func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) { if other == nil { return diff --git a/schema_test.go b/schema_test.go index 70bdc0a..097b308 100644 --- a/schema_test.go +++ b/schema_test.go @@ -18,31 +18,14 @@ import ( "github.com/go-openapi/testify/v2/require" ) -var knownSchemas = []*spec.Schema{ - spec.BoolProperty(), // 0 - spec.StringProperty(), // 1 - spec.Int8Property(), // 2 - spec.Int16Property(), // 3 - spec.Int32Property(), // 4 - spec.Int64Property(), // 5 - spec.Float32Property(), // 6 - spec.Float64Property(), // 7 - spec.DateProperty(), // 8 - spec.DateTimeProperty(), // 9 - (&spec.Schema{}), // 10 - (&spec.Schema{}).Typed("object", ""), // 11 - (&spec.Schema{}).Typed("", ""), // 12 - (&spec.Schema{}).Typed("", "uuid"), // 13 -} - func TestSchemaAnalysis_KnownTypes(t *testing.T) { - for i, v := range knownSchemas { + for i, v := range knownSchemas() { sch, err := Schema(SchemaOpts{Schema: v}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsKnownType, "item at %d should be a known type", i) } - for i, v := range complexSchemas { + for i, v := range complexSchemas() { sch, err := Schema(SchemaOpts{Schema: v}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Falsef(t, sch.IsKnownType, "item at %d should not be a known type", i) @@ -65,14 +48,14 @@ func TestSchemaAnalysis_KnownTypes(t *testing.T) { } func TestSchemaAnalysis_Array(t *testing.T) { - for i, v := range append(knownSchemas, (&spec.Schema{}).Typed("array", "")) { + for i, v := range append(knownSchemas(), (&spec.Schema{}).Typed("array", "")) { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsArray, "item at %d should be an array type", i) assert.Truef(t, sch.IsSimpleArray, "item at %d should be a simple array type", i) } - for i, v := range complexSchemas { + for i, v := range complexSchemas() { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsArray, "item at %d should be an array type", i) @@ -119,14 +102,14 @@ func TestSchemaAnalysis_Array(t *testing.T) { } func TestSchemaAnalysis_Map(t *testing.T) { - for i, v := range append(knownSchemas, spec.MapProperty(nil)) { + for i, v := range append(knownSchemas(), spec.MapProperty(nil)) { sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsMap, "item at %d should be a map type", i) assert.Truef(t, sch.IsSimpleMap, "item at %d should be a simple map type", i) } - for i, v := range complexSchemas { + for i, v := range complexSchemas() { sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsMap, "item at %d should be a map type", i) @@ -135,7 +118,7 @@ func TestSchemaAnalysis_Map(t *testing.T) { } func TestSchemaAnalysis_ExtendedObject(t *testing.T) { - for i, v := range knownSchemas { + for i, v := range knownSchemas() { wex := spec.MapProperty(v).SetProperty("name", *spec.StringProperty()) sch, err := Schema(SchemaOpts{Schema: wex}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) @@ -194,7 +177,7 @@ func TestSchemaAnalysis_BaseType(t *testing.T) { } func TestSchemaAnalysis_SimpleSchema(t *testing.T) { - for i, v := range append(knownSchemas, spec.ArrayProperty(nil), spec.MapProperty(nil)) { + for i, v := range append(knownSchemas(), spec.ArrayProperty(nil), spec.MapProperty(nil)) { sch, err := Schema(SchemaOpts{Schema: v}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Truef(t, sch.IsSimpleSchema, "item at %d should be a simple schema", i) @@ -208,7 +191,7 @@ func TestSchemaAnalysis_SimpleSchema(t *testing.T) { assert.Truef(t, msch.IsSimpleSchema, "map item at %d should be a simple schema", i) } - for i, v := range complexSchemas { + for i, v := range complexSchemas() { sch, err := Schema(SchemaOpts{Schema: v}) require.NoErrorf(t, err, "failed to analyze schema at %d: %v", i, err) assert.Falsef(t, sch.IsSimpleSchema, "item at %d should not be a simple schema", i) @@ -261,12 +244,35 @@ func newCObj() *spec.Schema { return (&spec.Schema{}).Typed("object", "").SetProperty("id", *spec.Int64Property()) } -var complexObject = newCObj() +func complexObject() *spec.Schema { + return newCObj() +} -var complexSchemas = []*spec.Schema{ - complexObject, - spec.ArrayProperty(complexObject), - spec.MapProperty(complexObject), +func complexSchemas() []*spec.Schema { + return []*spec.Schema{ + complexObject(), + spec.ArrayProperty(complexObject()), + spec.MapProperty(complexObject()), + } +} + +func knownSchemas() []*spec.Schema { + return []*spec.Schema{ + spec.BoolProperty(), // 0 + spec.StringProperty(), // 1 + spec.Int8Property(), // 2 + spec.Int16Property(), // 3 + spec.Int32Property(), // 4 + spec.Int64Property(), // 5 + spec.Float32Property(), // 6 + spec.Float64Property(), // 7 + spec.DateProperty(), // 8 + spec.DateTimeProperty(), // 9 + (&spec.Schema{}), // 10 + (&spec.Schema{}).Typed("object", ""), // 11 + (&spec.Schema{}).Typed("", ""), // 12 + (&spec.Schema{}).Typed("", "uuid"), // 13 + } } func knownRefs(base string) []spec.Ref { @@ -293,17 +299,17 @@ func complexRefs(base string) []spec.Ref { func refServer() *httptest.Server { mux := http.NewServeMux() - mux.Handle("/known/bool", schemaHandler(knownSchemas[0])) - mux.Handle("/known/string", schemaHandler(knownSchemas[1])) - mux.Handle("/known/integer", schemaHandler(knownSchemas[5])) - mux.Handle("/known/float", schemaHandler(knownSchemas[6])) - mux.Handle("/known/date", schemaHandler(knownSchemas[8])) - mux.Handle("/known/object", schemaHandler(knownSchemas[11])) - mux.Handle("/known/format", schemaHandler(knownSchemas[13])) - - mux.Handle("/complex/object", schemaHandler(complexSchemas[0])) - mux.Handle("/complex/array", schemaHandler(complexSchemas[1])) - mux.Handle("/complex/map", schemaHandler(complexSchemas[2])) + mux.Handle("/known/bool", schemaHandler(knownSchemas()[0])) + mux.Handle("/known/string", schemaHandler(knownSchemas()[1])) + mux.Handle("/known/integer", schemaHandler(knownSchemas()[5])) + mux.Handle("/known/float", schemaHandler(knownSchemas()[6])) + mux.Handle("/known/date", schemaHandler(knownSchemas()[8])) + mux.Handle("/known/object", schemaHandler(knownSchemas()[11])) + mux.Handle("/known/format", schemaHandler(knownSchemas()[13])) + + mux.Handle("/complex/object", schemaHandler(complexSchemas()[0])) + mux.Handle("/complex/array", schemaHandler(complexSchemas()[1])) + mux.Handle("/complex/map", schemaHandler(complexSchemas()[2])) return httptest.NewServer(mux) }