aboutsummaryrefslogtreecommitdiff
path: root/cmd/twirp-openapi-gen/internal/generator
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-02-19 21:37:28 -0500
committerXe Iaso <me@xeiaso.net>2025-02-19 21:37:28 -0500
commit39c822e6ebd930b4bdd29d985be56cd7759017ea (patch)
tree9430191fc28a647ff32351e2b85dd6db9f5404d1 /cmd/twirp-openapi-gen/internal/generator
parent3fd1aa2302ff42ca85021ee190b033d5aa2cc9e2 (diff)
downloadxesite-39c822e6ebd930b4bdd29d985be56cd7759017ea.tar.xz
xesite-39c822e6ebd930b4bdd29d985be56cd7759017ea.zip
remove this broken thing
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/twirp-openapi-gen/internal/generator')
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/aliases.go82
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/generator.go187
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/generator_test.go317
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/handlers.go579
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/doc.json413
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/gen/go/payment/v1alpha1/payment.pb.go263
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/gen/go/pet/v1/pet.pb.go896
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/gen/go/pet/v1/pet.twirp.go1705
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/paymentapis/payment/v1alpha1/payment.proto24
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/pet-api-doc.json321
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/pet-api-doc.yaml297
-rw-r--r--cmd/twirp-openapi-gen/internal/generator/testdata/petapis/pet/v1/pet.proto115
12 files changed, 0 insertions, 5199 deletions
diff --git a/cmd/twirp-openapi-gen/internal/generator/aliases.go b/cmd/twirp-openapi-gen/internal/generator/aliases.go
deleted file mode 100644
index 660d345..0000000
--- a/cmd/twirp-openapi-gen/internal/generator/aliases.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package generator
-
-var typeAliases = map[string]struct {
- Type, Format string
-}{
- // proto numeric types
- "int32": {Type: "integer", Format: "int32"},
- "uint32": {Type: "integer", Format: "uint32"},
- "sint32": {Type: "integer", Format: "int32"},
- "fixed32": {Type: "integer", Format: "int32"},
- "sfixed32": {Type: "integer", Format: "int32"},
-
- // proto numeric types, 64bit
- "int64": {Type: "string", Format: "int64"},
- "uint64": {Type: "string", Format: "uint64"},
- "sint64": {Type: "string", Format: "int64"},
- "fixed64": {Type: "string", Format: "int64"},
- "sfixed64": {Type: "string", Format: "int64"},
-
- "double": {Type: "number", Format: "double"},
- "float": {Type: "number", Format: "float"},
-
- // effectively copies google.protobuf.BytesValue
- "bytes": {
- Type: "string",
- Format: "byte",
- },
-
- // It is what it is
- "bool": {
- Type: "boolean",
- Format: "boolean",
- },
-
- "google.protobuf.Timestamp": {
- Type: "string",
- Format: "date-time",
- },
- "google.protobuf.Duration": {
- Type: "string",
- },
- "google.protobuf.StringValue": {
- Type: "string",
- },
- "google.protobuf.BytesValue": {
- Type: "string",
- Format: "byte",
- },
- "google.protobuf.Int32Value": {
- Type: "integer",
- Format: "int32",
- },
- "google.protobuf.UInt32Value": {
- Type: "integer",
- Format: "uint32",
- },
- "google.protobuf.Int64Value": {
- Type: "string",
- Format: "int64",
- },
- "google.protobuf.UInt64Value": {
- Type: "string",
- Format: "uint64",
- },
- "google.protobuf.FloatValue": {
- Type: "number",
- Format: "float",
- },
- "google.protobuf.DoubleValue": {
- Type: "number",
- Format: "double",
- },
- "google.protobuf.BoolValue": {
- Type: "boolean",
- Format: "boolean",
- },
-
- "google.type.DateTime": {
- Type: "string",
- Format: "date-time",
- },
-}
diff --git a/cmd/twirp-openapi-gen/internal/generator/generator.go b/cmd/twirp-openapi-gen/internal/generator/generator.go
deleted file mode 100644
index 2a08055..0000000
--- a/cmd/twirp-openapi-gen/internal/generator/generator.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package generator
-
-import (
- "encoding/json"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
-
- "github.com/emicklei/proto"
- "github.com/getkin/kin-openapi/openapi3"
- "github.com/invopop/yaml"
-)
-
-type generatorConfig struct {
- protoPaths []string
- servers []string
- title string
- docVersion string
- pathPrefix string
- format string
-}
-
-type Option func(config *generatorConfig) error
-
-func ProtoPaths(paths []string) Option {
- return func(config *generatorConfig) error {
- config.protoPaths = paths
- return nil
- }
-}
-
-func Servers(servers []string) Option {
- return func(config *generatorConfig) error {
- config.servers = servers
- return nil
- }
-}
-
-func Title(title string) Option {
- return func(config *generatorConfig) error {
- config.title = title
- return nil
- }
-}
-
-func DocVersion(version string) Option {
- return func(config *generatorConfig) error {
- config.docVersion = version
- return nil
- }
-}
-
-func PathPrefix(pathPrefix string) Option {
- return func(config *generatorConfig) error {
- config.pathPrefix = pathPrefix
- return nil
- }
-}
-
-func Format(format string) Option {
- return func(config *generatorConfig) error {
- config.format = format
- return nil
- }
-}
-
-type generator struct {
- openAPIV3 *openapi3.T
-
- conf *generatorConfig
- inputFiles []string
- packageName string
-
- importedFiles map[string]struct{}
-}
-
-func NewGenerator(inputFiles []string, options ...Option) (*generator, error) {
- conf := generatorConfig{}
- for _, opt := range options {
- if err := opt(&conf); err != nil {
- return nil, err
- }
- }
-
- if len(inputFiles) < 1 {
- return nil, fmt.Errorf("missing input files")
- }
-
- openAPIV3 := openapi3.T{
- OpenAPI: "3.0.0",
- Info: &openapi3.Info{
- Title: conf.title,
- Version: conf.docVersion,
- },
- Paths: openapi3.Paths{},
- Components: &openapi3.Components{
- Schemas: map[string]*openapi3.SchemaRef{},
- },
- }
-
- for _, server := range conf.servers {
- openAPIV3.Servers = append(openAPIV3.Servers, &openapi3.Server{URL: server})
- }
-
- slog.Debug("generating doc", "format", conf.format, "inputFiles", inputFiles)
-
- return &generator{
- inputFiles: inputFiles,
- openAPIV3: &openAPIV3,
- conf: &conf,
- importedFiles: map[string]struct{}{},
- }, nil
-}
-
-func (gen *generator) Generate(filename string) error {
- if _, err := gen.Parse(); err != nil {
- return err
- }
-
- if err := gen.Save(filename); err != nil {
- return err
- }
-
- return nil
-}
-
-func (gen *generator) Parse() (*openapi3.T, error) {
- for _, filename := range gen.inputFiles {
- protoFile, err := readProtoFile(filename, gen.conf.protoPaths)
- if err != nil {
- return nil, fmt.Errorf("readProtoFile: %w", err)
- }
- proto.Walk(protoFile, gen.Handlers()...)
- }
-
- slog.Debug("generated", "paths", len(gen.openAPIV3.Paths), "components", len(gen.openAPIV3.Components.Schemas))
- return gen.openAPIV3, nil
-}
-
-func (gen *generator) Save(filename string) error {
- var by []byte
- var err error
- switch gen.conf.format {
- case "json":
- by, err = gen.JSON()
- case "yaml", "yml":
- by, err = gen.YAML()
- default:
- return fmt.Errorf("missing format")
- }
- if err != nil {
- return err
- }
-
- return os.WriteFile(filename, by, os.ModePerm^0111)
-}
-
-func (gen *generator) JSON() ([]byte, error) {
- return json.MarshalIndent(gen.openAPIV3, "", " ")
-}
-
-func (gen *generator) YAML() ([]byte, error) {
- return yaml.Marshal(gen.openAPIV3)
-}
-
-func readProtoFile(filename string, protoPaths []string) (*proto.Proto, error) {
- var file *os.File
- var err error
- for _, path := range append(protoPaths, "") {
- file, err = os.Open(filepath.Join(path, filename))
- if err != nil {
- if os.IsNotExist(err) {
- continue
- }
- return nil, fmt.Errorf("Open: %w", err)
- }
- break
- }
- if file == nil {
- return nil, fmt.Errorf("could not read file %q", filename)
- }
- defer file.Close()
-
- parser := proto.NewParser(file)
- return parser.Parse()
-}
diff --git a/cmd/twirp-openapi-gen/internal/generator/generator_test.go b/cmd/twirp-openapi-gen/internal/generator/generator_test.go
deleted file mode 100644
index 78af950..0000000
--- a/cmd/twirp-openapi-gen/internal/generator/generator_test.go
+++ /dev/null
@@ -1,317 +0,0 @@
-package generator
-
-import (
- "flag"
- "log/slog"
- "os"
- "strings"
- "testing"
-)
-
-type ProtoRPC struct {
- name string
- input string
- output string
- desc string
-}
-
-type ProtoMessage struct {
- name string
- fields []ProtoField
-}
-
-type ProtoField struct {
- name string
- fieldType string
- format string
- desc string
- enums []string
- ref string
- itemsRef string
- itemsType string
-}
-
-var (
- verbose = flag.Bool("slog.verbose", false, "print debug logs to the console")
-)
-
-func init() {
- if *verbose {
- h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
- AddSource: true,
- Level: slog.LevelDebug,
- })
- slog.SetDefault(slog.New(h))
- } else {
- slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
- AddSource: true,
- })))
- }
-}
-
-func TestGenerator(t *testing.T) {
- flag.Parse()
-
- opts := []Option{
- ProtoPaths([]string{"./testdata/paymentapis", "./testdata/petapis"}),
- Servers([]string{"https://example.com"}),
- Title("Test"),
- DocVersion("0.1"),
- Format("json"),
- }
- gen, err := NewGenerator([]string{"./testdata/petapis/pet/v1/pet.proto"}, opts...)
- if err != nil {
- t.Fatal(err)
- }
- openAPI, err := gen.Parse()
- if err != nil {
- t.Fatal(err)
- }
-
- if err := gen.Save("./testdata/doc.json"); err != nil {
- t.Fatal(err)
- }
-
- pkgName := "pet.v1"
- serviceName := "PetStoreService"
- rpcs := []ProtoRPC{
- {
- name: "GetPet",
- input: "GetPetRequest",
- output: "GetPetResponse",
- },
- }
- messages := []ProtoMessage{
- {
- name: "GetPetRequest",
- fields: []ProtoField{
- {
- name: "pet_id",
- fieldType: "string",
- },
- },
- },
- {
- name: "Pet",
- fields: []ProtoField{
- {
- name: "pet_type",
- fieldType: "object",
- ref: "#/components/schemas/pet.v1.PetType",
- enums: []string{
- "PET_TYPE_UNSPECIFIED",
- "PET_TYPE_CAT",
- "PET_TYPE_DOG",
- "PET_TYPE_SNAKE",
- "PET_TYPE_HAMSTER",
- },
- },
- {
- name: "pet_types",
- fieldType: "array",
- itemsRef: "#/components/schemas/pet.v1.PetType",
- },
- {
- name: "tags",
- fieldType: "array",
- itemsType: "string",
- },
- {
- name: "pet_id",
- fieldType: "string",
- desc: "pet_id is an auto-generated id for the pet\\nthe id uniquely identifies a pet in the system",
- },
- {
- name: "name",
- fieldType: "string",
- },
- {
- name: "created_at",
- fieldType: "string",
- format: "date-time",
- },
- {
- name: "vet",
- fieldType: "object",
- ref: "#/components/schemas/pet.v1.Vet",
- },
- {
- name: "vets",
- fieldType: "array",
- itemsRef: "#/components/schemas/pet.v1.Vet",
- itemsType: "object",
- },
- },
- },
- }
-
- t.Run("RPC", func(t *testing.T) {
- for _, rpc := range rpcs {
- pathName := "/" + pkgName + "." + serviceName + "/" + rpc.name
- path, ok := openAPI.Paths[pathName]
- if !ok {
- t.Errorf("%s: missing rpc %q", pathName, rpc.name)
- }
-
- if path.Description != rpc.desc {
- t.Errorf("%s: expected desc %q but got %q", pathName, rpc.desc, path.Description)
- }
-
- post := path.Post
- if post == nil {
- t.Errorf("%s: missing post", pathName)
- continue
- }
-
- if post.Summary != rpc.name {
- t.Errorf("%s: expected summary %q but got %q", pathName, rpc.name, post.Summary)
- }
-
- requestBodyRef := post.RequestBody
- if requestBodyRef == nil {
- t.Errorf("%s: missing request body", pathName)
- continue
- }
-
- // request
- {
- requestBody := requestBodyRef.Value
- if requestBody == nil {
- t.Errorf("%s: missing request body", pathName)
- continue
- }
-
- mediaType, ok := requestBody.Content["application/json"]
- if !ok {
- t.Errorf("%s: missing content type", pathName)
- continue
- }
-
- if mediaType.Schema == nil {
- t.Errorf("%s: missing media type schema", pathName)
- continue
- }
-
- expectedRef := "#/components/schemas/" + pkgName + "." + rpc.input
- if mediaType.Schema.Ref != expectedRef {
- t.Errorf("%s: expected ref %q but got %q", pathName, expectedRef, mediaType.Schema.Ref)
- }
- }
-
- // response
- {
- respRef := post.Responses["200"]
- if respRef == nil {
- t.Errorf("%s: missing resp", pathName)
- continue
- }
-
- resp := respRef.Value
- if resp == nil {
- t.Errorf("%s: missing resp", pathName)
- continue
- }
-
- mediaType, ok := resp.Content["application/json"]
- if !ok {
- t.Errorf("%s: missing content type", pathName)
- continue
- }
-
- if mediaType.Schema == nil {
- t.Errorf("%s: missing media type schema", pathName)
- continue
- }
-
- expectedRef := "#/components/schemas/" + pkgName + "." + rpc.output
- if mediaType.Schema.Ref != expectedRef {
- t.Errorf("%s: expected ref %q but got %q", pathName, expectedRef, mediaType.Schema.Ref)
- }
- }
- }
- })
-
- t.Run("Messages", func(*testing.T) {
- for _, message := range messages {
- schemaName := "" + pkgName + "." + message.name
- schema, ok := openAPI.Components.Schemas[schemaName]
- if !ok {
- t.Errorf("%s: missing message %q", schemaName, message.name)
- }
- if schema.Value == nil {
- t.Errorf("%s: missing component", schemaName)
- continue
- }
- properties := schema.Value.Properties
- for _, messageField := range message.fields {
- propertyRef, ok := properties[messageField.name]
- if !ok {
- t.Errorf("%s: missing property %q", schemaName, messageField.name)
- }
-
- if propertyRef == nil || propertyRef.Value == nil {
- t.Errorf("%s: missing property ref", schemaName)
- continue
- }
-
- property := propertyRef.Value
- if property.Type != messageField.fieldType {
- t.Errorf("%s: %q expected property type %q but got %q", schemaName, message.name, messageField.fieldType, property.Type)
- continue
- }
-
- if messageField.format != "" {
- if messageField.format != "" && property.Format != messageField.format {
- t.Errorf("%s: expected property format %q but got %q", schemaName, messageField.format, property.Format)
- continue
- }
- }
-
- if propertyRef.Ref != messageField.ref {
- t.Errorf("%s: %q expected reference %q but got %q", schemaName, messageField.name, messageField.ref, propertyRef.Ref)
- }
-
- // check the reference schema
- if messageField.ref != "" {
- refParts := strings.Split(messageField.ref, "/")
- // the reference schema has the format of #/components/schemas/<type> so we need to get the last part
- schemaRef, ok := openAPI.Components.Schemas[refParts[len(refParts)-1]]
- if !ok {
- t.Errorf("%s: %q expected reference schema %q but got nil", schemaName, messageField.name, messageField.ref)
- } else {
- // check if the schema reference has the expected enum values
- if len(messageField.enums) > 0 {
- if schemaRef.Value.Enum == nil {
- t.Errorf("%s: %q expected reference schema enums %q but got nil", schemaName, messageField.name, messageField.ref)
- } else {
- enums := map[string]struct{}{}
- for _, e := range schemaRef.Value.Enum {
- enums[e.(string)] = struct{}{}
- }
- for _, e := range messageField.enums {
- if _, ok := enums[e]; !ok {
- t.Errorf("%s: %q expected reference schema enum %q to have %q but got nil", schemaName, messageField.name, messageField.ref, e)
- }
- }
- }
- }
- }
- }
-
- if property.Type == "array" {
- if property.Items == nil || property.Items.Value == nil {
- t.Errorf("%s: missing property enum array items", schemaName)
- }
- // only check the array items type if it's not a reference
- if messageField.itemsRef == "" && (property.Items.Value.Type != messageField.itemsType) {
- t.Errorf("%s: expected %s items type %q but got %q", schemaName, messageField.name, messageField.itemsType, property.Items.Value.Type)
- }
- // check the array items reference schema
- if property.Items.Ref != messageField.itemsRef {
- t.Errorf("%s: expected %s items ref %q but got %q", schemaName, messageField.name, messageField.itemsRef, property.Items.Ref)
- }
- }
- }
- }
- })
-}
diff --git a/cmd/twirp-openapi-gen/internal/generator/handlers.go b/cmd/twirp-openapi-gen/internal/generator/handlers.go
deleted file mode 100644
index 219000f..0000000
--- a/cmd/twirp-openapi-gen/internal/generator/handlers.go
+++ /dev/null
@@ -1,579 +0,0 @@
-package generator
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "log/slog"
- "path/filepath"
- "strings"
- "unicode"
-
- "github.com/emicklei/proto"
- "github.com/getkin/kin-openapi/openapi3"
-)
-
-const (
- googleAnyType = "google.protobuf.Any"
- googleListValueType = "google.protobuf.ListValue"
- googleStructType = "google.protobuf.Struct"
- googleValueType = "google.protobuf.Value"
- googleEmptyType = "google.protobuf.Empty"
-
- googleMoneyType = "google.type.Money"
-)
-
-var (
- successDescription = "Success"
-)
-
-func (gen *generator) Handlers() []proto.Handler {
- return []proto.Handler{
- proto.WithPackage(gen.Package),
- proto.WithImport(gen.Import),
- proto.WithRPC(gen.RPC),
- proto.WithEnum(gen.Enum),
- proto.WithMessage(gen.Message),
- }
-}
-
-func (gen *generator) Package(pkg *proto.Package) {
- slog.Debug("Package handler", "package", pkg.Name)
- gen.packageName = pkg.Name
-}
-
-func (gen *generator) Import(i *proto.Import) {
- slog.Debug("Import handler", "package", gen.packageName, "filename", i.Filename)
-
- if _, ok := gen.importedFiles[i.Filename]; ok {
- return
- }
- gen.importedFiles[i.Filename] = struct{}{}
-
- // Instead of loading and generating the OpenAPI docs for the google proto definitions,
- // its known types are mapped to OpenAPI types; see aliases.go.
- if strings.Contains(i.Filename, "google/") {
- return
- }
-
- protoFile, err := readProtoFile(i.Filename, gen.conf.protoPaths)
- if err != nil {
- slog.Error("could not import file", "filename", i.Filename, "error", err)
- return
- }
-
- oldPackageName := gen.packageName
-
- // Override the package name for the next round of Walk calls to preserve the types full import path
- withPackage := func(pkg *proto.Package) {
- gen.packageName = pkg.Name
- }
-
- // additional files walked for messages and imports only
- proto.Walk(protoFile,
- proto.WithPackage(withPackage),
- proto.WithImport(gen.Import),
- proto.WithRPC(gen.RPC),
- proto.WithEnum(gen.Enum),
- proto.WithMessage(gen.Message),
- )
-
- gen.packageName = oldPackageName
-}
-
-func (gen *generator) RPC(rpc *proto.RPC) {
- slog.Debug("RPC handler", "package", gen.packageName, "rpc", rpc.Name, "requestType", rpc.RequestType, "returnsType", rpc.ReturnsType)
-
- parent, ok := rpc.Parent.(*proto.Service)
- if !ok {
- log.Panicf("parent is not proto.service")
- }
- pathName := filepath.Join("/"+gen.conf.pathPrefix+"/", gen.packageName+"."+parent.Name, rpc.Name)
-
- var reqMediaType *openapi3.MediaType
- switch rpc.RequestType {
- case "google.protobuf.Empty":
- gen.addGoogleEmptySchema()
- default:
- }
- if strings.Contains(rpc.RequestType, ".") {
- reqMediaType = &openapi3.MediaType{
- Schema: &openapi3.SchemaRef{
- Ref: fmt.Sprintf("#/components/schemas/%s", rpc.RequestType),
- },
- }
- } else {
- reqMediaType = &openapi3.MediaType{
- Schema: &openapi3.SchemaRef{
- Ref: fmt.Sprintf("#/components/schemas/%s.%s", gen.packageName, rpc.RequestType),
- },
- }
- }
-
- var resMediaType *openapi3.MediaType
- switch rpc.ReturnsType {
- case "google.protobuf.Empty":
- gen.addGoogleEmptySchema()
- default:
- }
-
- if strings.Contains(rpc.ReturnsType, ".") {
- resMediaType = &openapi3.MediaType{
- Schema: &openapi3.SchemaRef{
- Ref: fmt.Sprintf("#/components/schemas/%s", rpc.ReturnsType),
- },
- }
- } else {
- resMediaType = &openapi3.MediaType{
- Schema: &openapi3.SchemaRef{
- Ref: fmt.Sprintf("#/components/schemas/%s.%s", gen.packageName, rpc.ReturnsType),
- },
- }
- }
-
- // NOTE: Redocly does not read the "examples" (plural) field, only the "example" (singular) one.
- commentMsg, reqExamples, resExamples, err := parseComment(rpc.Comment)
- if err != nil {
- // TODO(dm): how can we surface the errors from the parser instead of panicking?
- log.Panicf("failed to parse comment %s ", err)
- }
-
- if len(reqExamples) > 0 {
- exampleObj := make(map[string]interface{})
- for i, example := range reqExamples {
- exampleObj[fmt.Sprintf("example %d", i)] = example
- }
- reqMediaType.Example = exampleObj
- }
- if len(resExamples) > 0 {
- exampleObj := make(map[string]interface{})
- for i, example := range resExamples {
- exampleObj[fmt.Sprintf("example %d", i)] = example
- }
- resMediaType.Example = exampleObj
- }
-
- gen.openAPIV3.Paths[pathName] = &openapi3.PathItem{
- Post: &openapi3.Operation{
- Description: commentMsg,
- Summary: rpc.Name,
- RequestBody: &openapi3.RequestBodyRef{
- Value: &openapi3.RequestBody{
- Content: openapi3.Content{"application/json": reqMediaType},
- },
- },
- Responses: map[string]*openapi3.ResponseRef{
- "200": {
- Value: &openapi3.Response{
- Description: &successDescription,
- Content: openapi3.Content{"application/json": resMediaType},
- },
- },
- },
- },
- }
-}
-
-func (gen *generator) Enum(enum *proto.Enum) {
- slog.Debug("Enum handler", "package", gen.packageName, "enum", enum.Name)
- values := []interface{}{}
- for _, element := range enum.Elements {
- enumField := element.(*proto.EnumField)
- values = append(values, enumField.Name)
- }
-
- gen.openAPIV3.Components.Schemas[gen.packageName+"."+enum.Name] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: description(enum.Comment),
- Type: "string",
- Enum: values,
- },
- }
-}
-
-func (gen *generator) Message(msg *proto.Message) {
- slog.Debug("Message handler", "package", gen.packageName, "message", msg.Name)
-
- schemaProps := openapi3.Schemas{}
-
- for _, element := range msg.Elements {
- switch val := element.(type) {
- case *proto.Message:
- //logger.logd("proto.Message")
- gen.Message(val)
- case *proto.Comment:
- //logger.logd("proto.Comment")
- case *proto.Oneof:
- //logger.logd("proto.Oneof")
- case *proto.OneOfField:
- //logger.logd("proto.OneOfField")
- gen.addField(schemaProps, val.Field, false)
- case *proto.MapField:
- //logger.logd("proto.MapField")
- gen.addField(schemaProps, val.Field, false)
- case *proto.NormalField:
- //logger.logd("proto.NormalField %q %q", val.Field.Type, val.Field.Name)
- gen.addField(schemaProps, val.Field, val.Repeated)
- default:
- slog.Error("unknown field type", "type", fmt.Sprintf("%T", element))
- }
- }
-
- gen.openAPIV3.Components.Schemas[gen.packageName+"."+msg.Name] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: description(msg.Comment),
- Type: "object",
- Properties: schemaProps,
- },
- }
-}
-
-func (gen *generator) addField(schemaPropsV3 openapi3.Schemas, field *proto.Field, repeated bool) {
- fieldDescription := description(field.Comment)
- fieldName := field.Name
- fieldType := field.Type
- fieldFormat := field.Type
- // map proto types to openapi
- if p, ok := typeAliases[fieldType]; ok {
- fieldType = p.Type
- fieldFormat = p.Format
- }
-
- if fieldType == fieldFormat {
- fieldFormat = ""
- }
-
- switch fieldType {
- // Build the schema for native types that don't need to reference other schemas
- // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types
- case "boolean", "integer", "number", "string", "object":
- fieldSchemaV3 := openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: fieldDescription,
- Type: fieldType,
- Format: fieldFormat,
- },
- }
- if !repeated {
- schemaPropsV3[fieldName] = &fieldSchemaV3
- return
- }
- schemaPropsV3[fieldName] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: fieldDescription,
- Type: "array",
- Format: fieldFormat,
- Items: &fieldSchemaV3,
- },
- }
- return
-
- // generate the schema for google well known complex types: https://protobuf.dev/reference/protobuf/google.protobuf/#index
- case googleAnyType:
- slog.Debug("any", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleAnySchema()
- case googleListValueType:
- slog.Debug("ListValue", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleListValueSchema()
- case googleStructType:
- slog.Debug("Struct", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleValueSchema() // struct depends on value
- gen.addGoogleStructSchema()
- case googleValueType:
- slog.Debug("Value", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleValueSchema()
- case googleMoneyType:
- slog.Debug("Money", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleMoneySchema()
- case googleEmptyType:
- slog.Debug("Empty", "name", fieldName, "type", fieldType, "format", fieldFormat)
- gen.addGoogleEmptySchema()
- default:
- slog.Debug("Default", "name", fieldName, "type", fieldType, "format", fieldFormat)
- }
-
- // prefix custom types with the package name
- ref := fmt.Sprintf("#/components/schemas/%s", fieldType)
- if !strings.Contains(fieldType, ".") {
- ref = fmt.Sprintf("#/components/schemas/%s.%s", gen.packageName, fieldType)
- }
-
- if !repeated {
- schemaPropsV3[fieldName] = &openapi3.SchemaRef{
- Ref: ref,
- Value: &openapi3.Schema{
- Description: fieldDescription,
- Type: "object",
- },
- }
- return
- }
-
- schemaPropsV3[fieldName] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: fieldDescription,
- Type: "array",
- Items: &openapi3.SchemaRef{
- Ref: ref,
- Value: &openapi3.Schema{
- Type: "object",
- },
- },
- },
- }
-}
-
-func (gen *generator) addGoogleEmptySchema() {
- if _, ok := gen.openAPIV3.Components.Schemas[googleEmptyType]; ok {
- return
- }
- gen.openAPIV3.Components.Schemas[googleEmptyType] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: "A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance:",
- Type: "object",
- },
- }
-}
-
-// addGoogleAnySchema adds a schema item for the google.protobuf.Any type.
-func (gen *generator) addGoogleAnySchema() {
- if _, ok := gen.openAPIV3.Components.Schemas[googleAnyType]; ok {
- return
- }
- gen.openAPIV3.Components.Schemas[googleAnyType] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: `
-The JSON representation of an Any value uses the regular
-representation of the deserialized, embedded message, with an
-additional field @type which contains the type URL. Example:
-
- package google.profile;
- message Person {
- string first_name = 1;
- string last_name = 2;
- }
-
- {
- "@type": "type.googleapis.com/google.profile.Person",
- "firstName": <string>,
- "lastName": <string>
- }
-
-If the embedded message type is well-known and has a custom JSON
-representation, that representation will be embedded adding a field
-value which holds the custom JSON in addition to the @type
-field. Example (for message [google.protobuf.Duration][]):
-
- {
- "@type": "type.googleapis.com/google.protobuf.Duration",
- "value": "1.212s"
- }
-`,
- Type: "object",
- Properties: openapi3.Schemas{
- "@type": &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: "",
- Type: "string",
- Format: "",
- },
- },
- },
- },
- }
-}
-
-// addGoogleAnySchema adds a schema item for the google.protobuf.ListValue type.
-func (gen *generator) addGoogleListValueSchema() {
- if _, ok := gen.openAPIV3.Components.Schemas[googleListValueType]; ok {
- return
- }
- gen.openAPIV3.Components.Schemas[googleListValueType] = &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Description: `
-ListValue is a wrapper around a repeated field of values.
-The JSON representation for ListValue is JSON array.
-`,
- Type: "array",
- Items: &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- OneOf: openapi3.SchemaRefs{
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "string",
- },
- },
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "number",
- },
- },
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "integer",
- },
- },
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "boolean",
- },
- },
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "array",
- },
- },
- &openapi3.SchemaRef{
- Value: &openapi3.Schema{
- Type: "object",
- },
- },
- },
- },