internal/api: add missing fields to openapi.yaml The schema generator skipped embedded fields, so the Package response schema omitted the fields promoted from PackageInfo. Walk embedded structs and flatten their fields into the parent, matching how encoding/json promotes them. Fixes golang/go#79989 Change-Id: Id74a313ff4ab5667d647425ba04c5d31a57f73f0 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/790640 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> kokoro-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Ethan Lee <ethanalee@google.com> Auto-Submit: Ethan Lee <ethanalee@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/internal/api/openapi.yaml b/internal/api/openapi.yaml index c9dd9bc..3f2f9a7 100644 --- a/internal/api/openapi.yaml +++ b/internal/api/openapi.yaml
@@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Go Pkgsite API", - "version": "v0.1.0", + "version": "v0.1.1", "description": "API for accessing information about Go packages and modules on pkg.go.dev." }, "servers": [ @@ -653,6 +653,10 @@ "isLatest": { "type": "boolean" }, + "isRedistributable": { + "description": "Whether the license allows distribution.", + "type": "boolean" + }, "isStandardLibrary": { "type": "boolean" }, @@ -665,6 +669,15 @@ "modulePath": { "type": "string" }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "synopsis": { + "type": "string" + }, "version": { "type": "string" }
diff --git a/internal/api/openapi_test.go b/internal/api/openapi_test.go index e8a58ae..637cfb9 100644 --- a/internal/api/openapi_test.go +++ b/internal/api/openapi_test.go
@@ -100,6 +100,34 @@ }`, }, { + name: "embedded struct", + data: ` +package api +type Package struct { + Version string ` + "`" + `json:"version"` + "`" + ` + PackageInfo +} +type PackageInfo struct { + Path string ` + "`" + `json:"path"` + "`" + ` + Synopsis string ` + "`" + `json:"synopsis"` + "`" + ` +} +`, + want: `"Package": { + "properties": { + "path": { + "type": "string" + }, + "synopsis": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }`, + }, + { name: "instantiated generic", data: ` package api @@ -209,7 +237,7 @@ func GenerateOpenAPI() (string, error) { const ( openAPISpecVersion = "3.0.3" - apiVersion = "v0.1.0" + apiVersion = "v0.1.1" apiPathPrefix = "/v1beta" ) @@ -309,8 +337,7 @@ return nil, err } - schemas := make(map[string]any) - + structs := make(map[string]*ast.StructType) for _, decl := range file.Decls { genDecl, ok := decl.(*ast.GenDecl) if !ok || genDecl.Tok != token.TYPE { @@ -321,49 +348,57 @@ if !ok { continue } - structType, ok := typeSpec.Type.(*ast.StructType) - if !ok { - continue - } - typeName := typeSpec.Name.Name - properties := make(map[string]any) - - for _, field := range structType.Fields.List { - if field.Names == nil { - continue - } - - fieldName := field.Names[0].Name - tag := "" - if field.Tag != nil { - tag = field.Tag.Value - } - jsonName := extractJSONName(tag) - if jsonName == "" { - jsonName = fieldName - } - - typeStr := typeExprToString(field.Type) - prop := mapFieldType(typeStr) - if field.Doc != nil { - prop["description"] = strings.TrimSpace(field.Doc.Text()) - } else if field.Comment != nil { - prop["description"] = strings.TrimSpace(field.Comment.Text()) - } - properties[jsonName] = prop - } - - schemas[typeName] = map[string]any{ - "type": "object", - "properties": properties, + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + structs[typeName] = structType } } } + schemas := make(map[string]any) + for name, structType := range structs { + properties := make(map[string]any) + collectProperties(structType, structs, properties) + schemas[name] = map[string]any{ + "type": "object", + "properties": properties, + } + } + return schemas, nil } +// collectProperties adds the schema property for each field of st to properties, +// recursing into embedded structs so their fields are promoted to the parent. +func collectProperties(st *ast.StructType, structs map[string]*ast.StructType, properties map[string]any) { + for _, field := range st.Fields.List { + if field.Names == nil { + if embedded, ok := structs[typeExprToString(field.Type)]; ok { + collectProperties(embedded, structs, properties) + } + continue + } + + fieldName := field.Names[0].Name + tag := "" + if field.Tag != nil { + tag = field.Tag.Value + } + jsonName := extractJSONName(tag) + if jsonName == "" { + jsonName = fieldName + } + + prop := mapFieldType(typeExprToString(field.Type)) + if field.Doc != nil { + prop["description"] = strings.TrimSpace(field.Doc.Text()) + } else if field.Comment != nil { + prop["description"] = strings.TrimSpace(field.Comment.Text()) + } + properties[jsonName] = prop + } +} + func mapFieldType(t string) map[string]any { switch t { case "string":