Created
June 8, 2023 03:50
-
-
Save swuecho/73a4a722f0b8a27b1eff0f219afa83c9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net" | |
"os" | |
"strings" | |
"unicode" | |
"path" | |
"github.com/emicklei/proto" | |
"github.com/go-openapi/spec" | |
) | |
type Field struct { | |
Name string | |
Type string | |
Repeated bool | |
} | |
type Message struct { | |
Name string | |
Fields []*Field | |
} | |
type Document struct { | |
Name string | |
Messages []*Message | |
} | |
func fileStem(filePath string) string { | |
filename := path.Base(filePath) | |
extension := path.Ext(filename) | |
return filename[0 : len(filename)-len(extension)] | |
} | |
var protodoc Document | |
func main() { | |
fileName := fileStem(filePath) | |
protodoc.Name = fileName | |
reader, _ := os.Open(filePath) | |
defer reader.Close() | |
parser := proto.NewParser(reader) | |
definition, _ := parser.Parse() | |
proto.Walk(definition, | |
proto.WithService(handleService), | |
proto.WithMessage(handleMessage)) | |
bytes, err := json.MarshalIndent(protodoc, "", " ") | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(string(bytes)) | |
spec := createSwagger(protodoc) | |
// JSON | |
jsonbytes, err := json.MarshalIndent(spec, "", " ") | |
if err != nil { | |
panic(err) | |
} | |
err = ioutil.WriteFile("swagger/"+fileName+".json", jsonbytes, 0644) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func handleService(s *proto.Service) { | |
fmt.Println("service") | |
fmt.Println(s.Name) | |
} | |
func handleMessage(m *proto.Message) { | |
message := &Message{ | |
Name: m.Name, | |
} | |
protodoc.Messages = append(protodoc.Messages, message) | |
println("\n\n") | |
fmt.Println(m.Name) | |
println("\n") | |
lister := new(optionLister) | |
lister.doc = protodoc | |
for _, each := range m.Elements { | |
each.Accept(lister) | |
} | |
} | |
type optionLister struct { | |
proto.NoopVisitor | |
doc Document | |
} | |
func (l optionLister) VisitOption(o *proto.Option) { | |
fmt.Println(o.Name) | |
} | |
func (l optionLister) VisitNormalField(o *proto.NormalField) { | |
field := &Field{ | |
Name: o.Name, | |
Type: o.Type, | |
Repeated: o.Repeated, | |
} | |
message := l.doc.Messages[len(l.doc.Messages)-1] | |
message.Fields = append(message.Fields, field) | |
} | |
func IsComplexType(s string) bool { | |
for _, r := range s { | |
if r >= 'A' && r <= 'Z' { | |
return true | |
} | |
} | |
return false | |
} | |
func getType(input string) string { | |
switch input { | |
case "double", "float", "int64", "uint64", "int32", "fixed64", "fixed32", "bool", "uint32", "sfixed32", "sfixed64", "sint32", "sint64": | |
return "number" | |
default: | |
return input | |
} | |
} | |
func splitStringAtUpper(s string) []string { | |
var words []string | |
var lastIdx int | |
for i, r := range s { | |
if i > 0 && unicode.IsUpper(r) { | |
words = append(words, s[lastIdx:i]) | |
lastIdx = i | |
} | |
} | |
// add the final word | |
words = append(words, s[lastIdx:]) | |
return words | |
} | |
// method name to path | |
// | |
// CategorySave => category/save, ToolList => tool/list, TwoWordSave => two_word/save | |
func methodNameToPath(s string) string { | |
rules := GetRoutes("routes/" + protodoc.Name + ".yaml") | |
// if method in methodsMap | |
selectedRule := FindRuleBySelector(s, rules) | |
if selectedRule != nil { | |
return selectedRule.Post | |
} else { | |
words := splitStringAtUpper(s) | |
// slice the slice to exclude last element | |
joined := strings.Join(words[:len(words)-1], "_") | |
// append last word with underscore | |
return strings.ToLower(joined + "/" + words[len(words)-1]) | |
} | |
} | |
func createSchemaDef(swagger *spec.Swagger, doc Document) { | |
definitions := make(spec.Definitions) | |
for _, message := range protodoc.Messages { | |
msgSchema := spec.Schema{} | |
msgSchema.SchemaProps.Type = []string{"object"} | |
msgProperties := make(map[string]spec.Schema) | |
for _, field := range message.Fields { | |
// if type is not primitive type, | |
fieldType := getType(field.Type) | |
fieldSchema := spec.Schema{} | |
// isComplex, IsArray | |
if IsComplexType(field.Type) { | |
fieldSchema = spec.Schema{} | |
if field.Repeated { | |
fieldSchema.SchemaProps.Type = []string{"array"} | |
fieldSchema.SchemaProps.Items = &spec.SchemaOrArray{ | |
Schema: &spec.Schema{ | |
SchemaProps: spec.SchemaProps{ | |
Ref: spec.MustCreateRef( | |
fmt.Sprintf("#/definitions/%s", fieldType), | |
), | |
}, | |
}, | |
} | |
} else { | |
fieldSchema.SchemaProps = spec.SchemaProps{ | |
Ref: spec.MustCreateRef( | |
fmt.Sprintf("#/definitions/%s", fieldType), | |
), | |
} | |
} | |
} else { | |
fieldSchema.SchemaProps.Type = []string{fieldType} | |
if field.Repeated { | |
fieldSchema.SchemaProps.Type = []string{"array"} | |
fieldSchema.SchemaProps.Items = &spec.SchemaOrArray{ | |
Schema: &spec.Schema{ | |
SchemaProps: spec.SchemaProps{ | |
Type: []string{fieldType}, | |
}, | |
}, | |
} | |
} | |
} | |
msgProperties[field.Name] = fieldSchema | |
} | |
msgSchema.SchemaProps.Properties = msgProperties | |
definitions[message.Name] = msgSchema | |
} | |
swagger.Definitions = definitions | |
} | |
// create method def | |
func getLocalIp() string { | |
addrs, err := net.InterfaceAddrs() | |
if err != nil { | |
fmt.Println(err) | |
return "" | |
} | |
for _, addr := range addrs { | |
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { | |
if ipnet.IP.To4() != nil { | |
return ipnet.IP.String() | |
} | |
} | |
} | |
return "" | |
} | |
func addSecurityDefinitions(api *spec.Swagger) { | |
apiKeyAuth := spec.SecurityScheme{ | |
SecuritySchemeProps: spec.SecuritySchemeProps{ | |
Type: "apiKey", | |
Description: "Authentication using bearer token.", | |
In: "header", | |
Name: "token", | |
}, | |
} | |
api.SecurityDefinitions = map[string]*spec.SecurityScheme{ | |
"bearerAuth": &apiKeyAuth, | |
} | |
api.Security = []map[string][]string{{ | |
"bearerAuth": {}, | |
}} | |
} | |
func createSwagger(doc Document) spec.Swagger { | |
localIP := getLocalIp() | |
swagger := spec.Swagger{ | |
SwaggerProps: spec.SwaggerProps{ | |
Swagger: "2.0", | |
Consumes: []string{"application/json"}, | |
Produces: []string{"application/json"}, | |
Info: &spec.Info{ | |
InfoProps: spec.InfoProps{ | |
Title: protodoc.Name, | |
Description: protodoc.Name + " api", | |
Version: "1.0.0", | |
}, | |
}, | |
Host: localIP + ":8888", | |
BasePath: "/", | |
Schemes: []string{"http"}, | |
}, | |
} | |
addSecurityDefinitions(&swagger) | |
createSchemaDef(&swagger, doc) | |
pathDict := make(map[string]spec.PathItem) | |
for _, message := range doc.Messages { | |
if strings.HasSuffix(message.Name, "Req") { | |
method := message.Name[:len(message.Name)-3] | |
reqSchema := spec.Schema{ | |
SchemaProps: spec.SchemaProps{ | |
Ref: spec.MustCreateRef( | |
fmt.Sprintf("#/definitions/%s", message.Name), | |
), | |
}, | |
} | |
respSchema := spec.Schema{ | |
SchemaProps: spec.SchemaProps{ | |
Ref: spec.MustCreateRef( | |
fmt.Sprintf("#/definitions/%sResp", method), | |
), | |
}, | |
} | |
methodPath := methodNameToPath(method) // TODO: to sna | |
pathDict[fmt.Sprintf("/%s", methodPath)] = spec.PathItem{ | |
PathItemProps: spec.PathItemProps{ | |
Post: &spec.Operation{ | |
OperationProps: spec.OperationProps{ | |
Produces: []string{ | |
"application/json", | |
}, | |
Summary: method, | |
ID: method, | |
Parameters: []spec.Parameter{{ | |
ParamProps: spec.ParamProps{Name: "body", In: "body", Schema: &reqSchema}, | |
}, | |
}, | |
Responses: &spec.Responses{ | |
ResponsesProps: spec.ResponsesProps{ | |
StatusCodeResponses: map[int]spec.Response{ | |
200: { | |
ResponseProps: spec.ResponseProps{ | |
Schema: &respSchema, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
} | |
} | |
} | |
swagger.Paths = &spec.Paths{ | |
Paths: pathDict, | |
} | |
return swagger | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment