diff --git a/docparse/find.go b/docparse/find.go index b951b0c..913a595 100644 --- a/docparse/find.go +++ b/docparse/find.go @@ -473,6 +473,20 @@ func GetReference(prog *Program, context string, isEmbed bool, lookup, filePath } } + // Ensure the lookup key is unique when two packages share the same base name + // (e.g. task/reminder and time/reminder both produce "reminder.Request"). + if existing, ok := prog.References[ref.Lookup]; ok && existing.Package != ref.Package { + for i := 2; ; i++ { + candidate := fmt.Sprintf("%s%d", ref.Lookup, i) + if ex, ok := prog.References[candidate]; !ok { + ref.Lookup = candidate + break + } else if ex.Package == ref.Package { + ref.Lookup = candidate + break + } + } + } prog.References[ref.Lookup] = ref var ( nested []string @@ -541,42 +555,73 @@ func GetReference(prog *Program, context string, isEmbed bool, lookup, filePath } ref.Schema = schema - changed := false + if err := applyFieldWhitelists(prog, context, filePath, name, tagName, &ref); err != nil { + return nil, err + } + + // Merge for embedded structs without a tag. + for _, n := range nested { + ref.Fields = append(ref.Fields, prog.References[n].Fields...) + + if prog.References[n].Schema != nil { + for k, v := range prog.References[n].Schema.Properties { + if _, ok := ref.Schema.Properties[k]; !ok { + ref.Schema.Properties[k] = v + } + } + } + } + + if ref.IsSlice { + sliceSchema := &Schema{ + Type: "array", + Items: ref.Schema, + } + ref.Schema = sliceSchema + } + + if ref.Wrapper != "" { + wrappedSchema := &Schema{ + Title: ref.Name, + Type: "object", + Properties: map[string]*Schema{}, + } + + wrappedSchema.Properties[ref.Wrapper] = ref.Schema + ref.Schema = wrappedSchema + } + prog.References[ref.Lookup] = ref + + return &ref, nil +} + +func applyFieldWhitelists(prog *Program, context, filePath, name, tagName string, ref *Reference) error { + changed := false for _, p := range ref.Schema.Properties { - // Check if any fields are whitelisted, if not continue onto next property if len(p.FieldWhitelist) == 0 { continue } - changed = true - - // Get the package so we can lookup the correct reference split := strings.Split(p.Reference, ".") lookupStruct := strings.Join(split[:len(split)-1], ".") if lookupStruct != "" { lookupStruct += "." } - for i, f := range ref.Fields { if lookupStruct+f.Name != p.Reference { continue } - - // Find the referenced struct reference, err := GetReference(prog, context, false, lookupStruct+f.Name, filePath) if err != nil { - return nil, fmt.Errorf("could not get referenced struct %s", lookupStruct+f.Name) + return fmt.Errorf("could not get referenced struct %s", lookupStruct+f.Name) } - fields := []*ast.Field{} for _, field := range reference.Fields { if sliceutil.Contains(p.FieldWhitelist, strings.ToLower(field.Name)) { fields = append(fields, field.KindField) } } - - // Construct the parameter using the given fields ref.Fields[i] = Param{ Name: f.Name, KindField: &ast.Field{ @@ -585,10 +630,8 @@ func GetReference(prog *Program, context string, isEmbed bool, lookup, filePath }, Names: f.KindField.Names, Type: &ast.StructType{ - Struct: 0, Fields: &ast.FieldList{ - Opening: 0, - List: fields, + List: fields, }, }, Tag: f.KindField.Tag, @@ -597,51 +640,14 @@ func GetReference(prog *Program, context string, isEmbed bool, lookup, filePath } } } - - // If the fields have been changed, regenerate the schema with the new fields if changed { - schema, err = structToSchema(prog, name, tagName, ref) + schema, err := structToSchema(prog, name, tagName, *ref) if err != nil { - return nil, fmt.Errorf("%v can not be converted to JSON schema: %v", name, err) + return fmt.Errorf("%v can not be converted to JSON schema: %v", name, err) } ref.Schema = schema } - - // Merge for embedded structs without a tag. - for _, n := range nested { - ref.Fields = append(ref.Fields, prog.References[n].Fields...) - - if prog.References[n].Schema != nil { - for k, v := range prog.References[n].Schema.Properties { - if _, ok := ref.Schema.Properties[k]; !ok { - ref.Schema.Properties[k] = v - } - } - } - } - - if ref.IsSlice { - sliceSchema := &Schema{ - Type: "array", - Items: ref.Schema, - } - ref.Schema = sliceSchema - } - - if ref.Wrapper != "" { - wrappedSchema := &Schema{ - Title: ref.Name, - Type: "object", - Properties: map[string]*Schema{}, - } - - wrappedSchema.Properties[ref.Wrapper] = ref.Schema - ref.Schema = wrappedSchema - } - - prog.References[ref.Lookup] = ref - - return &ref, nil + return nil } func findNested(prog *Program, context string, isEmbed bool, f *ast.Field, filePath, pkg string) (string, error) { diff --git a/testdata/openapi2/src/package-name-collision/in.go b/testdata/openapi2/src/package-name-collision/in.go new file mode 100644 index 0000000..679cd30 --- /dev/null +++ b/testdata/openapi2/src/package-name-collision/in.go @@ -0,0 +1,16 @@ +package package_name_collision + +import ( + sharedA "package-name-collision/sub-a/shared" + sharedB "package-name-collision/sub-b/shared" +) + +// POST /foo create foo +// +// Request body: sharedA.Request +// Response 200: {empty} + +// POST /bar create bar +// +// Request body: sharedB.Request +// Response 200: {empty} diff --git a/testdata/openapi2/src/package-name-collision/sub-a/shared/in.go b/testdata/openapi2/src/package-name-collision/sub-a/shared/in.go new file mode 100644 index 0000000..3ac34b2 --- /dev/null +++ b/testdata/openapi2/src/package-name-collision/sub-a/shared/in.go @@ -0,0 +1,5 @@ +package shared + +type Request struct { + ID int64 `json:"id"` +} diff --git a/testdata/openapi2/src/package-name-collision/sub-b/shared/in.go b/testdata/openapi2/src/package-name-collision/sub-b/shared/in.go new file mode 100644 index 0000000..673ef8f --- /dev/null +++ b/testdata/openapi2/src/package-name-collision/sub-b/shared/in.go @@ -0,0 +1,5 @@ +package shared + +type Request struct { + Name string `json:"name"` +} diff --git a/testdata/openapi2/src/package-name-collision/want.yaml b/testdata/openapi2/src/package-name-collision/want.yaml new file mode 100644 index 0000000..11b07b6 --- /dev/null +++ b/testdata/openapi2/src/package-name-collision/want.yaml @@ -0,0 +1,64 @@ +swagger: "2.0" +info: + title: x + version: x +consumes: + - application/json +produces: + - application/json +tags: + - name: bar + - name: create + - name: foo +paths: + /bar: + post: + operationId: POST_bar + tags: + - create + - bar + consumes: + - application/json + produces: + - application/json + parameters: + - name: shared.Request2 + in: body + required: true + schema: + $ref: '#/definitions/shared.Request2' + responses: + 200: + description: 200 OK (no data) + /foo: + post: + operationId: POST_foo + tags: + - create + - foo + consumes: + - application/json + produces: + - application/json + parameters: + - name: shared.Request + in: body + required: true + schema: + $ref: '#/definitions/shared.Request' + responses: + 200: + description: 200 OK (no data) +definitions: + shared.Request: + title: Request + type: object + properties: + id: + type: integer + shared.Request2: + title: Request + type: object + properties: + name: + type: string