Skip to content

Instantly share code, notes, and snippets.

@swuecho
Created June 8, 2023 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swuecho/73a4a722f0b8a27b1eff0f219afa83c9 to your computer and use it in GitHub Desktop.
Save swuecho/73a4a722f0b8a27b1eff0f219afa83c9 to your computer and use it in GitHub Desktop.
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