github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/cmd/protoc-gen-openapi/converter/converter.go (about) 1 package converter 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "path" 9 "strings" 10 11 "github.com/tickoalcantara12/micro/v3/service/logger" 12 13 "github.com/getkin/kin-openapi/openapi3" 14 "github.com/golang/protobuf/proto" 15 "github.com/golang/protobuf/protoc-gen-go/descriptor" 16 plugin "github.com/golang/protobuf/protoc-gen-go/plugin" 17 prot "google.golang.org/protobuf/compiler/protogen" 18 ) 19 20 // Converter is everything you need to convert Micro protos into an OpenAPI spec: 21 type Converter struct { 22 microServiceName string 23 openAPISpec *openapi3.Swagger 24 sourceInfo *sourceCodeInfo 25 req *plugin.CodeGeneratorRequest 26 plug *prot.Plugin 27 } 28 29 // New returns a configured converter: 30 func New() *Converter { 31 return &Converter{} 32 } 33 34 // ConvertFrom tells the convert to work on the given input: 35 func (c *Converter) ConvertFrom(rd io.Reader) (*plugin.CodeGeneratorResponse, error) { 36 logger.Debug("Reading code generation request") 37 input, err := ioutil.ReadAll(rd) 38 if err != nil { 39 logger.Errorf("Failed to read request: %v", err) 40 return nil, err 41 } 42 43 req := &plugin.CodeGeneratorRequest{} 44 err = proto.Unmarshal(input, req) 45 if err != nil { 46 logger.Errorf("Can't unmarshal input: %v", err) 47 return nil, err 48 } 49 c.req = req 50 opts := &prot.Options{} 51 plug, err := opts.New(c.req) 52 if err != nil { 53 return nil, err 54 } 55 c.plug = plug 56 c.defaultSpec() 57 58 logger.Debugf("Converting input: %v", err) 59 return c.convert(req) 60 } 61 62 // Converts a proto file into an OpenAPI spec: 63 func (c *Converter) convertFile(file *descriptor.FileDescriptorProto) error { 64 65 // Input filename: 66 protoFileName := path.Base(file.GetName()) 67 68 // Get the proto package: 69 pkg, ok := c.relativelyLookupPackage(globalPkg, file.GetPackage()) 70 if !ok { 71 return fmt.Errorf("no such package found: %s", file.GetPackage()) 72 } 73 c.openAPISpec.Info.Title = strings.Title(strings.Replace(pkg.name, ".", "", 1)) 74 75 // Process messages: 76 for _, msg := range file.GetMessageType() { 77 78 // Convert the message: 79 logger.Debugf("Generating component schema for message (%s) from proto file (%s)", msg.GetName(), protoFileName) 80 componentSchema, err := c.convertMessageType(pkg, msg) 81 if err != nil { 82 logger.Errorf("Failed to convert (%s): %v", protoFileName, err) 83 return err 84 } 85 86 // Add the message to our component schemas (we'll refer to these later when we build the service endpoints): 87 // componentSchemaKey := fmt.Sprintf("%s.%s", pkg.name, componentSchema.Title) 88 c.openAPISpec.Components.Schemas[componentSchema.Title] = openapi3.NewSchemaRef("", componentSchema) 89 } 90 91 // Process services: 92 for _, svc := range file.GetService() { 93 94 // Convert the service: 95 logger.Infof("Generating service (%s) from proto file (%s)", svc.GetName(), protoFileName) 96 servicePaths, err := c.convertServiceType(file, pkg, svc) 97 if err != nil { 98 logger.Errorf("Failed to convert (%s): %v", protoFileName, err) 99 return err 100 } 101 102 // Add the paths to our API: 103 for path, pathItem := range servicePaths { 104 c.openAPISpec.Paths[path] = pathItem 105 } 106 } 107 108 return nil 109 } 110 111 func (c *Converter) convert(req *plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorResponse, error) { 112 res := &plugin.CodeGeneratorResponse{} 113 114 c.parseGeneratorParameters(req.GetParameter()) 115 116 // Parse the source code (this is where we pick up code comments, which become schema descriptions): 117 c.sourceInfo = newSourceCodeInfo(req.GetProtoFile()) 118 119 generateTargets := make(map[string]bool) 120 for _, file := range req.GetFileToGenerate() { 121 generateTargets[file] = true 122 } 123 124 // We're potentially dealing with several proto files: 125 for _, file := range req.GetProtoFile() { 126 127 // Make sure it belongs to a package (sometimes they don't): 128 if file.GetPackage() == "" { 129 logger.Warnf("Proto file (%s) doesn't specify a package", file.GetName()) 130 continue 131 } 132 133 // Set the service name from the proto package (if it isn't already set): 134 if c.microServiceName == "" { 135 c.microServiceName = protoServiceName(file.GetPackage()) 136 } 137 138 // Register all of the messages we can find: 139 for _, msg := range file.GetMessageType() { 140 logger.Debugf("Loading a message (%s/%s)", file.GetPackage(), msg.GetName()) 141 c.registerType(file.Package, msg) 142 } 143 144 if _, ok := generateTargets[file.GetName()]; ok { 145 logger.Debugf("Converting file (%s)", file.GetName()) 146 147 // set the name based on the file we're processing 148 c.microServiceName = protoServiceName(file.GetPackage()) 149 150 if err := c.convertFile(file); err != nil { 151 res.Error = proto.String(fmt.Sprintf("Failed to convert %s: %v", file.GetName(), err)) 152 return res, err 153 } 154 } 155 } 156 157 // Marshal the OpenAPI spec: 158 marshaledSpec, err := json.MarshalIndent(c.openAPISpec, "", " ") 159 if err != nil { 160 logger.Errorf("Unable to marshal the OpenAPI spec: %v", err) 161 return nil, err 162 } 163 164 // Add a response file: 165 res.File = []*plugin.CodeGeneratorResponse_File{ 166 { 167 Name: proto.String(c.openAPISpecFileName()), 168 Content: proto.String(string(marshaledSpec)), 169 }, 170 } 171 172 return res, nil 173 } 174 175 func (c *Converter) openAPISpecFileName() string { 176 return fmt.Sprintf("api-%s.json", c.microServiceName) 177 } 178 179 func (c *Converter) parseGeneratorParameters(parameters string) { 180 logger.Debug("Parsing params") 181 182 for _, parameter := range strings.Split(parameters, ",") { 183 184 logger.Debugf("Param: %s", parameter) 185 186 // Allow users to specify the service name: 187 if serviceNameParameter := strings.Split(parameter, "service="); len(serviceNameParameter) == 2 { 188 c.microServiceName = serviceNameParameter[1] 189 logger.Infof("Service name: %s", c.microServiceName) 190 } 191 } 192 }