diff --git a/docparse/jsonschema.go b/docparse/jsonschema.go index 9c7d45a..5b0031d 100644 --- a/docparse/jsonschema.go +++ b/docparse/jsonschema.go @@ -330,12 +330,8 @@ start: typ = &ast.Ident{Name: resolvedName} } } - if p.Type == "enum" && len(p.Enum) == 0 { - if variations, err := getEnumVariations(ref.File, pkg, typ.Name); len(variations) > 0 { - p.Enum = variations - } else if err != nil { - return nil, err - } + if err := fillEnumVariations(&p, ref.File, pkg, typ.Name); err != nil { + return nil, err } if mappedType == "" { // Only check for canonicalType if this isn't mapped. @@ -396,6 +392,12 @@ start: return nil, fmt.Errorf("cannot get canonical type: %v", err) } if canon != nil { + // Resolve enum variations before goto start: after re-entry the + // type name becomes the canonical primitive (e.g. "string") and + // getEnumVariations would look for the wrong type. + if err := fillEnumVariations(&p, ref.File, pkg, name.Name); err != nil { + return nil, err + } sw = canon goto start } @@ -675,6 +677,20 @@ func fillGenericsSchema( return nil } +// fillEnumVariations populates p.Enum if p.Type is "enum" and no values have +// been set yet. It is a no-op otherwise. +func fillEnumVariations(p *Schema, currentFile, pkgPath, typeName string) error { + if p.Type != "enum" || len(p.Enum) != 0 { + return nil + } + variations, err := getEnumVariations(currentFile, pkgPath, typeName) + if err != nil { + return err + } + p.Enum = variations + return nil +} + // Helper function to extract enum variations from a file. func getEnumVariations(currentFile, pkgPath, typeName string) ([]string, error) { resolvedPath, pkg, err := resolvePackage(currentFile, pkgPath) diff --git a/docparse/jsonschema_test.go b/docparse/jsonschema_test.go index 7b0047a..80f5d4b 100644 --- a/docparse/jsonschema_test.go +++ b/docparse/jsonschema_test.go @@ -145,6 +145,45 @@ func TestFieldToProperty(t *testing.T) { } }) + t.Run("external_enum", func(t *testing.T) { + wantExternal := map[string]*Schema{ + "status": {Type: "string", Enum: []string{"active", "inactive", "pending"}}, + "statuses": {Type: "array", Items: &Schema{Type: "string", Enum: []string{"active", "inactive", "pending"}}}, + } + + prog := NewProgram(false) + ts, _, _, err := findType("./testdata/src/a/a.go", "a", "withExternalEnum") + if err != nil { + t.Fatalf("could not parse file: %v", err) + } + + st, ok := ts.Type.(*ast.StructType) + if !ok { + t.Fatal("not a struct?!") + } + + for _, f := range st.Fields.List { + out, err := fieldToSchema(prog, f.Names[0].Name, "json", Reference{ + Package: "a", + File: "./testdata/src/a/a.go", + Context: "req", + }, f, nil) + if err != nil { + t.Fatal(err) + } + + for _, n := range f.Names { + w, ok := wantExternal[n.Name] + if !ok { + t.Fatalf("no test case for %v", n.Name) + } + if d := diff.Diff(w, out); d != "" { + t.Errorf("%v: %v", n.Name, d) + } + } + } + }) + t.Run("nested", func(t *testing.T) { prog := NewProgram(false) ts, _, _, err := findType("./testdata/src/a/a.go", "a", "nested") diff --git a/docparse/testdata/src/a/a.go b/docparse/testdata/src/a/a.go index a4b0d4a..c8ceb99 100644 --- a/docparse/testdata/src/a/a.go +++ b/docparse/testdata/src/a/a.go @@ -1,6 +1,10 @@ package a -import "net/mail" +import ( + "net/mail" + + "b" +) // GET / // @@ -78,3 +82,10 @@ type refAnother2 struct { strct bar pkg mail.Address } + +type withExternalEnum struct { + // {enum} + status b.StatusType + // {enum} + statuses []b.StatusType +} diff --git a/docparse/testdata/src/b/b.go b/docparse/testdata/src/b/b.go new file mode 100644 index 0000000..80428b0 --- /dev/null +++ b/docparse/testdata/src/b/b.go @@ -0,0 +1,9 @@ +package b + +type StatusType string + +const ( + StatusTypeActive StatusType = "active" + StatusTypeInactive StatusType = "inactive" + StatusTypePending StatusType = "pending" +)