github.com/ronaksoft/rony@v0.16.26-0.20230807065236-1743dbfe6959/cmd/protoc-gen-gorony/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "path/filepath" 7 "regexp" 8 "strings" 9 "text/template" 10 11 "github.com/go-openapi/spec" 12 "github.com/ronaksoft/rony/cmd/protoc-gen-gorony/helper" 13 "github.com/ronaksoft/rony/cmd/protoc-gen-gorony/repo" 14 "github.com/ronaksoft/rony/cmd/protoc-gen-gorony/rpc" 15 "github.com/ronaksoft/rony/internal/codegen" 16 "google.golang.org/protobuf/compiler/protogen" 17 "google.golang.org/protobuf/reflect/protoreflect" 18 ) 19 20 var ( 21 pluginOpt = &codegen.PluginOptions{} 22 pgo = protogen.Options{ 23 ParamFunc: pluginOpt.ParamFunc, 24 ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { 25 // TODO:: this is a hack for bug in Golang/Protobuf which does not support go module versions 26 switch path { 27 case "github.com/scylladb/gocqlx": 28 return "github.com/scylladb/gocqlx/v2" 29 case "go.opentelemetry.io/otel/semconv": 30 return "go.opentelemetry.io/otel/semconv/v1.7.0" 31 } 32 33 return path 34 }, 35 } 36 pathRegEx = regexp.MustCompile(``) 37 ) 38 39 func main() { 40 pgo.Run( 41 func(plugin *protogen.Plugin) error { 42 if pluginOpt.CRC32 { 43 codegen.CrcBits = 32 44 } 45 46 switch pluginOpt.ConstructorFormat { 47 case codegen.StringJSON: 48 return jsonStr(plugin) 49 case codegen.Int64JSON: 50 return jsonInt(plugin) 51 } 52 53 if pluginOpt.OpenAPI { 54 return exportOpenAPI(plugin) 55 } 56 57 if pluginOpt.ExportCleanProto { 58 return clearRonyTags(plugin) 59 } 60 61 err := normalMode(plugin) 62 if err != nil { 63 return err 64 } 65 66 return nil 67 }, 68 ) 69 } 70 71 func normalMode(plugin *protogen.Plugin) error { 72 protocVer := plugin.Request.GetCompilerVersion() 73 for _, protoFile := range plugin.Files { 74 if !protoFile.Generate { 75 continue 76 } 77 78 // Create the generator func 79 generatedFile := plugin.NewGeneratedFile( 80 fmt.Sprintf("%s.rony.go", protoFile.GeneratedFilenamePrefix), protoFile.GoImportPath, 81 ) 82 generatedFile.P("// Code generated by Rony's protoc plugin; DO NOT EDIT.") 83 generatedFile.P( 84 "// ProtoC ver. v", 85 protocVer.GetMajor(), ".", protocVer.GetMinor(), ".", protocVer.GetPatch(), 86 ) 87 generatedFile.P("// Rony ver. ", codegen.Version) 88 generatedFile.P("// Source: ", protoFile.Proto.GetName()) 89 generatedFile.P() 90 91 // Generate all the helper functions 92 _ = helper.GenFunc(generatedFile, pluginOpt, protoFile) 93 94 // Generate rpc helper functions (Server, Client and CLI) 95 _ = rpc.GenFunc(generatedFile, pluginOpt, protoFile) 96 97 // Generate Repository functionalities 98 g3 := repo.New(plugin, protoFile, generatedFile) 99 g3.Generate() 100 } 101 102 return nil 103 } 104 105 func jsonStr(plugin *protogen.Plugin) error { 106 var ( 107 importPath protogen.GoImportPath 108 filePrefix string 109 cn = map[string]uint64{} 110 cs = map[uint64]string{} 111 ) 112 for _, f := range plugin.Files { 113 if !f.Generate { 114 continue 115 } 116 importPath = f.GoImportPath 117 filePrefix = f.GeneratedFilenamePrefix 118 // reset the global model and fill with the new data 119 for _, mt := range f.Messages { 120 constructor := codegen.CrcHash([]byte(mt.Desc.Name())) 121 cn[string(mt.Desc.Name())] = constructor 122 cs[constructor] = string(mt.Desc.Name()) 123 } 124 for _, s := range f.Services { 125 for _, m := range s.Methods { 126 methodName := fmt.Sprintf("%s%s", s.Desc.Name(), m.Desc.Name()) 127 constructor := codegen.CrcHash([]byte(methodName)) 128 cn[methodName] = constructor 129 cs[constructor] = methodName 130 } 131 } 132 } 133 134 t := template.Must(template.New("t1").Parse(` 135 { 136 "ConstructorsByName": { 137 {{range $k,$v := .}} "{{$k}}": "{{$v}}", 138 {{end -}} 139 }, 140 "ConstructorsByValue": { 141 {{range $k,$v := .}} "{{$v}}": "{{$k}}", 142 {{end -}} 143 } 144 } 145 `)) 146 147 out := &bytes.Buffer{} 148 err := t.Execute(out, cn) 149 if err != nil { 150 panic(err) 151 } 152 153 gf := plugin.NewGeneratedFile(filepath.Join(filepath.Dir(filePrefix), "constructors.json"), importPath) 154 _, err = gf.Write(out.Bytes()) 155 156 return err 157 } 158 159 func jsonInt(plugin *protogen.Plugin) error { 160 var ( 161 importPath protogen.GoImportPath 162 filePrefix string 163 cn = map[string]int64{} 164 cs = map[int64]string{} 165 ) 166 for _, f := range plugin.Files { 167 if !f.Generate { 168 continue 169 } 170 importPath = f.GoImportPath 171 filePrefix = f.GeneratedFilenamePrefix 172 // reset the global model and fill with the new data 173 for _, mt := range f.Messages { 174 constructor := int64(codegen.CrcHash([]byte(mt.Desc.Name()))) 175 cn[string(mt.Desc.Name())] = constructor 176 cs[constructor] = string(mt.Desc.Name()) 177 } 178 for _, s := range f.Services { 179 for _, m := range s.Methods { 180 methodName := fmt.Sprintf("%s%s", s.Desc.Name(), m.Desc.Name()) 181 constructor := int64(codegen.CrcHash([]byte(methodName))) 182 cn[methodName] = constructor 183 cs[constructor] = methodName 184 } 185 } 186 } 187 188 t := template.Must(template.New("t1").Parse(` 189 { 190 "ConstructorsByName": { 191 {{range $k,$v := .}} "{{$k}}": "{{$v}}", 192 {{end -}} 193 }, 194 "ConstructorsByValue": { 195 {{range $k,$v := .}} "{{$v}}": "{{$k}}", 196 {{end -}} 197 } 198 } 199 `)) 200 201 out := &bytes.Buffer{} 202 err := t.Execute(out, cn) 203 if err != nil { 204 panic(err) 205 } 206 207 gf := plugin.NewGeneratedFile(filepath.Join(filepath.Dir(filePrefix), "constructors.json"), importPath) 208 _, err = gf.Write(out.Bytes()) 209 210 return err 211 } 212 213 func exportOpenAPI(plugin *protogen.Plugin) error { 214 var ( 215 importPath protogen.GoImportPath 216 filePrefix string 217 ) 218 swag := &spec.Swagger{} 219 swag.Info = &spec.Info{ 220 InfoProps: spec.InfoProps{ 221 Description: "Rony Swagger Service Description", 222 Title: "Rony", 223 Version: "0.0.1", 224 }, 225 } 226 swag.Schemes = []string{"http", "https"} 227 swag.Swagger = "2.0" 228 229 for _, protoFile := range plugin.Files { 230 if !protoFile.Generate || protoFile.Proto.GetPackage() == "google.protobuf" { 231 continue 232 } 233 importPath = protoFile.GoImportPath 234 filePrefix = protoFile.GeneratedFilenamePrefix 235 236 arg := codegen.GenTemplateArg(protoFile) 237 for _, s := range arg.Services { 238 addTag(swag, s) 239 for _, m := range s.Methods { 240 if !m.RestEnabled { 241 continue 242 } 243 addOperation(swag, s, m) 244 } 245 } 246 for _, m := range arg.Messages { 247 addDefinition(swag, m, true) 248 } 249 } 250 251 out, err := swag.MarshalJSON() 252 if err != nil { 253 return err 254 } 255 256 gf := plugin.NewGeneratedFile(filepath.Join(filepath.Dir(filePrefix), "swagger.json"), importPath) 257 258 _, err = gf.Write(out) 259 260 return err 261 } 262 func addTag(swag *spec.Swagger, s codegen.ServiceArg) { 263 swag.Tags = append( 264 swag.Tags, 265 spec.NewTag(s.Name(), s.Comments, nil), 266 ) 267 } 268 func addDefinition(swag *spec.Swagger, m codegen.MessageArg, jsonEncode bool) { 269 if swag.Definitions == nil { 270 swag.Definitions = map[string]spec.Schema{} 271 } 272 273 def := spec.Schema{} 274 def.Description = m.Comments 275 def.Typed("object", "") 276 for _, f := range m.Fields { 277 fName := f.DescName() 278 if jsonEncode { 279 fName = f.JSONName() 280 } 281 282 var wrapFunc func(schema *spec.Schema) spec.Schema 283 switch f.ProtoCardinality { 284 case protoreflect.Repeated: 285 wrapFunc = func(item *spec.Schema) spec.Schema { 286 return *spec.ArrayProperty(item) 287 } 288 default: 289 wrapFunc = func(schema *spec.Schema) spec.Schema { 290 return *schema 291 } 292 } 293 switch f.ProtoKind { 294 case protoreflect.StringKind: 295 def.SetProperty(fName, wrapFunc(spec.StringProperty())) 296 case protoreflect.BytesKind: 297 def.SetProperty(fName, wrapFunc(spec.ArrayProperty(spec.Int8Property()))) 298 case protoreflect.Int32Kind, protoreflect.Uint32Kind, 299 protoreflect.Sint32Kind, protoreflect.Fixed32Kind: 300 def.SetProperty(fName, wrapFunc(spec.Int32Property())) 301 case protoreflect.Int64Kind, protoreflect.Uint64Kind, 302 protoreflect.Sint64Kind, protoreflect.Fixed64Kind: 303 def.SetProperty(fName, wrapFunc(spec.Int64Property())) 304 case protoreflect.FloatKind: 305 def.SetProperty(fName, wrapFunc(spec.Float32Property())) 306 case protoreflect.DoubleKind: 307 def.SetProperty(fName, wrapFunc(spec.Float64Property())) 308 case protoreflect.EnumKind: 309 def.SetProperty(fName, wrapFunc(spec.Int32Property())) 310 case protoreflect.MessageKind: 311 def.SetProperty(fName, wrapFunc(spec.RefProperty(fmt.Sprintf("#/definitions/%s", f.Type())))) 312 default: 313 def.SetProperty(fName, wrapFunc(spec.StringProperty())) 314 } 315 } 316 317 swag.Definitions[m.Name()] = def 318 } 319 func addOperation(swag *spec.Swagger, s codegen.ServiceArg, m codegen.MethodArg) { 320 if swag.Paths == nil { 321 swag.Paths = &spec.Paths{ 322 Paths: map[string]spec.PathItem{}, 323 } 324 } 325 326 opID := fmt.Sprintf("%s%s", s.NameCC(), m.Name()) 327 op := spec.NewOperation(opID). 328 RespondsWith(200, 329 spec.NewResponse(). 330 WithSchema( 331 spec.RefProperty(fmt.Sprintf("#/definitions/%s", m.Output.Name())), 332 ), 333 ). 334 WithTags(s.Name()) 335 336 if m.Rest.Json { 337 op.WithProduces("application/json"). 338 WithConsumes("application/json") 339 } else { 340 op.WithProduces("application/protobuf"). 341 WithConsumes("application/protobuf") 342 } 343 344 for name, kind := range m.Rest.PathParams { 345 op.AddParam( 346 setParamType( 347 spec.PathParam(name). 348 AsRequired(). 349 NoEmptyValues(), 350 kind, 351 ), 352 ) 353 } 354 355 for name, kind := range m.Rest.QueryParams { 356 op.AddParam( 357 setParamType( 358 spec.QueryParam(name). 359 AsRequired(). 360 NoEmptyValues(), 361 kind, 362 ), 363 ) 364 } 365 366 if m.Rest.Unmarshal { 367 op.AddParam( 368 spec.BodyParam( 369 m.Input.Name(), 370 spec.RefProperty(fmt.Sprintf("#/definitions/%s", m.Input.Name())), 371 ), 372 ) 373 } 374 375 restPath := replacePath(m.Rest.Path) 376 pathItem := swag.Paths.Paths[restPath] 377 switch strings.ToLower(m.Rest.Method) { 378 case "get": 379 pathItem.Get = op 380 case "post": 381 pathItem.Post = op 382 case "put": 383 pathItem.Put = op 384 case "delete": 385 pathItem.Delete = op 386 case "patch": 387 pathItem.Patch = op 388 } 389 swag.Paths.Paths[restPath] = pathItem 390 } 391 func setParamType(p *spec.Parameter, kind protoreflect.Kind) *spec.Parameter { 392 switch kind { 393 case protoreflect.StringKind: 394 p.Typed("string", kind.String()) 395 case protoreflect.BytesKind: 396 p.Typed("array", "int8") 397 case protoreflect.DoubleKind, protoreflect.FloatKind: 398 p.Typed("number", kind.String()) 399 case protoreflect.Int32Kind, protoreflect.Sint32Kind, 400 protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 401 p.Typed("integer", "int32") 402 case protoreflect.Int64Kind, protoreflect.Sint64Kind, 403 protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 404 p.Typed("integer", "int64") 405 default: 406 p.Typed("integer", kind.String()) 407 } 408 409 return p 410 } 411 412 func replacePath(path string) string { 413 sb := strings.Builder{} 414 for idx, p := range strings.Split(path, "/") { 415 if idx > 0 { 416 sb.WriteRune('/') 417 } 418 if strings.HasPrefix(p, ":") { 419 sb.WriteRune('{') 420 sb.WriteString(p[1:]) 421 sb.WriteRune('}') 422 } else { 423 sb.WriteString(p) 424 } 425 } 426 427 return sb.String() 428 } 429 func clearRonyTags(plugin *protogen.Plugin) error { 430 for _, protoFile := range plugin.Files { 431 if !protoFile.Generate || protoFile.Proto.GetPackage() == "google.protobuf" { 432 continue 433 } 434 435 // Create the generator func 436 gFile := plugin.NewGeneratedFile( 437 fmt.Sprintf( 438 "%s.clean.proto", 439 protoFile.GeneratedFilenamePrefix, 440 ), 441 protoFile.GoImportPath, 442 ) 443 gFile.P("syntax = \"", protoFile.Proto.GetSyntax(), "\";") 444 gFile.P() 445 gFile.P("package ", protoFile.Proto.GetPackage(), ";") 446 gFile.P() 447 for _, dep := range protoFile.Proto.Dependency { 448 for _, f := range plugin.Request.FileToGenerate { 449 if f == dep { 450 gFile.P("import \"", dep, "\";") 451 } 452 } 453 } 454 for _, s := range protoFile.Services { 455 gFile.P() 456 for _, c := range s.Comments.LeadingDetached { 457 gFile.P(s.Comments.Leading, " ", c, " ", s.Comments.Trailing) 458 } 459 gFile.P("service ", s.Desc.Name(), "{") 460 for _, m := range s.Methods { 461 for _, c := range m.Comments.LeadingDetached { 462 gFile.P(m.Comments.Leading, " ", c, " ", m.Comments.Trailing) 463 } 464 gFile.P("\t rpc ", m.Desc.Name(), "(", m.Desc.Input().Name(), ") returns (", m.Desc.Output().Name(), ");") 465 } 466 gFile.P("}") 467 } 468 for _, m := range protoFile.Messages { 469 gFile.P() 470 for _, c := range m.Comments.LeadingDetached { 471 gFile.P(m.Comments.Leading, " ", c, " ", m.Comments.Trailing) 472 } 473 gFile.P("message ", m.Desc.Name(), "{") 474 for _, f := range m.Fields { 475 for _, c := range f.Comments.LeadingDetached { 476 gFile.P(f.Comments.Leading, " ", c, " ", f.Comments.Trailing) 477 } 478 switch protoFile.Proto.GetSyntax() { 479 case "proto3": 480 switch f.Desc.Cardinality() { 481 case protoreflect.Optional, protoreflect.Required: 482 switch f.Desc.Kind() { 483 case protoreflect.MessageKind: 484 gFile.P("\t", f.Desc.Message().Name(), " ", f.Desc.Name(), " = ", f.Desc.Number(), ";") 485 case protoreflect.EnumKind: 486 gFile.P("\t", f.Desc.Enum().Name(), " ", f.Desc.Name(), " = ", f.Desc.Number(), ";") 487 default: 488 gFile.P("\t", f.Desc.Kind(), " ", f.Desc.Name(), " = ", f.Desc.Number(), ";") 489 } 490 case protoreflect.Repeated: 491 switch f.Desc.Kind() { 492 case protoreflect.MessageKind: 493 gFile.P( 494 "\t", 495 f.Desc.Cardinality().String(), " ", f.Desc.Message().Name(), " ", 496 f.Desc.Name(), " = ", f.Desc.Number(), ";", 497 ) 498 case protoreflect.EnumKind: 499 gFile.P( 500 "\t", 501 f.Desc.Cardinality().String(), " ", f.Desc.Enum().Name(), " ", 502 f.Desc.Name(), " = ", f.Desc.Number(), ";", 503 ) 504 default: 505 gFile.P( 506 "\t", 507 f.Desc.Cardinality().String(), " ", f.Desc.Kind(), " ", 508 f.Desc.Name(), " = ", f.Desc.Number(), ";", 509 ) 510 } 511 } 512 case "proto2": 513 switch f.Desc.Kind() { 514 case protoreflect.MessageKind: 515 gFile.P( 516 "\t", 517 f.Desc.Cardinality().String(), " ", f.Desc.Message().Name(), " ", 518 f.Desc.Name(), " = ", f.Desc.Number(), ";", 519 ) 520 case protoreflect.EnumKind: 521 gFile.P( 522 "\t", 523 f.Desc.Cardinality().String(), " ", f.Desc.Enum().Name(), " ", 524 f.Desc.Name(), " = ", f.Desc.Number(), ";", 525 ) 526 default: 527 gFile.P( 528 "\t", 529 f.Desc.Cardinality().String(), " ", f.Desc.Kind(), " ", 530 f.Desc.Name(), " = ", f.Desc.Number(), ";", 531 ) 532 } 533 } 534 } 535 gFile.P("}") 536 } 537 for _, m := range protoFile.Enums { 538 gFile.P() 539 for _, c := range m.Comments.LeadingDetached { 540 gFile.P(m.Comments.Leading, " ", c, " ", m.Comments.Trailing) 541 } 542 gFile.P("enum ", m.Desc.Name(), "{") 543 for _, f := range m.Values { 544 gFile.P("\t", f.Desc.Name(), " = ", f.Desc.Number(), ";") 545 } 546 gFile.P("}") 547 } 548 } 549 550 return nil 551 }