github.com/ronaksoft/rony@v0.16.26-0.20230807065236-1743dbfe6959/internal/codegen/arg.go (about) 1 package codegen 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/ronaksoft/rony" 8 "github.com/ronaksoft/rony/tools" 9 "google.golang.org/protobuf/compiler/protogen" 10 "google.golang.org/protobuf/proto" 11 "google.golang.org/protobuf/reflect/protoreflect" 12 "google.golang.org/protobuf/types/descriptorpb" 13 ) 14 15 /* 16 Creation Time: 2021 - Jul - 07 17 Created by: (ehsan) 18 Maintainers: 19 1. Ehsan N. Moosa (E2) 20 Auditor: Ehsan N. Moosa (E2) 21 Copyright Ronak Software Group 2020 22 */ 23 24 type TextCase string 25 26 const ( 27 None TextCase = "" 28 CamelCase TextCase = "CC" 29 LowerCamelCase TextCase = "LCC" 30 KebabCase TextCase = "KC" 31 SnakeCase TextCase = "SC" 32 ) 33 34 type Language string 35 36 const ( 37 LangGo Language = "GO" 38 LangCQL Language = "CQL" 39 LangProto Language = "PROTO" 40 ) 41 42 type TemplateArg struct { 43 Messages []MessageArg 44 Services []ServiceArg 45 } 46 47 func GenTemplateArg(f *protogen.File) *TemplateArg { 48 arg := &TemplateArg{} 49 for _, m := range f.Messages { 50 arg.Messages = append(arg.Messages, GetMessageArg(m).With(f)) 51 } 52 for _, s := range f.Services { 53 arg.Services = append(arg.Services, getServiceArg(s).With(f)) 54 } 55 56 return arg 57 } 58 59 // MessageArg holds the data needed by the template engine to generate code based on the protogen.Message 60 type MessageArg struct { 61 file *protogen.File 62 desc *protogen.Message 63 name string 64 pkg string 65 Fields []FieldArg 66 C uint64 67 ImportPath protogen.GoImportPath 68 Comments string 69 70 // If message is representing a model then following parameters are filled 71 IsAggregate bool 72 IsSingleton bool 73 GlobalRepo string 74 LocalRepo string 75 Table *ModelKey 76 TableExtra []Prop 77 Views []*ModelKey 78 } 79 80 func GetMessageArg(m *protogen.Message) MessageArg { 81 arg := MessageArg{ 82 desc: m, 83 } 84 arg.name = string(m.Desc.Name()) 85 arg.pkg = string(m.Desc.ParentFile().Package()) 86 arg.C = CrcHash([]byte(m.Desc.Name())) 87 arg.ImportPath = m.GoIdent.GoImportPath 88 for _, f := range m.Fields { 89 arg.Fields = append(arg.Fields, getFieldArg(f)) 90 } 91 for _, c := range m.Comments.LeadingDetached { 92 arg.Comments += c.String() + "\r\n" 93 } 94 95 // Generate the aggregate description from proto options 96 opt, _ := m.Desc.Options().(*descriptorpb.MessageOptions) 97 modelOpt := proto.GetExtension(opt, rony.E_RonyModel).(*rony.ModelOpt) 98 if modelOpt == nil { 99 return arg 100 } 101 102 arg.GlobalRepo = strings.ToLower(modelOpt.GetGlobalDatasource()) 103 arg.LocalRepo = strings.ToLower(modelOpt.GetLocalDatasource()) 104 arg.IsSingleton = modelOpt.GetSingleton() 105 106 if arg.IsSingleton { 107 // if message is going to be singleton then it could not be aggregate 108 return arg 109 } 110 111 // If there is no table defined then it is not an aggregate 112 if modelOpt.Table == nil { 113 return arg 114 } 115 116 arg.IsAggregate = true 117 arg.parsePrimaryKeyOpt(m) 118 119 return arg 120 } 121 122 func (ma *MessageArg) parsePrimaryKeyOpt(m *protogen.Message) { 123 opt, _ := m.Desc.Options().(*descriptorpb.MessageOptions) 124 modelOpt := proto.GetExtension(opt, rony.E_RonyModel).(*rony.ModelOpt) 125 tablePK := modelOpt.Table 126 viewPKs := modelOpt.View 127 128 // Generate Go and CQL kinds of the fields 129 cqlTypes := map[string]string{} 130 goTypes := map[string]string{} 131 protoTypes := map[string]string{} 132 uniqueView := map[string]*ModelKey{} 133 extraProp := map[string]Prop{} 134 for _, f := range m.Fields { 135 protoTypes[f.GoName] = f.Desc.Kind().String() 136 cqlTypes[f.GoName] = CqlKind(f.Desc) 137 goTypes[f.GoName] = GoKind(f.Desc) 138 } 139 140 // Fill Table's ModelKey 141 ma.Table = &ModelKey{ 142 Arg: ma, 143 alias: tablePK.GetAlias(), 144 } 145 for _, k := range tablePK.PartKey { 146 ma.Table.pks = append(ma.Table.pks, Prop{ 147 Name: k, 148 ProtoType: protoTypes[k], 149 CqlType: cqlTypes[k], 150 GoType: goTypes[k], 151 Order: "", 152 }) 153 } 154 for _, k := range tablePK.SortKey { 155 order := ASC 156 if strings.HasPrefix(k, "-") { 157 order = DESC 158 } 159 160 k = strings.TrimLeft(k, "-") 161 ma.Table.cks = append(ma.Table.cks, Prop{ 162 Name: k, 163 ProtoType: protoTypes[k], 164 CqlType: cqlTypes[k], 165 GoType: goTypes[k], 166 Order: order, 167 }) 168 } 169 170 // Fill Views' ModelKey 171 for _, v := range viewPKs { 172 view := &ModelKey{ 173 Arg: ma, 174 alias: v.GetAlias(), 175 } 176 for _, k := range v.PartKey { 177 p := Prop{ 178 Name: k, 179 ProtoType: protoTypes[k], 180 CqlType: cqlTypes[k], 181 GoType: goTypes[k], 182 Order: "", 183 } 184 if !ma.Table.HasProp(k) { 185 extraProp[k] = p 186 } 187 view.pks = append(view.pks, p) 188 } 189 for _, k := range v.SortKey { 190 order := ASC 191 if strings.HasPrefix(k, "-") { 192 order = DESC 193 } 194 k = strings.TrimLeft(k, "-") 195 p := Prop{ 196 Name: k, 197 ProtoType: protoTypes[k], 198 CqlType: cqlTypes[k], 199 GoType: goTypes[k], 200 Order: order, 201 } 202 if !ma.Table.HasProp(k) { 203 extraProp[k] = p 204 } 205 view.cks = append(view.cks, p) 206 } 207 if oldView, ok := uniqueView[view.Names(PropFilterPKs, "", "", "", None)]; ok { 208 view.index = oldView.index + 1 209 } 210 uniqueView[view.Names(PropFilterPKs, "", "", "", None)] = view 211 ma.Views = append(ma.Views, view) 212 } 213 for _, p := range extraProp { 214 ma.TableExtra = append(ma.TableExtra, p) 215 } 216 } 217 218 func (ma *MessageArg) currentPkg() string { 219 var pkg string 220 if ma.file != nil { 221 pkg = string(ma.file.GoPackageName) 222 } 223 224 return pkg 225 } 226 227 func (ma MessageArg) Name() string { 228 return ma.name 229 } 230 231 func (ma MessageArg) NameCC() string { 232 return tools.ToLowerCamel(ma.name) 233 } 234 235 func (ma MessageArg) NameKC() string { 236 return tools.ToKebab(ma.name) 237 } 238 239 func (ma MessageArg) NameSC() string { 240 return tools.ToSnake(ma.name) 241 } 242 243 func (ma MessageArg) Pkg() string { 244 if ma.currentPkg() == ma.pkg { 245 return "" 246 } 247 248 return ma.pkg 249 } 250 251 func (ma MessageArg) ViewsByPK() map[string][]*ModelKey { 252 var res = map[string][]*ModelKey{} 253 for _, v := range ma.Views { 254 k := v.Names(PropFilterPKs, "", "", "", None) 255 res[k] = append(res[k], v) 256 } 257 258 return res 259 } 260 261 func (ma MessageArg) Fullname() string { 262 if ma.pkg == ma.currentPkg() { 263 return ma.name 264 } else { 265 return fmt.Sprintf("%s.%s", ma.pkg, ma.name) 266 } 267 } 268 269 func (ma MessageArg) CName() string { 270 return fmt.Sprintf("C_%s", ma.name) 271 } 272 273 func (ma MessageArg) With(f *protogen.File) MessageArg { 274 ma.file = f 275 for idx := range ma.Fields { 276 ma.Fields[idx] = ma.Fields[idx].With(f) 277 } 278 279 return ma 280 } 281 282 func (ma MessageArg) IsEnvelope() bool { 283 opt := ma.Options() 284 if opt == nil { 285 return false 286 } 287 288 return proto.GetExtension(opt, rony.E_RonyEnvelope).(bool) 289 } 290 291 func (ma MessageArg) SkipJson() bool { 292 opt := ma.Options() 293 if opt == nil { 294 return false 295 } 296 297 return proto.GetExtension(opt, rony.E_RonySkipJson).(bool) 298 } 299 300 func (ma MessageArg) Options() *descriptorpb.MessageOptions { 301 opt, _ := ma.desc.Desc.Options().(*descriptorpb.MessageOptions) 302 303 return opt 304 } 305 306 // FieldArg holds the data needed by the template engine to generate code based on the protogen.Field 307 type FieldArg struct { 308 file *protogen.File 309 desc protoreflect.FieldDescriptor 310 name string 311 pkg string 312 ImportPath protogen.GoImportPath 313 ZeroValue string 314 Kind string 315 ProtoKind protoreflect.Kind 316 ProtoCardinality protoreflect.Cardinality 317 GoKind string 318 CqlKind string 319 Cardinality string 320 HasIndex bool 321 HelpText string 322 DefaultValue string 323 } 324 325 func getFieldArg(f *protogen.Field) FieldArg { 326 arg := FieldArg{ 327 desc: f.Desc, 328 } 329 330 arg.name = f.GoName 331 if f.Message != nil { 332 arg.pkg = string(f.Message.Desc.ParentFile().Package()) 333 arg.ImportPath = f.Message.GoIdent.GoImportPath 334 } else { 335 arg.pkg = string(f.Desc.ParentFile().Package()) 336 } 337 338 arg.Kind = f.Desc.Kind().String() 339 arg.ProtoKind = f.Desc.Kind() 340 arg.ProtoCardinality = f.Desc.Cardinality() 341 arg.GoKind = GoKind(f.Desc) 342 arg.CqlKind = CqlKind(f.Desc) 343 arg.Cardinality = f.Desc.Cardinality().String() 344 arg.ZeroValue = ZeroValue(f.Desc) 345 346 opt, _ := f.Desc.Options().(*descriptorpb.FieldOptions) 347 arg.HasIndex = proto.GetExtension(opt, rony.E_RonyIndex).(bool) 348 arg.HelpText = proto.GetExtension(opt, rony.E_RonyHelp).(string) 349 arg.DefaultValue = proto.GetExtension(opt, rony.E_RonyDefault).(string) 350 351 return arg 352 } 353 354 func (fa FieldArg) currentPkg() string { 355 var pkg string 356 if fa.file != nil { 357 pkg = string(fa.file.GoPackageName) 358 } 359 360 return pkg 361 } 362 363 func (fa FieldArg) Name() string { 364 return fa.name 365 } 366 367 func (fa FieldArg) NameCC() string { 368 return tools.ToLowerCamel(fa.name) 369 } 370 371 func (fa FieldArg) NameKC() string { 372 return tools.ToKebab(fa.name) 373 } 374 375 func (fa FieldArg) NameSC() string { 376 return tools.ToSnake(fa.name) 377 } 378 379 func (fa FieldArg) JSONName() string { 380 return fa.desc.JSONName() 381 } 382 383 func (fa FieldArg) DescName() string { 384 return string(fa.desc.Name()) 385 } 386 387 func (fa FieldArg) Type() string { 388 if fa.desc.Message() != nil { 389 return string(fa.desc.Message().Name()) 390 } 391 392 return fa.GoKind 393 } 394 395 func (fa FieldArg) Pkg() string { 396 if fa.currentPkg() == fa.pkg { 397 return "" 398 } 399 400 return fa.pkg 401 } 402 403 func (fa FieldArg) With(f *protogen.File) FieldArg { 404 fa.file = f 405 406 return fa 407 } 408 409 func (fa FieldArg) Options() *descriptorpb.FieldOptions { 410 opt, _ := fa.desc.Options().(*descriptorpb.FieldOptions) 411 412 return opt 413 } 414 415 // ServiceArg holds the data needed by the template engine to generate code based on the protogen.Service 416 type ServiceArg struct { 417 file *protogen.File 418 desc protoreflect.ServiceDescriptor 419 name string 420 C uint64 421 Methods []MethodArg 422 Comments string 423 HasRestProxy bool 424 } 425 426 func (sa ServiceArg) currentPkg() string { 427 var pkg string 428 if sa.file != nil { 429 pkg = string(sa.file.GoPackageName) 430 } 431 432 return pkg 433 } 434 435 func (sa ServiceArg) Name() string { 436 return sa.name 437 } 438 439 func (sa ServiceArg) NameCC() string { 440 return tools.ToLowerCamel(sa.name) 441 } 442 443 func (sa ServiceArg) NameKC() string { 444 return tools.ToKebab(sa.name) 445 } 446 447 func (sa ServiceArg) NameSC() string { 448 return tools.ToSnake(sa.name) 449 } 450 451 func (sa ServiceArg) With(f *protogen.File) ServiceArg { 452 sa.file = f 453 for idx := range sa.Methods { 454 sa.Methods[idx] = sa.Methods[idx].With(sa.file) 455 } 456 457 return sa 458 } 459 460 func (sa ServiceArg) Options() *descriptorpb.ServiceOptions { 461 opt, _ := sa.desc.Options().(*descriptorpb.ServiceOptions) 462 463 return opt 464 } 465 466 func getServiceArg(s *protogen.Service) ServiceArg { 467 arg := ServiceArg{ 468 desc: s.Desc, 469 } 470 arg.name = string(s.Desc.Name()) 471 arg.C = CrcHash([]byte(arg.name)) 472 for _, c := range s.Comments.LeadingDetached { 473 arg.Comments += c.String() + "\r\n" 474 } 475 476 for _, m := range s.Methods { 477 ma := getMethodArg(s, m) 478 if ma.RestEnabled { 479 arg.HasRestProxy = true 480 } 481 if arg.currentPkg() != "" && arg.currentPkg() != ma.Input.pkg { 482 panic("input must be with in the same package of its service") 483 } 484 arg.Methods = append(arg.Methods, ma) 485 } 486 487 return arg 488 } 489 490 // MethodArg holds the data needed by the template engine to generate code based on the protogen.Method 491 type MethodArg struct { 492 desc protoreflect.MethodDescriptor 493 file *protogen.File 494 name string 495 fullname string 496 C uint64 497 Comments string 498 Input MessageArg 499 Output MessageArg 500 RestEnabled bool 501 TunnelOnly bool 502 Rest RestArg 503 } 504 505 type RestArg struct { 506 Method string 507 Path string 508 Json bool 509 Unmarshal bool 510 ExtraCode []string 511 PathParams map[string]protoreflect.Kind 512 QueryParams map[string]protoreflect.Kind 513 } 514 515 func getRestArg(m *protogen.Method, arg *MethodArg) { 516 opt, _ := m.Desc.Options().(*descriptorpb.MethodOptions) 517 restOpt := proto.GetExtension(opt, rony.E_RonyRest).(*rony.RestOpt) 518 if restOpt == nil { 519 return 520 } 521 522 arg.RestEnabled = true 523 arg.Rest.Method = restOpt.GetMethod() 524 arg.Rest.Path = fmt.Sprintf("/%s", strings.Trim(restOpt.GetPath(), "/")) 525 arg.Rest.Json = restOpt.GetJsonEncode() 526 527 var ( 528 pathParams = make([]string, 0, 8) 529 queryParams = make([]string, 0, 8) 530 ) 531 bindParams := map[string]string{} 532 for _, pv := range strings.Split(arg.Rest.Path, "/") { 533 if !strings.HasPrefix(pv, ":") { 534 continue 535 } 536 pathParam := strings.TrimLeft(pv, ":") 537 pathParams = append(pathParams, pathParam) 538 bindParams[pathParam] = pathParam 539 } 540 for _, bv := range restOpt.GetBindPathParam() { 541 parts := strings.SplitN(strings.TrimSpace(bv), "=", 2) 542 if len(parts) == 2 { 543 bindParams[parts[0]] = parts[1] 544 } 545 } 546 for _, bv := range restOpt.GetBindQueryParam() { 547 parts := strings.SplitN(strings.TrimSpace(bv), "=", 2) 548 if len(parts) == 2 { 549 bindParams[parts[0]] = parts[1] 550 queryParams = append(queryParams, strings.TrimSpace(parts[0])) 551 } 552 } 553 554 if len(arg.Input.Fields) > len(bindParams) { 555 arg.Rest.Unmarshal = true 556 } 557 558 for _, paramName := range pathParams { 559 fieldName := bindParams[paramName] 560 for _, f := range m.Input.Fields { 561 if ec := getExtraCode(f, fieldName, paramName); ec != "" { 562 arg.Rest.ExtraCode = append(arg.Rest.ExtraCode, ec) 563 arg.Rest.PathParams[paramName] = f.Desc.Kind() 564 } 565 } 566 } 567 568 for _, paramName := range queryParams { 569 fieldName := bindParams[paramName] 570 for _, f := range m.Input.Fields { 571 if ec := getExtraCode(f, fieldName, paramName); ec != "" { 572 arg.Rest.ExtraCode = append(arg.Rest.ExtraCode, ec) 573 arg.Rest.QueryParams[paramName] = f.Desc.Kind() 574 } 575 } 576 } 577 578 return 579 } 580 581 func getExtraCode(f *protogen.Field, fieldName, paramName string) (ec string) { 582 if string(f.Desc.Name()) != fieldName { 583 return 584 } 585 586 switch f.Desc.Kind() { 587 case protoreflect.Int64Kind, protoreflect.Sfixed64Kind, protoreflect.Sint64Kind: 588 ec = fmt.Sprint( 589 "req.", 590 f.GoName, "= tools.StrToInt64(tools.GetString(conn.Get(\"", paramName, "\"), \"0\"))", 591 ) 592 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 593 ec = fmt.Sprint( 594 "req.", 595 f.GoName, "= tools.StrToUInt64(tools.GetString(conn.Get(\"", paramName, "\"), \"0\"))", 596 ) 597 case protoreflect.Int32Kind, protoreflect.Sfixed32Kind, protoreflect.Sint32Kind: 598 ec = fmt.Sprint( 599 "req.", 600 f.GoName, "= tools.StrToInt32(tools.GetString(conn.Get(\"", paramName, "\"), \"0\"))", 601 ) 602 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 603 ec = fmt.Sprint( 604 "req.", 605 f.GoName, "= tools.StrToUInt32(tools.GetString(conn.Get(\"", paramName, "\"), \"0\"))", 606 ) 607 case protoreflect.StringKind: 608 ec = fmt.Sprint( 609 "req.", 610 f.GoName, "= tools.GetString(conn.Get(\"", paramName, "\"), \"\")", 611 ) 612 case protoreflect.BytesKind: 613 ec = fmt.Sprint( 614 "req.", 615 f.GoName, "= tools.S2B(tools.GetString(conn.Get(\"", paramName, "\"), \"\"))", 616 ) 617 case protoreflect.DoubleKind: 618 ec = fmt.Sprint( 619 "req.", 620 f.GoName, "= tools.StrToFloat32(tools.GetString(conn.Get(\"", paramName, "\"), \"0\"))", 621 ) 622 default: 623 ec = "" 624 } 625 626 return 627 } 628 629 func (ma MethodArg) Fullname() string { 630 return ma.fullname 631 } 632 633 func (ma MethodArg) Name() string { 634 return ma.name 635 } 636 637 func (ma MethodArg) NameCC() string { 638 return tools.ToLowerCamel(ma.name) 639 } 640 641 func (ma MethodArg) NameKC() string { 642 return tools.ToKebab(ma.name) 643 } 644 645 func (ma MethodArg) NameSC() string { 646 return tools.ToSnake(ma.name) 647 } 648 649 func (ma MethodArg) With(f *protogen.File) MethodArg { 650 ma.file = f 651 ma.Input = ma.Input.With(f) 652 ma.Output = ma.Output.With(f) 653 654 return ma 655 } 656 657 func (ma MethodArg) Options() *descriptorpb.MethodOptions { 658 opt, _ := ma.desc.Options().(*descriptorpb.MethodOptions) 659 660 return opt 661 } 662 663 func getMethodArg(s *protogen.Service, m *protogen.Method) MethodArg { 664 arg := MethodArg{ 665 desc: m.Desc, 666 Rest: RestArg{ 667 Method: "", 668 Path: "", 669 Json: false, 670 PathParams: map[string]protoreflect.Kind{}, 671 QueryParams: map[string]protoreflect.Kind{}, 672 }, 673 } 674 arg.fullname = fmt.Sprintf("%s%s", s.Desc.Name(), m.Desc.Name()) 675 arg.name = string(m.Desc.Name()) 676 arg.C = CrcHash([]byte(fmt.Sprintf("%s%s", s.Desc.Name(), m.Desc.Name()))) 677 arg.Input = GetMessageArg(m.Input) 678 arg.Output = GetMessageArg(m.Output) 679 for _, c := range m.Comments.LeadingDetached { 680 arg.Comments += c.String() + "\r\n" 681 } 682 683 if arg.Input.IsSingleton || arg.Input.IsAggregate { 684 panic("method input cannot be aggregate or singleton") 685 } 686 if arg.Output.IsSingleton || arg.Output.IsAggregate { 687 panic("method output cannot be aggregate or singleton") 688 } 689 690 opt, _ := m.Desc.Options().(*descriptorpb.MethodOptions) 691 getRestArg(m, &arg) 692 693 arg.TunnelOnly = proto.GetExtension(opt, rony.E_RonyInternal).(bool) 694 695 return arg 696 }