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  }