diff --git a/go.mod b/go.mod index ffd413ac3..48d311b08 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.4.0 ) @@ -152,5 +153,4 @@ require ( golang.org/x/text v0.28.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/pkg/loop/cmd/genwiring/README.md b/pkg/loop/cmd/genwiring/README.md new file mode 100644 index 000000000..95d6585c4 --- /dev/null +++ b/pkg/loop/cmd/genwiring/README.md @@ -0,0 +1,20 @@ +# Tiny code generator that turns your domain model into: + + - .proto service + messages + - single Go file (rpc.go) with: + - typed gRPC Client + - typed gRPC Server (thin shim over your implementation) + - pb ↔ domain converters for every user message + - oneof (interface) adapters + - safe handling of bytes, repeated fields, and fixed-size arrays + - an optional rpc_test.go with a single bufconn server and subtests (happy-path roundtrip) +Usage example: +Suppose your interface is in pkg/path and its called MyInterface: +1. Generate proto files + go wrappers +//go:generate bash -c "set -euo pipefail; mkdir -p ./gen/pb ./gen/wrap && go run ./genwiring --pkg pkg/path --interface MyInterface --config config.yaml --service MyService --proto-pkg loop.test --proto-go-package my/proto/package --proto-out ./path/to/my.proto --go-out ./path/to/my/go/wrappers --go-pkg my/go/pkg" +2. Use protoc to generate go proto types +//go:generate bash -c "set -euo pipefail; protoc -I ./gen/pb --go_out=paths=source_relative:./gen/pb --go-grpc_out=paths=source_relative:./gen/pb ./gen/pb/service.proto" +The output will be: + - A strongly typed Client that does domain <-> pb conversions rpc.go, rpc_test.go + - A server wrapper that converts pb and calls your impl. +A full usage example with config in pkg/loop/internal/generator/testdata diff --git a/pkg/loop/cmd/genwiring/main.go b/pkg/loop/cmd/genwiring/main.go new file mode 100644 index 000000000..646eca906 --- /dev/null +++ b/pkg/loop/cmd/genwiring/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator" +) + +func main() { + var ( + pkgPath = flag.String("pkg", "", "Go package path containing the interface (module-qualified)") + ifaceName = flag.String("interface", "", "Interface name (e.g. ExampleService)") + serviceName = flag.String("service", "Example", "Proto service name (e.g. Example)") + protoOut = flag.String("proto-out", "", ".proto output path") + goOutDir = flag.String("go-out", "", "Directory for generated Go wrappers") + goPkg = flag.String("go-pkg", "", "Go package import path for generated wrappers") + protoPkg = flag.String("proto-pkg", "loop.solana", "Proto package name") + goPackageOpt = flag.String("proto-go-package", "github.com/smartcontractkit/chainlink-common/pkg/loop/solana", "option go_package value for the .proto") + configPath = flag.String("config", "", "YAML config for externals/aliases/enums") + ) + flag.Parse() + + if *pkgPath == "" || *ifaceName == "" || *protoOut == "" || *goOutDir == "" || *goPkg == "" { + log.Fatalf("missing required flags: --pkg, --interface, --proto-out, --go-out, --go-pkg") + } + + cfg, err := generator.LoadConfig(*configPath) + if err != nil { + log.Fatalf("load config: %v", err) + } + + svc, err := generator.ParseInterface(*pkgPath, *ifaceName, cfg) + if err != nil { + log.Fatalf("parse: %v", err) + } + svc.ServiceName = *serviceName + svc.ProtoPkg = *protoPkg + svc.OptionGoPackage = *goPackageOpt + svc.WrapGoPackage = *goPkg + + if err := os.MkdirAll(*goOutDir, 0o755); err != nil { + log.Fatalf("mkdir go-out: %v", err) + } + + if err := generator.RenderAll(*protoOut, *goOutDir, svc); err != nil { + log.Fatalf("render: %v", err) + } + + fmt.Printf("Generated:\n %s\n %s/server.go\n %s/client.go\n", *protoOut, *goOutDir, *goOutDir) +} diff --git a/pkg/loop/internal/generator/generator.go b/pkg/loop/internal/generator/generator.go new file mode 100644 index 000000000..80b75a276 --- /dev/null +++ b/pkg/loop/internal/generator/generator.go @@ -0,0 +1,832 @@ +package generator + +import ( + "cmp" + "fmt" + "go/types" + "os" + "path" + "slices" + "sort" + "strings" + + "golang.org/x/tools/go/packages" + "gopkg.in/yaml.v2" +) + +type Service struct { + PkgPath string + IfaceName string + ServiceName string + Methods []Method + ProtoImports []string + GoImports []string + ProtoPkg string + OptionGoPackage string + WrapGoPackage string + UserMessages []UserMessage + IfacePkgName string + ImportAliases []ImportAlias + InterfaceOneofs []InterfaceOneof +} + +type UserMessage struct { + Name string + Fields []Field + PkgPath string + DomainGo string +} + +type Method struct { + Name string + Req Message + Rep Message + HasContext bool +} + +type Message struct { + Name string + Fields []Field +} + +type Field struct { + Name string + ProtoTag int + Type ProtoType + GoType string + + DomainIsArray bool // true if domain is [N]T + DomainElemGoType string // element type of array/slice on the domain side + DomainElemIsByte bool // true if element is byte/uint8 +} + +type ProtoType struct { + Name string + IsRepeated bool +} + +type ExternalMap struct { + GoType string `yaml:"go_type"` + ProtoType string `yaml:"proto_type"` + ToProto string `yaml:"to_proto"` + FromProto string `yaml:"from_proto"` + Import string `yaml:"import"` + ProtoImport string `yaml:"proto_import"` +} + +type EnumMap struct { + GoType string `yaml:"go_type"` + ProtoType string `yaml:"proto_type"` + Values []string `yaml:"values"` +} + +type Config struct { + Externals []ExternalMap `yaml:"externals"` + Enums []EnumMap `yaml:"enums"` + Interfaces []InterfaceMap `yaml:"interfaces"` +} + +type InterfaceMap struct { + GoType string `yaml:"go_type"` + Strategy string `yaml:"strategy"` + ProtoContainer string `yaml:"proto_container"` + Cases []InterfaceCase `yaml:"cases"` +} + +type InterfaceCase struct { + GoType string `yaml:"go_type"` +} + +type OneofCase struct { + PkgPath string + TypeName string + DomainGo string +} + +type InterfaceOneof struct { + Name string + InterfacePkg string + InterfaceName string + GoInterface string + Cases []OneofCase +} + +type ImportAlias struct { + Path string + Alias string +} + +func LoadConfig(path string) (*Config, error) { + if path == "" { + return &Config{}, nil + } + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var cfg Config + if err := yaml.Unmarshal(b, &cfg); err != nil { + return nil, err + } + return &cfg, nil +} + +func ParseInterface(pkgPath, iface string, cfg *Config) (*Service, error) { + svc := &Service{PkgPath: pkgPath, IfaceName: iface} + pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedName}, pkgPath) + if err != nil { + return nil, err + } + if len(pkgs) == 0 || pkgs[0].Types == nil { + return nil, fmt.Errorf("package not found: %s", pkgPath) + } + + pkg := pkgs[0].Types + obj := pkg.Scope().Lookup(iface) + if obj == nil { + return nil, fmt.Errorf("interface %s not found in %s", iface, pkgPath) + } + + svc.IfacePkgName = pkg.Name() + named, ok := obj.Type().(*types.Named) + if !ok { + return nil, fmt.Errorf("%s is not a named type", iface) + } + itf, ok := named.Underlying().(*types.Interface) + if !ok { + return nil, fmt.Errorf("%s is not an interface", iface) + } + itf = itf.Complete() + + reg := map[string]*UserMessage{} + oneofReg := map[string]*InterfaceOneof{} + domainPkgs := map[string]string{} + + for i := 0; i < itf.NumMethods(); i++ { + m := itf.Method(i) + sig := m.Type().(*types.Signature) + meth, err := buildMethod(m.Name(), sig, cfg, reg, domainPkgs, oneofReg) + if err != nil { + return nil, fmt.Errorf("method %s: %w", m.Name(), err) + } + svc.Methods = append(svc.Methods, *meth) + } + + for _, um := range reg { + svc.UserMessages = append(svc.UserMessages, *um) + } + + slices.SortFunc(svc.UserMessages, func(i, j UserMessage) int { + return cmp.Compare(i.Name, j.Name) + }) + + for _, io := range oneofReg { + svc.InterfaceOneofs = append(svc.InterfaceOneofs, *io) + } + sort.Slice(svc.InterfaceOneofs, func(i, j int) bool { return svc.InterfaceOneofs[i].Name < svc.InterfaceOneofs[j].Name }) + + aliasMap, aliases := assignAliases(domainPkgs) + svc.ImportAliases = aliases + + // Qualify method field types + for mi := range svc.Methods { + for fi := range svc.Methods[mi].Req.Fields { + f := &svc.Methods[mi].Req.Fields[fi] + f.GoType = goTypeStringWithAliases(f.GoType, aliasMap) + if f.DomainElemGoType != "" { + f.DomainElemGoType = goTypeStringWithAliases(f.DomainElemGoType, aliasMap) + } + } + for fi := range svc.Methods[mi].Rep.Fields { + f := &svc.Methods[mi].Rep.Fields[fi] + f.GoType = goTypeStringWithAliases(f.GoType, aliasMap) + if f.DomainElemGoType != "" { + f.DomainElemGoType = goTypeStringWithAliases(f.DomainElemGoType, aliasMap) + } + } + } + + // Qualify user messages + field types + for i := range svc.UserMessages { + alias := aliasMap[svc.UserMessages[i].PkgPath] + if alias == "" { + alias = path.Base(svc.UserMessages[i].PkgPath) + } + svc.UserMessages[i].DomainGo = alias + "." + svc.UserMessages[i].Name + for fi := range svc.UserMessages[i].Fields { + f := &svc.UserMessages[i].Fields[fi] + f.GoType = goTypeStringWithAliases(f.GoType, aliasMap) + if f.DomainElemGoType != "" { + f.DomainElemGoType = goTypeStringWithAliases(f.DomainElemGoType, aliasMap) + } + } + } + + // Qualify oneofs + for i := range svc.InterfaceOneofs { + ialias := aliasMap[svc.InterfaceOneofs[i].InterfacePkg] + if ialias == "" { + ialias = path.Base(svc.InterfaceOneofs[i].InterfacePkg) + } + svc.InterfaceOneofs[i].GoInterface = ialias + "." + svc.InterfaceOneofs[i].InterfaceName + for j := range svc.InterfaceOneofs[i].Cases { + c := &svc.InterfaceOneofs[i].Cases[j] + alias := aliasMap[c.PkgPath] + if alias == "" { + alias = path.Base(c.PkgPath) + } + c.DomainGo = alias + "." + c.TypeName + } + } + + return svc, nil +} + +// Replace full package prefixes with aliases, longest path first +func goTypeStringWithAliases(s string, aliasMap map[string]string) string { + if !strings.Contains(s, ".") { + return s + } + paths := make([]string, 0, len(aliasMap)) + for p := range aliasMap { + paths = append(paths, p) + } + sort.Slice(paths, func(i, j int) bool { return len(paths[i]) > len(paths[j]) }) + out := s + for _, p := range paths { + alias := aliasMap[p] + out = strings.ReplaceAll(out, p+".", alias+".") + } + return out +} + +func assignAliases(domainPkgs map[string]string) (map[string]string, []ImportAlias) { + aliasMap := make(map[string]string) + used := make(map[string]int) + + paths := make([]string, 0, len(domainPkgs)) + for p := range domainPkgs { + paths = append(paths, p) + } + sort.Strings(paths) + + for _, p := range paths { + base := domainPkgs[p] + if base == "" { + base = path.Base(p) + } + alias := base + if c := used[base]; c > 0 { + alias = fmt.Sprintf("%s%d", base, c+1) + } + used[base]++ + aliasMap[p] = alias + } + + var out []ImportAlias + for _, p := range paths { + out = append(out, ImportAlias{Path: p, Alias: aliasMap[p]}) + } + return aliasMap, out +} + +func buildMethod(name string, sig *types.Signature, cfg *Config, reg map[string]*UserMessage, domainImports map[string]string, oneofReg map[string]*InterfaceOneof) (*Method, error) { + m := &Method{Name: name} + params := sig.Params() + results := sig.Results() + + recordPkg := func(t types.Type) { + if n, ok := t.(*types.Named); ok { + if p := n.Obj().Pkg(); p != nil { + domainImports[p.Path()] = p.Name() + } + } else { + recordPkgFromTypeString(goTypeName(t), domainImports) + } + } + + if results.Len() == 0 { + return nil, fmt.Errorf("no results; need (T, error)") + } + if !isErrorType(results.At(results.Len() - 1).Type()) { + return nil, fmt.Errorf("last result must be error") + } + + start := 0 + if params.Len() > 0 && isContext(params.At(0).Type()) { + m.HasContext = true + start = 1 + } + + req := Message{Name: name + "Request"} + for i := start; i < params.Len(); i++ { + pv := params.At(i) + recordPkg(pv.Type()) + pt, err := mapGoToProto(pv.Type(), cfg, reg, domainImports, oneofReg) + if err != nil { + return nil, fmt.Errorf("param %d: %w", i, err) + } + + elem := arrayElemViaUnderlying(pv.Type()) + isArr := elem != nil + if !isArr && pt.Name == "bytes" { // fixed [N]byte alias case + if yes, e := underlyingByteArray(pv.Type()); yes { + isArr = true + elem = e + } + } + // make sure we record any package from element too (e.g. []evm.Hash) + recordPkg(elem) + + req.Fields = append(req.Fields, Field{ + Name: exported(pv.Name(), i-start), + ProtoTag: len(req.Fields) + 1, + Type: pt, + GoType: pv.Type().String(), + DomainIsArray: isArr, + DomainElemGoType: goTypeOrEmpty(elem), + DomainElemIsByte: isByteOrUint8(elem), + }) + } + m.Req = req + + if results.Len() == 1 { + m.Rep = Message{Name: name + "Reply"} + } else { + rt := results.At(0).Type() + recordPkg(rt) + pt, err := mapGoToProto(rt, cfg, reg, domainImports, oneofReg) + if err != nil { + return nil, fmt.Errorf("result: %w", err) + } + + elem := arrayElemViaUnderlying(rt) + isArr := elem != nil + if !isArr && pt.Name == "bytes" { + if yes, e := underlyingByteArray(rt); yes { + isArr = true + elem = e + } + } + recordPkg(elem) + + m.Rep = Message{ + Name: name + "Reply", + Fields: []Field{{ + Name: "result", + ProtoTag: 1, + Type: pt, + GoType: rt.String(), + DomainIsArray: isArr, + DomainElemGoType: goTypeOrEmpty(elem), + DomainElemIsByte: isByteOrUint8(elem), + }}, + } + } + return m, nil +} + +func isContext(t types.Type) bool { + p, ok := t.(*types.Named) + if !ok { + return false + } + return p.Obj().Pkg() != nil && p.Obj().Pkg().Path() == "context" && p.Obj().Name() == "Context" +} + +func isErrorType(t types.Type) bool { + ni, ok := t.Underlying().(*types.Interface) + return ok && ni.NumMethods() == 1 && ni.Method(0).Name() == "Error" +} + +func exported(name string, idx int) string { + if name == "" { + name = fmt.Sprintf("arg%d", idx+1) + } + return strings.ToLower(name[:1]) + name[1:] +} + +func mapGoToProto(t types.Type, cfg *Config, reg map[string]*UserMessage, domainImports map[string]string, oneofReg map[string]*InterfaceOneof) (ProtoType, error) { + switch u := t.(type) { + case *types.Named: + qn := qualifiedName(u) + + // ensure import + if p := u.Obj().Pkg(); p != nil { + domainImports[p.Path()] = p.Name() + } + + // interface -> oneof container + if _, isIface := u.Underlying().(*types.Interface); isIface { + im, ok := findInterfaceCfg(qn, cfg) + if !ok { + return ProtoType{}, fmt.Errorf("interface %q in RPC signature has no mapping (config.interfaces)", qn) + } + if _, exists := oneofReg[qn]; !exists { + io := &InterfaceOneof{ + Name: im.ProtoContainer, + InterfacePkg: u.Obj().Pkg().Path(), + InterfaceName: u.Obj().Name(), + } + for _, c := range im.Cases { + nc, err := lookupNamed(c.GoType) + if err != nil { + return ProtoType{}, err + } + if p := nc.Obj().Pkg(); p != nil { + domainImports[p.Path()] = p.Name() + } + st, ok := nc.Underlying().(*types.Struct) + if !ok { + return ProtoType{}, fmt.Errorf("interface case %q is not a named struct", c.GoType) + } + ensureUserMessage(nc, st, cfg, reg, domainImports, oneofReg) + io.Cases = append(io.Cases, OneofCase{ + PkgPath: nc.Obj().Pkg().Path(), + TypeName: nc.Obj().Name(), + }) + } + oneofReg[qn] = io + } + return ProtoType{Name: im.ProtoContainer}, nil + } + + // externals / enums + for _, ex := range cfg.Externals { + if ex.GoType == qn { + return ProtoType{Name: ex.ProtoType}, nil + } + } + for _, em := range cfg.Enums { + if em.GoType == qn { + return ProtoType{Name: em.ProtoType}, nil + } + } + + // struct -> user message + if st, ok := u.Underlying().(*types.Struct); ok { + ensureUserMessage(u, st, cfg, reg, domainImports, oneofReg) + return ProtoType{Name: u.Obj().Name()}, nil + } + + // alias: delegate to underlying so arrays/slices/scalars map correctly + return mapGoToProto(u.Underlying(), cfg, reg, domainImports, oneofReg) + + case *types.Pointer: + return mapGoToProto(u.Elem(), cfg, reg, domainImports, oneofReg) + + case *types.Slice: + if isUint8(u.Elem()) { + return ProtoType{Name: "bytes"}, nil + } + pt, err := mapGoToProto(u.Elem(), cfg, reg, domainImports, oneofReg) + if err != nil { + return ProtoType{}, err + } + pt.IsRepeated = true + return pt, nil + + default: + return mapPrimitiveToProto(t, cfg) + } +} + +func mapPrimitiveToProto(t types.Type, cfg *Config) (ProtoType, error) { + switch u := t.Underlying().(type) { + case *types.Basic: + switch u.Kind() { + case types.Bool: + return ProtoType{Name: "bool"}, nil + case types.String: + return ProtoType{Name: "string"}, nil + case types.Int8, types.Int16, types.Int32: + return ProtoType{Name: "int32"}, nil + case types.Int, types.Int64: + return ProtoType{Name: "int64"}, nil + case types.Uint8, types.Uint16, types.Uint32: + return ProtoType{Name: "uint32"}, nil + case types.Uint, types.Uint64, types.Uintptr: + return ProtoType{Name: "uint64"}, nil + case types.UnsafePointer: + return ProtoType{}, fmt.Errorf("unsafe.Pointer unsupported") + default: + return ProtoType{}, fmt.Errorf("unsupported basic kind: %v", u.Kind()) + } + + case *types.Slice: + pt, err := mapPrimitiveToProto(u.Elem(), cfg) + if err != nil { + return ProtoType{}, err + } + if pt.Name == "uint32" && isUint8(u.Elem()) { // []byte + return ProtoType{Name: "bytes"}, nil + } + pt.IsRepeated = true + return pt, nil + + case *types.Array: + if isUint8(u.Elem()) { + return ProtoType{Name: "bytes"}, nil + } + pt, err := mapPrimitiveToProto(u.Elem(), cfg) + if err != nil { + return ProtoType{}, err + } + pt.IsRepeated = true + return pt, nil + + case *types.Pointer: + return mapPrimitiveToProto(u.Elem(), cfg) + + case *types.Named: + qn := qualifiedName(u) + for _, ex := range cfg.Externals { + if ex.GoType == qn { + return ProtoType{Name: ex.ProtoType}, nil + } + } + for _, em := range cfg.Enums { + if em.GoType == qn { + return ProtoType{Name: em.ProtoType}, nil + } + } + // unwrap alias so arrays/scalars map correctly + return mapPrimitiveToProto(u.Underlying(), cfg) + + default: + return ProtoType{}, fmt.Errorf("unsupported type: %T", u) + } +} + +func ensureUserMessage(n *types.Named, st *types.Struct, cfg *Config, reg map[string]*UserMessage, domainImports map[string]string, oneofReg map[string]*InterfaceOneof) { + qn := qualifiedName(n) + if _, exists := reg[qn]; exists { + return + } + + um := &UserMessage{ + Name: n.Obj().Name(), + PkgPath: n.Obj().Pkg().Path(), + } + reg[qn] = um + + tag := 1 + for i := 0; i < st.NumFields(); i++ { + f := st.Field(i) + if !f.Exported() { + continue + } + + // record package even if alias (e.g. evm.Address) + if p := f.Pkg(); p != nil { + domainImports[p.Path()] = p.Name() + } + recordPkgFromTypeString(goTypeName(f.Type()), domainImports) + + ft, err := mapGoToProto(f.Type(), cfg, reg, domainImports, oneofReg) + if err != nil { + continue + } + + elem := arrayElemViaUnderlying(f.Type()) + isArr := elem != nil + if !isArr && ft.Name == "bytes" { + if yes, e := underlyingByteArray(f.Type()); yes { + isArr = true + elem = e + } + } + // also record the element's package (e.g. []evm.Hash) + recordPkgFromType(elem, domainImports) + + pname := toSnake(f.Name()) + + um.Fields = append(um.Fields, Field{ + Name: pname, + ProtoTag: tag, + Type: ft, + GoType: f.Type().String(), + DomainIsArray: isArr, + DomainElemGoType: goTypeOrEmpty(elem), + DomainElemIsByte: isByteOrUint8(elem), + }) + tag++ + } + + reg[qn] = um +} + +func splitQ(q string) (pkgPath, typeName string) { + i := strings.LastIndex(q, ".") + if i < 0 { + return "", q + } + return q[:i], q[i+1:] +} + +func lookupNamed(q string) (*types.Named, error) { + p, tn := splitQ(q) + if p == "" || tn == "" { + return nil, fmt.Errorf("invalid qualified name: %q", q) + } + pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedTypes | packages.NeedName}, p) + if err != nil { + return nil, err + } + if len(pkgs) == 0 || pkgs[0].Types == nil { + return nil, fmt.Errorf("package not found: %s", p) + } + obj := pkgs[0].Types.Scope().Lookup(tn) + if obj == nil { + return nil, fmt.Errorf("type %s not found in %s", tn, p) + } + n, ok := obj.Type().(*types.Named) + if !ok { + return nil, fmt.Errorf("%s in %s is not a named type", tn, p) + } + return n, nil +} + +func findInterfaceCfg(qn string, cfg *Config) (InterfaceMap, bool) { + for _, im := range cfg.Interfaces { + if im.GoType == qn && strings.EqualFold(im.Strategy, "oneof") { + return im, true + } + } + return InterfaceMap{}, false +} + +func toSnake(s string) string { + var b strings.Builder + for i, r := range s { + if i > 0 && r >= 'A' && r <= 'Z' { + b.WriteByte('_') + } + b.WriteRune(toLower(r)) + } + return b.String() +} + +func toLower(r rune) rune { + if r >= 'A' && r <= 'Z' { + return r - 'A' + 'a' + } + return r +} + +func isUint8(t types.Type) bool { + b, ok := t.Underlying().(*types.Basic) + return ok && b.Kind() == types.Uint8 +} + +func qualifiedName(n *types.Named) string { + pkg := n.Obj().Pkg() + if pkg == nil { + return n.Obj().Name() + } + return pkg.Path() + "." + n.Obj().Name() +} + +func arrayInfo(t types.Type) (isArray bool, elem types.Type) { + switch u := t.(type) { + case *types.Array: + return true, u.Elem() + case *types.Named: + return arrayInfo(u.Underlying()) + case *types.Pointer: + return arrayInfo(u.Elem()) + default: + return false, nil + } +} + +// returns array element type after unwrapping named/pointer; nil if not an array +func arrayElemViaUnderlying(t types.Type) types.Type { + for { + switch u := t.(type) { + case *types.Array: + return u.Elem() + case *types.Named: + t = u.Underlying() + case *types.Pointer: + t = u.Elem() + default: + return nil + } + } +} + +func elemTypeOf(t types.Type) types.Type { + switch u := t.(type) { + case *types.Slice: + return u.Elem() + case *types.Array: + return u.Elem() + case *types.Pointer: + return elemTypeOf(u.Elem()) + case *types.Named: + return elemTypeOf(u.Underlying()) + default: + return nil + } +} + +func isByteOrUint8(t types.Type) bool { + for { + switch u := t.(type) { + case *types.Named: + t = u.Underlying() + case *types.Pointer: + t = u.Elem() + default: + goto EXIT + } + } +EXIT: + b, ok := t.(*types.Basic) + if !ok { + return false + } + return b.Kind() == types.Byte || b.Kind() == types.Uint8 +} + +func goTypeName(t types.Type) string { + return types.TypeString(t, func(p *types.Package) string { + if p == nil { + return "" + } + return p.Path() + }) +} + +func goTypeOrEmpty(t types.Type) string { + if t == nil { + return "" + } + return goTypeName(t) +} + +func underlyingByteArray(t types.Type) (bool, types.Type) { + for { + switch u := t.(type) { + case *types.Pointer: + t = u.Elem() + case *types.Named: + t = u.Underlying() + case *types.Array: + if isByteOrUint8(u.Elem()) { + return true, u.Elem() + } + return false, nil + default: + return false, nil + } + } +} + +// record packages for named types or, if alias erased, try to recover from type string +func recordPkgFromType(t types.Type, domainImports map[string]string) { + if t == nil { + return + } + if n, ok := t.(*types.Named); ok { + if p := n.Obj().Pkg(); p != nil { + domainImports[p.Path()] = p.Name() + return + } + } + recordPkgFromTypeString(goTypeName(t), domainImports) +} + +func recordPkgFromTypeString(s string, domainImports map[string]string) { + // strip common wrappers + trim := func(x string) string { + for { + switch { + case strings.HasPrefix(x, "[]"): + x = x[2:] + case strings.HasPrefix(x, "*"): + x = x[1:] + case strings.HasPrefix(x, "map["): + // drop until ']' + if i := strings.IndexByte(x, ']'); i >= 0 { + x = x[i+1:] + } else { + return x + } + case strings.HasPrefix(x, "chan "): + x = x[5:] + default: + return x + } + } + } + x := trim(s) + if i := strings.LastIndex(x, "."); i > 0 { + p := x[:i] + // still may include wrappers; ensure it looks like a path + if strings.Contains(p, "/") { + domainImports[p] = path.Base(p) + } + } +} diff --git a/pkg/loop/internal/generator/render.go b/pkg/loop/internal/generator/render.go new file mode 100644 index 000000000..be1b93704 --- /dev/null +++ b/pkg/loop/internal/generator/render.go @@ -0,0 +1,918 @@ +package generator + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "text/template" +) + +func RenderAll(protoOut, goOutDir string, svc *Service) error { + if err := renderFile(templatesProto, protoOut, svc); err != nil { + return err + } + if err := renderFile(templatesWiring, filepath.Join(goOutDir, "rpc.go"), svc); err != nil { + return err + } + if err := renderFile(templatesRPCTest, filepath.Join(goOutDir, "rpc_test.go"), svc); err != nil { + return err + } + return nil +} + +func renderFile(tmplSrc, outPath string, data any) error { + // Build alias lookup for conditional helpers/imports + aliasMap := map[string]string{} + if svc, ok := data.(*Service); ok { + for _, ia := range svc.ImportAliases { + aliasMap[ia.Path] = ia.Alias + } + } + aliasFor := func(p string) string { + if a := aliasMap[p]; a != "" { + return a + } + if i := strings.LastIndex(p, "/"); i >= 0 { + return p[i+1:] + } + return p + } + + t, err := template.New("x").Funcs(template.FuncMap{ + "pbField": pbFieldName, + "isMsg": isMessage, + "toPB": toPBFuncName, + "fromPB": fromPBFuncName, + "domainFieldGoName": domainFieldGoName, + "add": add, + "lower": lower, + + // Flags from generator.go + "elemIsArray": func(f Field) bool { return f.DomainIsArray }, + "elemGo": func(f Field) string { return f.DomainElemGoType }, + "elemArrayIsByte": func(f Field) bool { return f.DomainElemIsByte }, + "isBytesType": func(pt ProtoType) bool { return pt.Name == "bytes" }, + + // Element type from Go type string (safe fallback) + "sliceElem": func(f Field) string { return chooseElemType(f) }, + + // String-based helpers + "isSliceType": func(s string) bool { + s = strings.TrimSpace(s) + return strings.HasPrefix(s, "[]") + }, + + // Import/alias helpers + "aliasFor": aliasFor, + }).Parse(tmplSrc) + if err != nil { + return err + } + var buf bytes.Buffer + if err := t.Execute(&buf, data); err != nil { + return err + } + return os.WriteFile(outPath, buf.Bytes(), 0o644) +} + +func chooseElemType(f Field) string { + if f.DomainElemGoType != "" { + return f.DomainElemGoType + } + return elemFromGoType(f.GoType) +} + +func elemFromGoType(goType string) string { + s := strings.TrimSpace(goType) + if s == "" { + return s + } + // Strip leading slice brackets "[]" + for strings.HasPrefix(s, "[]") { + s = s[2:] + } + // If array form "[N]T", strip "[N]" and return "T" + if strings.HasPrefix(s, "[") { + if i := strings.Index(s, "]"); i > 0 && i+1 < len(s) { + return strings.TrimSpace(s[i+1:]) + } + } + return s +} + +func domainFieldGoName(proto string) string { + // reverse of pbFieldName + if strings.Contains(proto, "_") { + parts := strings.Split(proto, "_") + for i := range parts { + if parts[i] == "" { + continue + } + parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:] + } + return strings.Join(parts, "") + } + return strings.ToUpper(proto[:1]) + proto[1:] +} +func isMessage(pt ProtoType) bool { + switch pt.Name { + case "bytes", "string", "bool", "uint64", "uint32", "int64", "int32", "double", "float", "sint64", "sint32", "fixed64", "fixed32": + return false + default: + // anything not a scalar/bytes is considered a message reference + return true + } +} + +func toPBFuncName(typeName string) string { return "toPB_" + typeName } +func fromPBFuncName(typeName string) string { return "fromPB_" + typeName } +func pbFieldName(s string) string { + if s == "" { + return "" + } + // Allow both snake_case and lowerCamel in source model + if strings.Contains(s, "_") { + parts := strings.Split(s, "_") + for i, p := range parts { + if p == "" { + continue + } + parts[i] = strings.ToUpper(p[:1]) + p[1:] + } + return strings.Join(parts, "") + } + return strings.ToUpper(s[:1]) + s[1:] +} +func add(a, b int) int { return a + b } + +func lower(s string) string { + if s == "" { + return s + } + return strings.ToLower(s[:1]) + s[1:] +} + +var templatesProto = `syntax = "proto3"; +option go_package = "{{.OptionGoPackage}}"; +package {{.ProtoPkg}}; +{{- range .ProtoImports }} +import "{{ . }}"; +{{- end }} + +{{/* 1) Domain struct messages */}} +{{- range .UserMessages }} +message {{ .Name }} { +{{- range .Fields }} +{{- if .Type.IsRepeated }} +repeated {{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; +{{- else }} +{{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; +{{- end }} +{{- end }} +} +{{- end }} + +{{ range .InterfaceOneofs }} +message {{ .Name }} { + oneof kind { + {{- range $i, $c := .Cases }} + {{ $c.TypeName }} {{ lower $c.TypeName }} = {{ add $i 1 }}; + {{- end }} + } +} +{{ end }} + +service {{ .ServiceName }} { + {{- range .Methods }} + rpc {{ .Name }}({{ .Req.Name }}) returns ({{ .Rep.Name }}); + {{- end }} +} + +{{/* 2) Per-method request/reply */}} +{{- range .Methods }} +message {{ .Req.Name }} { + {{- range .Req.Fields }} + {{- if .Type.IsRepeated }} + repeated {{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; + {{- else }} + {{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; + {{- end }} + {{- end }} +} +message {{ .Rep.Name }} { + {{- range .Rep.Fields }} + {{- if .Type.IsRepeated }} + repeated {{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; + {{- else }} + {{ .Type.Name }} {{ .Name }} = {{ .ProtoTag }}; + {{- end }} + {{- end }} +} +{{- end }}` + +var templatesServer = ` +// Code generated by genrpc; DO NOT EDIT. +package {{ .ServiceName | lower }} + +import ( + "context" + {{- if aliasFor "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" }} + "fmt" + {{- end }} + + pb "{{ .OptionGoPackage }}" + {{- range .ImportAliases }} + {{ .Alias }} "{{ .Path }}" + {{- end }} +) + + +` + +var templatesWiring = ` +// Code generated by genrpc; DO NOT EDIT. +package {{ .ServiceName | lower }} + +import ( + "context" + {{- if aliasFor "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" }} + "fmt" + {{- end }} + + pb "{{ .OptionGoPackage }}" + {{- range .ImportAliases }} + {{ .Alias }} "{{ .Path }}" + {{- end }} +) + +type Client struct{ rpc pb.{{ .ServiceName }}Client } + +func NewClient(r pb.{{ .ServiceName }}Client) *Client { return &Client{rpc: r} } + +{{ range .Methods }} +func (c *Client) {{ .Name }}( + ctx context.Context{{ range .Req.Fields }}, {{ .Name }} {{ .GoType }}{{ end }}, +) ({{ if .Rep.Fields }}{{ (index .Rep.Fields 0).GoType }}{{ else }}struct{}{{ end }}, error) { + + req := &pb.{{ .Req.Name }}{ + {{- range .Req.Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + {{ pbField .Name }}: func(in {{ .GoType }}) []*pb.{{ .Type.Name }} { + if in == nil { return nil } + out := make([]*pb.{{ .Type.Name }}, len(in)) + for i, v := range in { out[i] = {{ toPB .Type.Name }}(v) } + return out + }({{ .Name }}), + {{- else if isMsg .Type }} + {{ pbField .Name }}: {{ toPB ( .Type.Name ) }}({{ .Name }}), + {{- else if .Type.IsRepeated }} + {{- if eq .Type.Name "bytes" }} + // domain []T -> pb [][]byte (T may be a fixed [N]byte) + {{ pbField .Name }}: func(in {{ .GoType }}) [][]byte { + if in == nil { return nil } + out := make([][]byte, len(in)) + for i := range in { + {{- if isSliceType (sliceElem .) }} + out[i] = []byte(in[i]) + {{- else }} + out[i] = in[i][:] + {{- end }} + } + return out + }({{ .Name }}), + {{- else }} + // domain []scalar -> pb []scalar + {{ pbField .Name }}: func(in {{ .GoType }}) []{{ .Type.Name }} { + if in == nil { return nil } + out := make([]{{ .Type.Name }}, len(in)) + for i := range in { out[i] = {{ .Type.Name }}(in[i]) } + return out + }({{ .Name }}), + {{- end }} + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + {{ pbField .Name }}: {{ .Name }}, + {{- else }} + // domain [N]byte -> pb []byte + {{ pbField .Name }}: {{ .Name }}[:], + {{- end }} + {{- else }} + {{ pbField .Name }}: {{ .Type.Name }}({{ .Name }}), + {{- end }} + {{- end }} + } + + rep, err := c.rpc.{{ .Name }}(ctx, req) + if err != nil { + {{- if .Rep.Fields }} var zero {{ (index .Rep.Fields 0).GoType }}; return zero, err {{- else }} return struct{}{}, err {{- end }} + } + + {{- if .Rep.Fields }} + {{- $rf := (index .Rep.Fields 0) }} + {{- if isMsg $rf.Type }} + {{- if $rf.Type.IsRepeated }} + if rep.{{ pbField $rf.Name }} == nil { var zero {{ $rf.GoType }}; return zero, nil } + out := make({{ $rf.GoType }}, len(rep.{{ pbField $rf.Name }})) + for i, v := range rep.{{ pbField $rf.Name }} { out[i] = {{ fromPB $rf.Type.Name }}(v) } + return out, nil + {{- else }} + return {{ fromPB ( $rf.Type.Name ) }}(rep.{{ pbField $rf.Name }}), nil + {{- end }} + {{- else }} + {{- if $rf.Type.IsRepeated }} + {{- if $rf.DomainIsArray }} + // pb []scalar -> domain [N]T + var out {{ $rf.GoType }} + if rep.{{ pbField $rf.Name }} != nil { + for i := range out { + if i < len(rep.{{ pbField $rf.Name }}) { + out[i] = {{ sliceElem $rf }}(rep.{{ pbField $rf.Name }}[i]) + } + } + } + return out, nil + {{- else if eq $rf.Type.Name "bytes" }} + // pb [][]byte -> domain []T (T may be fixed [N]byte) + if rep.{{ pbField $rf.Name }} == nil { var zero {{ $rf.GoType }}; return zero, nil } + out := make({{ $rf.GoType }}, len(rep.{{ pbField $rf.Name }})) + for i := range rep.{{ pbField $rf.Name }} { + {{- if isSliceType (sliceElem $rf) }} + out[i] = {{ sliceElem $rf }}(rep.{{ pbField $rf.Name }}[i]) + {{- else }} + var e {{ sliceElem $rf }} + if len(rep.{{ pbField $rf.Name }}[i]) != len(e) { var zero {{ $rf.GoType }}; return zero, fmt.Errorf("invalid length for {{ pbField $rf.Name }}[%d]: got %d want %d", i, len(rep.{{ pbField $rf.Name }}[i]), len(e)) } + copy(e[:], rep.{{ pbField $rf.Name }}[i]) + out[i] = e + {{- end }} + } + return out, nil + {{- else }} + // pb []scalar -> domain []scalar (cast if needed) + if rep.{{ pbField $rf.Name }} == nil { var zero {{ $rf.GoType }}; return zero, nil } + out := make({{ $rf.GoType }}, len(rep.{{ pbField $rf.Name }})) + for i := range rep.{{ pbField $rf.Name }} { + out[i] = {{ sliceElem $rf }}(rep.{{ pbField $rf.Name }}[i]) + } + return out, nil + {{- end }} + {{- else if eq $rf.Type.Name "bytes" }} + {{- if isSliceType $rf.GoType }} + // pb []byte -> domain []byte + return rep.{{ pbField $rf.Name }}, nil + {{- else }} + // pb []byte -> domain [N]byte + var out {{ $rf.GoType }} + if rep.{{ pbField $rf.Name }} != nil { + if len(rep.{{ pbField $rf.Name }}) != len(out) { var zero {{ $rf.GoType }}; return zero, fmt.Errorf("invalid length for {{ pbField $rf.Name }}: got %d want %d", len(rep.{{ pbField $rf.Name }}), len(out)) } + copy(out[:], rep.{{ pbField $rf.Name }}) + } + return out, nil + {{- end }} + {{- else }} + return {{ $rf.GoType }}(rep.{{ pbField $rf.Name }}), nil + {{- end }} + {{- end }} + {{- else }} + return struct{}{}, nil + {{- end }} +} +{{ end }} + +// ---- pb<->domain converters for user messages ---- +{{- range .UserMessages }} +func {{ toPB .Name }}(in {{ .DomainGo }}) *pb.{{ .Name }} { + out := &pb.{{ .Name }}{} + {{- range .Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + if in.{{ domainFieldGoName .Name }} != nil { + out.{{ pbField .Name }} = make([]*pb.{{ .Type.Name }}, len(in.{{ domainFieldGoName .Name }})) + for i, v := range in.{{ domainFieldGoName .Name }} { out.{{ pbField .Name }}[i] = {{ toPB .Type.Name }}(v) } + } + {{- else if isMsg .Type }} + out.{{ pbField .Name }} = {{ toPB .Type.Name }}(in.{{ domainFieldGoName .Name }}) + {{- else if .Type.IsRepeated }} + {{- if eq .Type.Name "bytes" }} + // domain []T -> pb [][]byte (T may be fixed [N]byte) + if in.{{ domainFieldGoName .Name }} != nil { + out.{{ pbField .Name }} = make([][]byte, len(in.{{ domainFieldGoName .Name }})) + for i := range in.{{ domainFieldGoName .Name }} { + {{- if isSliceType (sliceElem .) }} + out.{{ pbField .Name }}[i] = []byte(in.{{ domainFieldGoName .Name }}[i]) + {{- else }} + out.{{ pbField .Name }}[i] = in.{{ domainFieldGoName .Name }}[i][:] + {{- end }} + } + } + {{- else if .DomainIsArray }} + // domain [N]T -> pb []scalar + { + src := in.{{ domainFieldGoName .Name }} + out.{{ pbField .Name }} = make([]{{ .Type.Name }}, len(src)) + for i := range src { out.{{ pbField .Name }}[i] = {{ .Type.Name }}(src[i]) } + } + {{- else }} + // domain []T -> pb []scalar + if in.{{ domainFieldGoName .Name }} != nil { + out.{{ pbField .Name }} = make([]{{ .Type.Name }}, len(in.{{ domainFieldGoName .Name }})) + for i := range in.{{ domainFieldGoName .Name }} { + out.{{ pbField .Name }}[i] = {{ .Type.Name }}(in.{{ domainFieldGoName .Name }}[i]) + } + } + {{- end }} + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + out.{{ pbField .Name }} = in.{{ domainFieldGoName .Name }} + {{- else }} + // domain [N]byte/uint8 -> pb []byte + out.{{ pbField .Name }} = in.{{ domainFieldGoName .Name }}[:] + {{- end }} + {{- else }} + out.{{ pbField .Name }} = {{ .Type.Name }}(in.{{ domainFieldGoName .Name }}) + {{- end }} + {{- end }} + return out +} + +func {{ fromPB .Name }}(in *pb.{{ .Name }}) {{ .DomainGo }} { + var out {{ .DomainGo }} + if in == nil { return out } + {{- range .Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + if in.{{ pbField .Name }} != nil { + out.{{ domainFieldGoName .Name }} = make([]{{ sliceElem . }}, len(in.{{ pbField .Name }})) + for i, v := range in.{{ pbField .Name }} { + out.{{ domainFieldGoName .Name }}[i] = {{ fromPB .Type.Name }}(v) + } + } + {{- else if isMsg .Type }} + out.{{ domainFieldGoName .Name }} = {{ fromPB .Type.Name }}(in.{{ pbField .Name }}) + {{- else if .Type.IsRepeated }} + {{- if eq .Type.Name "bytes" }} + // pb [][]byte -> domain []T (T may be fixed [N]byte) + if in.{{ pbField .Name }} != nil { + out.{{ domainFieldGoName .Name }} = make([]{{ sliceElem . }}, len(in.{{ pbField .Name }})) + for i := range in.{{ pbField .Name }} { + {{- if isSliceType (sliceElem .) }} + out.{{ domainFieldGoName .Name }}[i] = {{ sliceElem . }}(in.{{ pbField .Name }}[i]) + {{- else }} + var e {{ sliceElem . }} + // Length-check omitted here to keep converter pure; client/server paths enforce it and can return error. + copy(e[:], in.{{ pbField .Name }}[i]) + out.{{ domainFieldGoName .Name }}[i] = e + {{- end }} + } + } + {{- else if .DomainIsArray }} + // pb []scalar -> domain [N]T + for i := range out.{{ domainFieldGoName .Name }} { + if in.{{ pbField .Name }} != nil && i < len(in.{{ pbField .Name }}) { + out.{{ domainFieldGoName .Name }}[i] = {{ sliceElem . }}(in.{{ pbField .Name }}[i]) + } + } + {{- else }} + // pb []scalar -> domain []T + if in.{{ pbField .Name }} != nil { + out.{{ domainFieldGoName .Name }} = make([]{{ sliceElem . }}, len(in.{{ pbField .Name }})) + for i := range in.{{ pbField .Name }} { + out.{{ domainFieldGoName .Name }}[i] = {{ sliceElem . }}(in.{{ pbField .Name }}[i]) + } + } + {{- end }} + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + // pb []byte -> domain []byte + out.{{ domainFieldGoName .Name }} = in.{{ pbField .Name }} + {{- else }} + // pb []byte -> domain [N]byte/uint8 + if in.{{ pbField .Name }} != nil { + copy(out.{{ domainFieldGoName .Name }}[:], in.{{ pbField .Name }}) + } + {{- end }} + {{- else }} + out.{{ domainFieldGoName .Name }} = {{ .GoType }}(in.{{ pbField .Name }}) + {{- end }} + {{- end }} + return out +} +{{- end }} + + +{{- range $io := .InterfaceOneofs }} +func toPB_{{$io.Name}}(in {{$io.GoInterface}}) *pb.{{$io.Name}} { + if in == nil { return nil } + switch v := in.(type) { + {{- range $c := $io.Cases }} + case *{{$c.DomainGo}}: + return &pb.{{$io.Name}}{ + Kind: &pb.{{$io.Name}}_{{$c.TypeName}}{ {{$c.TypeName}}: toPB_{{$c.TypeName}}(*v) }, + } + {{- end }} + default: + return nil + } +} + +func fromPB_{{$io.Name}}(in *pb.{{$io.Name}}) {{$io.GoInterface}} { + if in == nil { return nil } + switch k := in.Kind.(type) { + {{- range $c := $io.Cases }} + case *pb.{{$io.Name}}_{{$c.TypeName}}: + { + v := fromPB_{{$c.TypeName}}(k.{{$c.TypeName}}) + return &v + } + {{- end }} + default: + return nil + } +} +{{- end }} + +// ---- Optional helpers for fixed-size arrays (with length checks) ---- +{{- $evm := aliasFor "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" -}} +{{- if $evm }} + +// toAddress converts a byte slice into an {{$evm}}.Address with length check. +func toAddress(b []byte) ({{$evm}}.Address, error) { + var out {{$evm}}.Address + if len(b) != len(out) { return out, fmt.Errorf("invalid evm.Address length: got %d want %d", len(b), len(out)) } + copy(out[:], b) + return out, nil +} + +// toHash converts a byte slice into an {{$evm}}.Hash with length check. +func toHash(b []byte) ({{$evm}}.Hash, error) { + var out {{$evm}}.Hash + if len(b) != len(out) { return out, fmt.Errorf("invalid evm.Hash length: got %d want %d", len(b), len(out)) } + copy(out[:], b) + return out, nil +} +{{- end }} + +// server +type Server struct { + pb.Unimplemented{{ .ServiceName }}Server + impl interface{ + {{- range .Methods }} + {{ .Name }}(ctx context.Context{{ range .Req.Fields }}, {{ .Name }} {{ .GoType }}{{ end }}) ({{ if .Rep.Fields }}{{ (index .Rep.Fields 0).GoType }}{{ else }}struct{}{{ end }}, error) + {{- end }} + } +} + +func NewServer(impl any) *Server { + return &Server{impl: impl.(interface{ + {{- range .Methods }} + {{ .Name }}(ctx context.Context{{ range .Req.Fields }}, {{ .Name }} {{ .GoType }}{{ end }}) ({{ if .Rep.Fields }}{{ (index .Rep.Fields 0).GoType }}{{ else }}struct{}{{ end }}, error) + {{- end }} + })} +} + +{{ range .Methods }} +func (s *Server) {{ .Name }}(ctx context.Context, req *pb.{{ .Req.Name }}) (*pb.{{ .Rep.Name }}, error) { + {{- /* pb -> domain params */}} + {{- range .Req.Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + var {{ .Name }} {{ .GoType }} + if req.{{ pbField .Name }} != nil { + {{ .Name }} = make({{ .GoType }}, len(req.{{ pbField .Name }})) + for i, v := range req.{{ pbField .Name }} { + {{ .Name }}[i] = {{ fromPB .Type.Name }}(v) + } + } + {{- else if isMsg .Type }} + var {{ .Name }} {{ .GoType }} = {{ fromPB .Type.Name }}(req.{{ pbField .Name }}) + {{- else if and .Type.IsRepeated (eq .Type.Name "bytes") }} + // pb [][]byte -> domain []T (T may be fixed [N]byte) + var {{ .Name }} {{ .GoType }} + if req.{{ pbField .Name }} != nil { + {{ .Name }} = make({{ .GoType }}, len(req.{{ pbField .Name }})) + for i := range req.{{ pbField .Name }} { + {{- if isSliceType (sliceElem .) }} + {{ .Name }}[i] = {{ sliceElem . }}(req.{{ pbField .Name }}[i]) + {{- else }} + var e {{ sliceElem . }} + if len(req.{{ pbField .Name }}[i]) != len(e) { return nil, fmt.Errorf("invalid length for {{ .Name }}[%d]: got %d want %d", i, len(req.{{ pbField .Name }}[i]), len(e)) } + copy(e[:], req.{{ pbField .Name }}[i]) + {{ .Name }}[i] = e + {{- end }} + } + } + {{- else if .Type.IsRepeated }} + // scalar repeated: assign directly (proto-shaped types) + var {{ .Name }} {{ .GoType }} = req.{{ pbField .Name }} + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + var {{ .Name }} {{ .GoType }} = req.{{ pbField .Name }} + {{- else }} + // pb []byte -> domain [N]byte + var {{ .Name }} {{ .GoType }} + if req.{{ pbField .Name }} != nil { + if len(req.{{ pbField .Name }}) != len({{ .Name }}) { return nil, fmt.Errorf("invalid length for {{ .Name }}: got %d want %d", len(req.{{ pbField .Name }}), len({{ .Name }})) } + copy({{ .Name }}[:], req.{{ pbField .Name }}) + } + {{- end }} + {{- else }} + // scalar single + var {{ .Name }} {{ .GoType }} = {{ .GoType }}(req.{{ pbField .Name }}) + {{- end }} + {{- end }} + + {{- if .Rep.Fields }} + res, err := s.impl.{{ .Name }}(ctx{{ range .Req.Fields }}, {{ .Name }}{{ end }}) + if err != nil { return nil, err } + rep := &pb.{{ .Rep.Name }}{} + {{- $rf := (index .Rep.Fields 0) }} + {{- if isMsg $rf.Type }} + {{- if $rf.Type.IsRepeated }} + if res != nil { + rep.{{ pbField $rf.Name }} = make([]*pb.{{ $rf.Type.Name }}, len(res)) + for i, v := range res { rep.{{ pbField $rf.Name }}[i] = {{ toPB $rf.Type.Name }}(v) } + } + {{- else }} + rep.{{ pbField $rf.Name }} = {{ toPB $rf.Type.Name }}(res) + {{- end }} + {{- else }} + {{- if $rf.Type.IsRepeated }} + {{- if eq $rf.Type.Name "bytes" }} + // domain []T -> pb [][]byte (T may be fixed [N]byte) + if res != nil { + rep.{{ pbField $rf.Name }} = make([][]byte, len(res)) + for i := range res { + {{- if isSliceType (sliceElem $rf) }} + rep.{{ pbField $rf.Name }}[i] = []byte(res[i]) + {{- else }} + rep.{{ pbField $rf.Name }}[i] = res[i][:] + {{- end }} + } + } + {{- else if $rf.DomainIsArray }} + // domain [N]T -> pb []scalar (array cannot be nil) + rep.{{ pbField $rf.Name }} = make([]{{ $rf.Type.Name }}, len(res)) + for i := range res { rep.{{ pbField $rf.Name }}[i] = {{ $rf.Type.Name }}(res[i]) } + {{- else }} + // domain slice -> pb slice (scalar) + if res != nil { + rep.{{ pbField $rf.Name }} = make([]{{ $rf.Type.Name }}, len(res)) + for i := range res { rep.{{ pbField $rf.Name }}[i] = {{ $rf.Type.Name }}(res[i]) } + } + {{- end }} + {{- else if eq $rf.Type.Name "bytes" }} + {{- if isSliceType $rf.GoType }} + rep.{{ pbField $rf.Name }} = res + {{- else }} + // domain [N]byte/uint8 -> pb []byte + rep.{{ pbField $rf.Name }} = res[:] + {{- end }} + {{- else }} + rep.{{ pbField $rf.Name }} = {{ $rf.Type.Name }}(res) + {{- end }} + {{- end }} + return rep, nil + {{- else }} + _, err := s.impl.{{ .Name }}(ctx{{ range .Req.Fields }}, {{ .Name }}{{ end }}) + if err != nil { return nil, err } + return &pb.{{ .Rep.Name }}{}, nil + {{- end }} +} +{{ end }} + +` + +var templatesRPCTest = `// Code generated by genrpc tests; DO NOT EDIT. +package {{ .ServiceName | lower }} + +import ( + "context" + "net" + "reflect" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + + pb "{{ .OptionGoPackage }}" + {{- range .ImportAliases }} + {{ .Alias }} "{{ .Path }}" + {{- end }} +) + +const bufSize = 1024 * 1024 + +// ---- fake impl that returns preloaded values per method ---- +type fakeImpl struct { + {{- range .Methods }} + ret_{{ .Name }} {{ if .Rep.Fields }}{{ (index .Rep.Fields 0).GoType }}{{ else }}struct{}{{ end }} + {{- end }} +} +{{- range .Methods }} +func (f *fakeImpl) {{ .Name }}(ctx context.Context{{ range .Req.Fields }}, {{ .Name }} {{ .GoType }}{{ end }}) ({{ if .Rep.Fields }}{{ (index .Rep.Fields 0).GoType }}{{ else }}struct{}{{ end }}, error) { + return f.ret_{{ .Name }}, nil +} +{{- end }} + +// ---- fixtures (depth-limited) for user-defined messages ---- +{{- range .UserMessages }} + +// Public, depth-1 convenience wrapper +func fixture_{{ .Name }}() {{ .DomainGo }} { return fixture_{{ .Name }}_depth(3) } + +func fixture_{{ .Name }}_depth(d int) {{ .DomainGo }} { + var out {{ .DomainGo }} + if d <= 0 { return out } + {{- range .Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + out.{{ domainFieldGoName .Name }} = make([]{{ sliceElem . }}, 2) + out.{{ domainFieldGoName .Name }}[0] = fixture_{{ .Type.Name }}_depth(d-1) + out.{{ domainFieldGoName .Name }}[1] = fixture_{{ .Type.Name }}_depth(d-1) + {{- else if isMsg .Type }} + out.{{ domainFieldGoName .Name }} = fixture_{{ .Type.Name }}_depth(d-1) + {{- else if .Type.IsRepeated }} + {{- if eq .Type.Name "bytes" }} + // []bytes (element may be []byte or fixed-size [N]byte-like) + out.{{ domainFieldGoName .Name }} = make([]{{ sliceElem . }}, 2) + {{- if isSliceType (sliceElem .) }} + out.{{ domainFieldGoName .Name }}[0] = []byte{1,2,3} + out.{{ domainFieldGoName .Name }}[1] = []byte{4,5,6} + {{- else }} + { + var e0 {{ sliceElem . }}; copy(e0[:], []byte{1,2,3,4}) + var e1 {{ sliceElem . }}; copy(e1[:], []byte{5,6,7,8}) + out.{{ domainFieldGoName .Name }}[0], out.{{ domainFieldGoName .Name }}[1] = e0, e1 + } + {{- end }} + {{- else }} + // []scalar + out.{{ domainFieldGoName .Name }} = []{{ sliceElem . }}{ {{ sliceElem . }}(1), {{ sliceElem . }}(2) } + {{- end }} + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + // []byte + out.{{ domainFieldGoName .Name }} = []byte{9,8,7,6} + {{- else }} + // fixed-size [N]byte-like (named array type) + { + var e {{ .GoType }} + copy(e[:], []byte{9,8,7,6}) + out.{{ domainFieldGoName .Name }} = e + } + {{- end }} + {{- else if eq .Type.Name "string" }} + out.{{ domainFieldGoName .Name }} = {{ .GoType }}("fixture") + {{- else if eq .Type.Name "bool" }} + out.{{ domainFieldGoName .Name }} = {{ .GoType }}(true) + {{- else if or (eq .Type.Name "double") (eq .Type.Name "float") }} + out.{{ domainFieldGoName .Name }} = {{ .GoType }}(1.5) + {{- else }} + // numeric scalar (cast) + out.{{ domainFieldGoName .Name }} = {{ .GoType }}(2) + {{- end }} + {{- end }} + return out +} +{{- end }} + +// ---- fixtures for interface oneofs (pick first case), depth-limited ---- +{{- range $io := .InterfaceOneofs }} + +// Public, depth-1 convenience wrapper +func fixture_{{$io.Name}}() {{$io.GoInterface}} { return fixture_{{$io.Name}}_depth(1) } + +func fixture_{{$io.Name}}_depth(d int) {{$io.GoInterface}} { + if d <= 0 { return nil } + {{- if gt (len $io.Cases) 0 }} + { + v := fixture_{{ (index $io.Cases 0).TypeName }}_depth(d-1) + return &v + } + {{- else }} + return nil + {{- end }} +} +{{- end }} + +// ---- single suite with subtests (one server/client for all) ---- +func Test_{{ .ServiceName }}_DomainRoundtrip(t *testing.T) { + impl := &fakeImpl{} + + lis := bufconn.Listen(bufSize) + s := grpc.NewServer() + pb.Register{{ .ServiceName }}Server(s, NewServer(impl)) + go func() { _ = s.Serve(lis) }() + t.Cleanup(func() { s.Stop(); _ = lis.Close() }) + + dialer := func(ctx context.Context, _ string) (net.Conn, error) { return lis.Dial() } + conn, err := grpc.DialContext(context.Background(), "bufnet", + grpc.WithContextDialer(dialer), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { t.Fatalf("dial: %v", err) } + t.Cleanup(func() { _ = conn.Close() }) + + c := NewClient(pb.New{{ .ServiceName }}Client(conn)) + + {{- range .Methods }} + t.Run("{{ .Name }} happy path", func(t *testing.T) { + ctx := context.Background() + {{- if .Rep.Fields }} + {{- $rf := (index .Rep.Fields 0) }} + {{- if and (isMsg $rf.Type) $rf.Type.IsRepeated }} + // want: []Message + want := make({{ $rf.GoType }}, 2) + want[0] = fixture_{{ $rf.Type.Name }}() + want[1] = fixture_{{ $rf.Type.Name }}() + {{- else if isMsg $rf.Type }} + // want: Message + want := fixture_{{ $rf.Type.Name }}() + {{- else if $rf.DomainIsArray }} + // want: [N]T (fixed-size array) + var want {{ $rf.GoType }} + for i := range want { want[i] = {{ sliceElem $rf }}(i+1) } + {{- else if and $rf.Type.IsRepeated (eq $rf.Type.Name "bytes") }} + // want: [][]byte (or []<[N]byte]-like) + want := make({{ $rf.GoType }}, 2) + {{- if isSliceType (sliceElem $rf) }} + want[0] = []byte{10,11} + want[1] = []byte{12,13} + {{- else }} + { + var e0 {{ sliceElem $rf }}; copy(e0[:], []byte{1,2,3,4}) + var e1 {{ sliceElem $rf }}; copy(e1[:], []byte{5,6,7,8}) + want[0], want[1] = e0, e1 + } + {{- end }} + {{- else if $rf.Type.IsRepeated }} + // want: []scalar + want := {{ $rf.GoType }}{ {{ sliceElem $rf }}(1), {{ sliceElem $rf }}(2), {{ sliceElem $rf }}(3) } + {{- else if eq $rf.Type.Name "bytes" }} + {{- if isSliceType $rf.GoType }} + // want: []byte + want := []byte{10,11} + {{- else }} + // want: [N]byte-like + var want {{ $rf.GoType }}; copy(want[:], []byte{10,11,12,13}) + {{- end }} + {{- else if eq $rf.Type.Name "string" }} + want := {{ $rf.GoType }}("want") + {{- else if eq $rf.Type.Name "bool" }} + want := {{ $rf.GoType }}(true) + {{- else if or (eq $rf.Type.Name "double") (eq $rf.Type.Name "float") }} + want := {{ $rf.GoType }}(2.5) + {{- else }} + want := {{ $rf.GoType }}(2) + {{- end }} + impl.ret_{{ .Name }} = want + {{- end }} + + {{- range .Req.Fields }} + {{- if and (isMsg .Type) .Type.IsRepeated }} + var {{ .Name }} {{ .GoType }} + {{ .Name }} = make({{ .GoType }}, 2) + {{ .Name }}[0] = fixture_{{ .Type.Name }}() + {{ .Name }}[1] = fixture_{{ .Type.Name }}() + {{- else if isMsg .Type }} + var {{ .Name }} {{ .GoType }} = fixture_{{ .Type.Name }}() + {{- else if and .Type.IsRepeated (eq .Type.Name "bytes") }} + var {{ .Name }} {{ .GoType }} + {{- if isSliceType (sliceElem .) }} + {{ .Name }} = [][]byte{ []byte{4,5,6}, []byte{7,8,9} } + {{- else }} + { + var e0 {{ sliceElem . }}; copy(e0[:], []byte{1,2,3,4}) + var e1 {{ sliceElem . }}; copy(e1[:], []byte{5,6,7,8}) + {{ .Name }} = []{{ sliceElem . }}{ e0, e1 } + } + {{- end }} + {{- else if .Type.IsRepeated }} + var {{ .Name }} {{ .GoType }} = {{ .GoType }}{ {{ sliceElem . }}(1), {{ sliceElem . }}(2) } + {{- else if eq .Type.Name "bytes" }} + {{- if isSliceType .GoType }} + var {{ .Name }} {{ .GoType }} = []byte{4,5,6} + {{- else }} + var {{ .Name }} {{ .GoType }}; copy({{ .Name }}[:], []byte{9,8,7,6}) + {{- end }} + {{- else if eq .Type.Name "string" }} + var {{ .Name }} {{ .GoType }} = {{ .GoType }}("in") + {{- else if eq .Type.Name "bool" }} + var {{ .Name }} {{ .GoType }} = {{ .GoType }}(true) + {{- else if or (eq .Type.Name "double") (eq .Type.Name "float") }} + var {{ .Name }} {{ .GoType }} = {{ .GoType }}(1.25) + {{- else }} + var {{ .Name }} {{ .GoType }} = {{ .GoType }}(3) + {{- end }} + {{- end }} + + {{- if .Rep.Fields }} + got, err := c.{{ .Name }}(ctx{{ range .Req.Fields }}, {{ .Name }}{{ end }}) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_{{ .Name }}) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_{{ .Name }}) + } + {{- else }} + _, err := c.{{ .Name }}(ctx{{ range .Req.Fields }}, {{ .Name }}{{ end }}) + if err != nil { t.Fatalf("rpc error: %v", err) } + {{- end }} + }) + {{- end }} +} +` diff --git a/pkg/loop/internal/generator/testdata/config.yaml b/pkg/loop/internal/generator/testdata/config.yaml new file mode 100644 index 000000000..f7a5d43c7 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/config.yaml @@ -0,0 +1,7 @@ +interfaces: + - go_type: github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives.Primitive + strategy: oneof + proto_container: Primitive + cases: + - go_type: github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives/evm.Address + - go_type: github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives/evm.EventByTopic diff --git a/pkg/loop/internal/generator/testdata/gen/pb/service.pb.go b/pkg/loop/internal/generator/testdata/gen/pb/service.pb.go new file mode 100644 index 000000000..8778b96d7 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/gen/pb/service.pb.go @@ -0,0 +1,1130 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v5.29.3 +// source: service.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Address struct { + state protoimpl.MessageState `protogen:"open.v1"` + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Address) Reset() { + *x = Address{} + mi := &file_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Address) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Address) ProtoMessage() {} + +func (x *Address) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Address.ProtoReflect.Descriptor instead. +func (*Address) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{0} +} + +func (x *Address) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +type BoolExpression struct { + state protoimpl.MessageState `protogen:"open.v1"` + Expressions []*Expression `protobuf:"bytes,1,rep,name=expressions,proto3" json:"expressions,omitempty"` + BoolOperator int64 `protobuf:"varint,2,opt,name=bool_operator,json=boolOperator,proto3" json:"bool_operator,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BoolExpression) Reset() { + *x = BoolExpression{} + mi := &file_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BoolExpression) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BoolExpression) ProtoMessage() {} + +func (x *BoolExpression) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BoolExpression.ProtoReflect.Descriptor instead. +func (*BoolExpression) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{1} +} + +func (x *BoolExpression) GetExpressions() []*Expression { + if x != nil { + return x.Expressions + } + return nil +} + +func (x *BoolExpression) GetBoolOperator() int64 { + if x != nil { + return x.BoolOperator + } + return 0 +} + +type EventByTopic struct { + state protoimpl.MessageState `protogen:"open.v1"` + Topic uint64 `protobuf:"varint,1,opt,name=topic,proto3" json:"topic,omitempty"` + HashedValueComparers []*HashedValueComparator `protobuf:"bytes,2,rep,name=hashed_value_comparers,json=hashedValueComparers,proto3" json:"hashed_value_comparers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EventByTopic) Reset() { + *x = EventByTopic{} + mi := &file_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EventByTopic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventByTopic) ProtoMessage() {} + +func (x *EventByTopic) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventByTopic.ProtoReflect.Descriptor instead. +func (*EventByTopic) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{2} +} + +func (x *EventByTopic) GetTopic() uint64 { + if x != nil { + return x.Topic + } + return 0 +} + +func (x *EventByTopic) GetHashedValueComparers() []*HashedValueComparator { + if x != nil { + return x.HashedValueComparers + } + return nil +} + +type Expression struct { + state protoimpl.MessageState `protogen:"open.v1"` + Primitive *Primitive `protobuf:"bytes,1,opt,name=primitive,proto3" json:"primitive,omitempty"` + BoolExpression *BoolExpression `protobuf:"bytes,2,opt,name=bool_expression,json=boolExpression,proto3" json:"bool_expression,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Expression) Reset() { + *x = Expression{} + mi := &file_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Expression) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Expression) ProtoMessage() {} + +func (x *Expression) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Expression.ProtoReflect.Descriptor instead. +func (*Expression) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{3} +} + +func (x *Expression) GetPrimitive() *Primitive { + if x != nil { + return x.Primitive + } + return nil +} + +func (x *Expression) GetBoolExpression() *BoolExpression { + if x != nil { + return x.BoolExpression + } + return nil +} + +type HashedValueComparator struct { + state protoimpl.MessageState `protogen:"open.v1"` + Values [][]byte `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + Operator int64 `protobuf:"varint,2,opt,name=operator,proto3" json:"operator,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HashedValueComparator) Reset() { + *x = HashedValueComparator{} + mi := &file_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HashedValueComparator) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HashedValueComparator) ProtoMessage() {} + +func (x *HashedValueComparator) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HashedValueComparator.ProtoReflect.Descriptor instead. +func (*HashedValueComparator) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{4} +} + +func (x *HashedValueComparator) GetValues() [][]byte { + if x != nil { + return x.Values + } + return nil +} + +func (x *HashedValueComparator) GetOperator() int64 { + if x != nil { + return x.Operator + } + return 0 +} + +type MyStruct struct { + state protoimpl.MessageState `protogen:"open.v1"` + B []byte `protobuf:"bytes,1,opt,name=b,proto3" json:"b,omitempty"` + Prim string `protobuf:"bytes,2,opt,name=prim,proto3" json:"prim,omitempty"` + Expr *Expression `protobuf:"bytes,3,opt,name=expr,proto3" json:"expr,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MyStruct) Reset() { + *x = MyStruct{} + mi := &file_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MyStruct) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MyStruct) ProtoMessage() {} + +func (x *MyStruct) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MyStruct.ProtoReflect.Descriptor instead. +func (*MyStruct) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{5} +} + +func (x *MyStruct) GetB() []byte { + if x != nil { + return x.B + } + return nil +} + +func (x *MyStruct) GetPrim() string { + if x != nil { + return x.Prim + } + return "" +} + +func (x *MyStruct) GetExpr() *Expression { + if x != nil { + return x.Expr + } + return nil +} + +type NestedStruct struct { + state protoimpl.MessageState `protogen:"open.v1"` + F1 *MyStruct `protobuf:"bytes,1,opt,name=f1,proto3" json:"f1,omitempty"` + F2 *MyStruct `protobuf:"bytes,2,opt,name=f2,proto3" json:"f2,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NestedStruct) Reset() { + *x = NestedStruct{} + mi := &file_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NestedStruct) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NestedStruct) ProtoMessage() {} + +func (x *NestedStruct) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NestedStruct.ProtoReflect.Descriptor instead. +func (*NestedStruct) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{6} +} + +func (x *NestedStruct) GetF1() *MyStruct { + if x != nil { + return x.F1 + } + return nil +} + +func (x *NestedStruct) GetF2() *MyStruct { + if x != nil { + return x.F2 + } + return nil +} + +type Primitive struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Kind: + // + // *Primitive_Address + // *Primitive_EventByTopic + Kind isPrimitive_Kind `protobuf_oneof:"kind"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Primitive) Reset() { + *x = Primitive{} + mi := &file_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Primitive) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Primitive) ProtoMessage() {} + +func (x *Primitive) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Primitive.ProtoReflect.Descriptor instead. +func (*Primitive) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{7} +} + +func (x *Primitive) GetKind() isPrimitive_Kind { + if x != nil { + return x.Kind + } + return nil +} + +func (x *Primitive) GetAddress() *Address { + if x != nil { + if x, ok := x.Kind.(*Primitive_Address); ok { + return x.Address + } + } + return nil +} + +func (x *Primitive) GetEventByTopic() *EventByTopic { + if x != nil { + if x, ok := x.Kind.(*Primitive_EventByTopic); ok { + return x.EventByTopic + } + } + return nil +} + +type isPrimitive_Kind interface { + isPrimitive_Kind() +} + +type Primitive_Address struct { + Address *Address `protobuf:"bytes,1,opt,name=address,proto3,oneof"` +} + +type Primitive_EventByTopic struct { + EventByTopic *EventByTopic `protobuf:"bytes,2,opt,name=eventByTopic,proto3,oneof"` +} + +func (*Primitive_Address) isPrimitive_Kind() {} + +func (*Primitive_EventByTopic) isPrimitive_Kind() {} + +type GetBytesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + B []byte `protobuf:"bytes,1,opt,name=b,proto3" json:"b,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBytesRequest) Reset() { + *x = GetBytesRequest{} + mi := &file_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBytesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBytesRequest) ProtoMessage() {} + +func (x *GetBytesRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBytesRequest.ProtoReflect.Descriptor instead. +func (*GetBytesRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{8} +} + +func (x *GetBytesRequest) GetB() []byte { + if x != nil { + return x.B + } + return nil +} + +type GetBytesReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result []byte `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBytesReply) Reset() { + *x = GetBytesReply{} + mi := &file_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBytesReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBytesReply) ProtoMessage() {} + +func (x *GetBytesReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBytesReply.ProtoReflect.Descriptor instead. +func (*GetBytesReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{9} +} + +func (x *GetBytesReply) GetResult() []byte { + if x != nil { + return x.Result + } + return nil +} + +type GetIntRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntRequest) Reset() { + *x = GetIntRequest{} + mi := &file_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntRequest) ProtoMessage() {} + +func (x *GetIntRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntRequest.ProtoReflect.Descriptor instead. +func (*GetIntRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{10} +} + +type GetIntReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result uint64 `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntReply) Reset() { + *x = GetIntReply{} + mi := &file_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntReply) ProtoMessage() {} + +func (x *GetIntReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntReply.ProtoReflect.Descriptor instead. +func (*GetIntReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{11} +} + +func (x *GetIntReply) GetResult() uint64 { + if x != nil { + return x.Result + } + return 0 +} + +type GetIntArrRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntArrRequest) Reset() { + *x = GetIntArrRequest{} + mi := &file_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntArrRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntArrRequest) ProtoMessage() {} + +func (x *GetIntArrRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntArrRequest.ProtoReflect.Descriptor instead. +func (*GetIntArrRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{12} +} + +type GetIntArrReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result []int64 `protobuf:"varint,1,rep,packed,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntArrReply) Reset() { + *x = GetIntArrReply{} + mi := &file_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntArrReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntArrReply) ProtoMessage() {} + +func (x *GetIntArrReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntArrReply.ProtoReflect.Descriptor instead. +func (*GetIntArrReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{13} +} + +func (x *GetIntArrReply) GetResult() []int64 { + if x != nil { + return x.Result + } + return nil +} + +type GetIntSliceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntSliceRequest) Reset() { + *x = GetIntSliceRequest{} + mi := &file_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntSliceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntSliceRequest) ProtoMessage() {} + +func (x *GetIntSliceRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntSliceRequest.ProtoReflect.Descriptor instead. +func (*GetIntSliceRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{14} +} + +type GetIntSliceReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result []int64 `protobuf:"varint,1,rep,packed,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIntSliceReply) Reset() { + *x = GetIntSliceReply{} + mi := &file_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIntSliceReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntSliceReply) ProtoMessage() {} + +func (x *GetIntSliceReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntSliceReply.ProtoReflect.Descriptor instead. +func (*GetIntSliceReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{15} +} + +func (x *GetIntSliceReply) GetResult() []int64 { + if x != nil { + return x.Result + } + return nil +} + +type SendNestedRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ns *NestedStruct `protobuf:"bytes,1,opt,name=ns,proto3" json:"ns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendNestedRequest) Reset() { + *x = SendNestedRequest{} + mi := &file_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendNestedRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNestedRequest) ProtoMessage() {} + +func (x *SendNestedRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendNestedRequest.ProtoReflect.Descriptor instead. +func (*SendNestedRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{16} +} + +func (x *SendNestedRequest) GetNs() *NestedStruct { + if x != nil { + return x.Ns + } + return nil +} + +type SendNestedReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *NestedStruct `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendNestedReply) Reset() { + *x = SendNestedReply{} + mi := &file_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendNestedReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNestedReply) ProtoMessage() {} + +func (x *SendNestedReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendNestedReply.ProtoReflect.Descriptor instead. +func (*SendNestedReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{17} +} + +func (x *SendNestedReply) GetResult() *NestedStruct { + if x != nil { + return x.Result + } + return nil +} + +type SendStructRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ms *MyStruct `protobuf:"bytes,1,opt,name=ms,proto3" json:"ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendStructRequest) Reset() { + *x = SendStructRequest{} + mi := &file_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendStructRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendStructRequest) ProtoMessage() {} + +func (x *SendStructRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendStructRequest.ProtoReflect.Descriptor instead. +func (*SendStructRequest) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{18} +} + +func (x *SendStructRequest) GetMs() *MyStruct { + if x != nil { + return x.Ms + } + return nil +} + +type SendStructReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *MyStruct `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendStructReply) Reset() { + *x = SendStructReply{} + mi := &file_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendStructReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendStructReply) ProtoMessage() {} + +func (x *SendStructReply) ProtoReflect() protoreflect.Message { + mi := &file_service_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendStructReply.ProtoReflect.Descriptor instead. +func (*SendStructReply) Descriptor() ([]byte, []int) { + return file_service_proto_rawDescGZIP(), []int{19} +} + +func (x *SendStructReply) GetResult() *MyStruct { + if x != nil { + return x.Result + } + return nil +} + +var File_service_proto protoreflect.FileDescriptor + +const file_service_proto_rawDesc = "" + + "\n" + + "\rservice.proto\x12\tloop.test\"#\n" + + "\aAddress\x12\x18\n" + + "\aaddress\x18\x01 \x01(\fR\aaddress\"n\n" + + "\x0eBoolExpression\x127\n" + + "\vexpressions\x18\x01 \x03(\v2\x15.loop.test.ExpressionR\vexpressions\x12#\n" + + "\rbool_operator\x18\x02 \x01(\x03R\fboolOperator\"|\n" + + "\fEventByTopic\x12\x14\n" + + "\x05topic\x18\x01 \x01(\x04R\x05topic\x12V\n" + + "\x16hashed_value_comparers\x18\x02 \x03(\v2 .loop.test.HashedValueComparatorR\x14hashedValueComparers\"\x84\x01\n" + + "\n" + + "Expression\x122\n" + + "\tprimitive\x18\x01 \x01(\v2\x14.loop.test.PrimitiveR\tprimitive\x12B\n" + + "\x0fbool_expression\x18\x02 \x01(\v2\x19.loop.test.BoolExpressionR\x0eboolExpression\"K\n" + + "\x15HashedValueComparator\x12\x16\n" + + "\x06values\x18\x01 \x03(\fR\x06values\x12\x1a\n" + + "\boperator\x18\x02 \x01(\x03R\boperator\"W\n" + + "\bMyStruct\x12\f\n" + + "\x01b\x18\x01 \x01(\fR\x01b\x12\x12\n" + + "\x04prim\x18\x02 \x01(\tR\x04prim\x12)\n" + + "\x04expr\x18\x03 \x01(\v2\x15.loop.test.ExpressionR\x04expr\"X\n" + + "\fNestedStruct\x12#\n" + + "\x02f1\x18\x01 \x01(\v2\x13.loop.test.MyStructR\x02f1\x12#\n" + + "\x02f2\x18\x02 \x01(\v2\x13.loop.test.MyStructR\x02f2\"\x82\x01\n" + + "\tPrimitive\x12.\n" + + "\aaddress\x18\x01 \x01(\v2\x12.loop.test.AddressH\x00R\aaddress\x12=\n" + + "\feventByTopic\x18\x02 \x01(\v2\x17.loop.test.EventByTopicH\x00R\feventByTopicB\x06\n" + + "\x04kind\"\x1f\n" + + "\x0fGetBytesRequest\x12\f\n" + + "\x01b\x18\x01 \x01(\fR\x01b\"'\n" + + "\rGetBytesReply\x12\x16\n" + + "\x06result\x18\x01 \x01(\fR\x06result\"\x0f\n" + + "\rGetIntRequest\"%\n" + + "\vGetIntReply\x12\x16\n" + + "\x06result\x18\x01 \x01(\x04R\x06result\"\x12\n" + + "\x10GetIntArrRequest\"(\n" + + "\x0eGetIntArrReply\x12\x16\n" + + "\x06result\x18\x01 \x03(\x03R\x06result\"\x14\n" + + "\x12GetIntSliceRequest\"*\n" + + "\x10GetIntSliceReply\x12\x16\n" + + "\x06result\x18\x01 \x03(\x03R\x06result\"<\n" + + "\x11SendNestedRequest\x12'\n" + + "\x02ns\x18\x01 \x01(\v2\x17.loop.test.NestedStructR\x02ns\"B\n" + + "\x0fSendNestedReply\x12/\n" + + "\x06result\x18\x01 \x01(\v2\x17.loop.test.NestedStructR\x06result\"8\n" + + "\x11SendStructRequest\x12#\n" + + "\x02ms\x18\x01 \x01(\v2\x13.loop.test.MyStructR\x02ms\">\n" + + "\x0fSendStructReply\x12+\n" + + "\x06result\x18\x01 \x01(\v2\x13.loop.test.MyStructR\x06result2\xa4\x03\n" + + "\x04Test\x12@\n" + + "\bGetBytes\x12\x1a.loop.test.GetBytesRequest\x1a\x18.loop.test.GetBytesReply\x12:\n" + + "\x06GetInt\x12\x18.loop.test.GetIntRequest\x1a\x16.loop.test.GetIntReply\x12C\n" + + "\tGetIntArr\x12\x1b.loop.test.GetIntArrRequest\x1a\x19.loop.test.GetIntArrReply\x12I\n" + + "\vGetIntSlice\x12\x1d.loop.test.GetIntSliceRequest\x1a\x1b.loop.test.GetIntSliceReply\x12F\n" + + "\n" + + "SendNested\x12\x1c.loop.test.SendNestedRequest\x1a\x1a.loop.test.SendNestedReply\x12F\n" + + "\n" + + "SendStruct\x12\x1c.loop.test.SendStructRequest\x1a\x1a.loop.test.SendStructReplyBZZXgithub.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/pbb\x06proto3" + +var ( + file_service_proto_rawDescOnce sync.Once + file_service_proto_rawDescData []byte +) + +func file_service_proto_rawDescGZIP() []byte { + file_service_proto_rawDescOnce.Do(func() { + file_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_service_proto_rawDesc), len(file_service_proto_rawDesc))) + }) + return file_service_proto_rawDescData +} + +var file_service_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_service_proto_goTypes = []any{ + (*Address)(nil), // 0: loop.test.Address + (*BoolExpression)(nil), // 1: loop.test.BoolExpression + (*EventByTopic)(nil), // 2: loop.test.EventByTopic + (*Expression)(nil), // 3: loop.test.Expression + (*HashedValueComparator)(nil), // 4: loop.test.HashedValueComparator + (*MyStruct)(nil), // 5: loop.test.MyStruct + (*NestedStruct)(nil), // 6: loop.test.NestedStruct + (*Primitive)(nil), // 7: loop.test.Primitive + (*GetBytesRequest)(nil), // 8: loop.test.GetBytesRequest + (*GetBytesReply)(nil), // 9: loop.test.GetBytesReply + (*GetIntRequest)(nil), // 10: loop.test.GetIntRequest + (*GetIntReply)(nil), // 11: loop.test.GetIntReply + (*GetIntArrRequest)(nil), // 12: loop.test.GetIntArrRequest + (*GetIntArrReply)(nil), // 13: loop.test.GetIntArrReply + (*GetIntSliceRequest)(nil), // 14: loop.test.GetIntSliceRequest + (*GetIntSliceReply)(nil), // 15: loop.test.GetIntSliceReply + (*SendNestedRequest)(nil), // 16: loop.test.SendNestedRequest + (*SendNestedReply)(nil), // 17: loop.test.SendNestedReply + (*SendStructRequest)(nil), // 18: loop.test.SendStructRequest + (*SendStructReply)(nil), // 19: loop.test.SendStructReply +} +var file_service_proto_depIdxs = []int32{ + 3, // 0: loop.test.BoolExpression.expressions:type_name -> loop.test.Expression + 4, // 1: loop.test.EventByTopic.hashed_value_comparers:type_name -> loop.test.HashedValueComparator + 7, // 2: loop.test.Expression.primitive:type_name -> loop.test.Primitive + 1, // 3: loop.test.Expression.bool_expression:type_name -> loop.test.BoolExpression + 3, // 4: loop.test.MyStruct.expr:type_name -> loop.test.Expression + 5, // 5: loop.test.NestedStruct.f1:type_name -> loop.test.MyStruct + 5, // 6: loop.test.NestedStruct.f2:type_name -> loop.test.MyStruct + 0, // 7: loop.test.Primitive.address:type_name -> loop.test.Address + 2, // 8: loop.test.Primitive.eventByTopic:type_name -> loop.test.EventByTopic + 6, // 9: loop.test.SendNestedRequest.ns:type_name -> loop.test.NestedStruct + 6, // 10: loop.test.SendNestedReply.result:type_name -> loop.test.NestedStruct + 5, // 11: loop.test.SendStructRequest.ms:type_name -> loop.test.MyStruct + 5, // 12: loop.test.SendStructReply.result:type_name -> loop.test.MyStruct + 8, // 13: loop.test.Test.GetBytes:input_type -> loop.test.GetBytesRequest + 10, // 14: loop.test.Test.GetInt:input_type -> loop.test.GetIntRequest + 12, // 15: loop.test.Test.GetIntArr:input_type -> loop.test.GetIntArrRequest + 14, // 16: loop.test.Test.GetIntSlice:input_type -> loop.test.GetIntSliceRequest + 16, // 17: loop.test.Test.SendNested:input_type -> loop.test.SendNestedRequest + 18, // 18: loop.test.Test.SendStruct:input_type -> loop.test.SendStructRequest + 9, // 19: loop.test.Test.GetBytes:output_type -> loop.test.GetBytesReply + 11, // 20: loop.test.Test.GetInt:output_type -> loop.test.GetIntReply + 13, // 21: loop.test.Test.GetIntArr:output_type -> loop.test.GetIntArrReply + 15, // 22: loop.test.Test.GetIntSlice:output_type -> loop.test.GetIntSliceReply + 17, // 23: loop.test.Test.SendNested:output_type -> loop.test.SendNestedReply + 19, // 24: loop.test.Test.SendStruct:output_type -> loop.test.SendStructReply + 19, // [19:25] is the sub-list for method output_type + 13, // [13:19] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_service_proto_init() } +func file_service_proto_init() { + if File_service_proto != nil { + return + } + file_service_proto_msgTypes[7].OneofWrappers = []any{ + (*Primitive_Address)(nil), + (*Primitive_EventByTopic)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_service_proto_rawDesc), len(file_service_proto_rawDesc)), + NumEnums: 0, + NumMessages: 20, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_service_proto_goTypes, + DependencyIndexes: file_service_proto_depIdxs, + MessageInfos: file_service_proto_msgTypes, + }.Build() + File_service_proto = out.File + file_service_proto_goTypes = nil + file_service_proto_depIdxs = nil +} diff --git a/pkg/loop/internal/generator/testdata/gen/pb/service.proto b/pkg/loop/internal/generator/testdata/gen/pb/service.proto new file mode 100644 index 000000000..3244d02d1 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/gen/pb/service.proto @@ -0,0 +1,86 @@ +syntax = "proto3"; +option go_package = "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/pb"; +package loop.test; + + +message Address { +bytes address = 1; +} +message BoolExpression { +repeated Expression expressions = 1; +int64 bool_operator = 2; +} +message EventByTopic { +uint64 topic = 1; +repeated HashedValueComparator hashed_value_comparers = 2; +} +message Expression { +Primitive primitive = 1; +BoolExpression bool_expression = 2; +} +message HashedValueComparator { +repeated bytes values = 1; +int64 operator = 2; +} +message MyStruct { +bytes b = 1; +string prim = 2; +Expression expr = 3; +} +message NestedStruct { +MyStruct f1 = 1; +MyStruct f2 = 2; +} + + +message Primitive { + oneof kind { + Address address = 1; + EventByTopic eventByTopic = 2; + } +} + + +service Test { + rpc GetBytes(GetBytesRequest) returns (GetBytesReply); + rpc GetInt(GetIntRequest) returns (GetIntReply); + rpc GetIntArr(GetIntArrRequest) returns (GetIntArrReply); + rpc GetIntSlice(GetIntSliceRequest) returns (GetIntSliceReply); + rpc SendNested(SendNestedRequest) returns (SendNestedReply); + rpc SendStruct(SendStructRequest) returns (SendStructReply); +} + + +message GetBytesRequest { + bytes b = 1; +} +message GetBytesReply { + bytes result = 1; +} +message GetIntRequest { +} +message GetIntReply { + uint64 result = 1; +} +message GetIntArrRequest { +} +message GetIntArrReply { + repeated int64 result = 1; +} +message GetIntSliceRequest { +} +message GetIntSliceReply { + repeated int64 result = 1; +} +message SendNestedRequest { + NestedStruct ns = 1; +} +message SendNestedReply { + NestedStruct result = 1; +} +message SendStructRequest { + MyStruct ms = 1; +} +message SendStructReply { + MyStruct result = 1; +} \ No newline at end of file diff --git a/pkg/loop/internal/generator/testdata/gen/pb/service_grpc.pb.go b/pkg/loop/internal/generator/testdata/gen/pb/service_grpc.pb.go new file mode 100644 index 000000000..1916a4150 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/gen/pb/service_grpc.pb.go @@ -0,0 +1,311 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: service.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Test_GetBytes_FullMethodName = "/loop.test.Test/GetBytes" + Test_GetInt_FullMethodName = "/loop.test.Test/GetInt" + Test_GetIntArr_FullMethodName = "/loop.test.Test/GetIntArr" + Test_GetIntSlice_FullMethodName = "/loop.test.Test/GetIntSlice" + Test_SendNested_FullMethodName = "/loop.test.Test/SendNested" + Test_SendStruct_FullMethodName = "/loop.test.Test/SendStruct" +) + +// TestClient is the client API for Test service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TestClient interface { + GetBytes(ctx context.Context, in *GetBytesRequest, opts ...grpc.CallOption) (*GetBytesReply, error) + GetInt(ctx context.Context, in *GetIntRequest, opts ...grpc.CallOption) (*GetIntReply, error) + GetIntArr(ctx context.Context, in *GetIntArrRequest, opts ...grpc.CallOption) (*GetIntArrReply, error) + GetIntSlice(ctx context.Context, in *GetIntSliceRequest, opts ...grpc.CallOption) (*GetIntSliceReply, error) + SendNested(ctx context.Context, in *SendNestedRequest, opts ...grpc.CallOption) (*SendNestedReply, error) + SendStruct(ctx context.Context, in *SendStructRequest, opts ...grpc.CallOption) (*SendStructReply, error) +} + +type testClient struct { + cc grpc.ClientConnInterface +} + +func NewTestClient(cc grpc.ClientConnInterface) TestClient { + return &testClient{cc} +} + +func (c *testClient) GetBytes(ctx context.Context, in *GetBytesRequest, opts ...grpc.CallOption) (*GetBytesReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetBytesReply) + err := c.cc.Invoke(ctx, Test_GetBytes_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) GetInt(ctx context.Context, in *GetIntRequest, opts ...grpc.CallOption) (*GetIntReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetIntReply) + err := c.cc.Invoke(ctx, Test_GetInt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) GetIntArr(ctx context.Context, in *GetIntArrRequest, opts ...grpc.CallOption) (*GetIntArrReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetIntArrReply) + err := c.cc.Invoke(ctx, Test_GetIntArr_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) GetIntSlice(ctx context.Context, in *GetIntSliceRequest, opts ...grpc.CallOption) (*GetIntSliceReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetIntSliceReply) + err := c.cc.Invoke(ctx, Test_GetIntSlice_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) SendNested(ctx context.Context, in *SendNestedRequest, opts ...grpc.CallOption) (*SendNestedReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SendNestedReply) + err := c.cc.Invoke(ctx, Test_SendNested_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) SendStruct(ctx context.Context, in *SendStructRequest, opts ...grpc.CallOption) (*SendStructReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SendStructReply) + err := c.cc.Invoke(ctx, Test_SendStruct_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TestServer is the server API for Test service. +// All implementations must embed UnimplementedTestServer +// for forward compatibility. +type TestServer interface { + GetBytes(context.Context, *GetBytesRequest) (*GetBytesReply, error) + GetInt(context.Context, *GetIntRequest) (*GetIntReply, error) + GetIntArr(context.Context, *GetIntArrRequest) (*GetIntArrReply, error) + GetIntSlice(context.Context, *GetIntSliceRequest) (*GetIntSliceReply, error) + SendNested(context.Context, *SendNestedRequest) (*SendNestedReply, error) + SendStruct(context.Context, *SendStructRequest) (*SendStructReply, error) + mustEmbedUnimplementedTestServer() +} + +// UnimplementedTestServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedTestServer struct{} + +func (UnimplementedTestServer) GetBytes(context.Context, *GetBytesRequest) (*GetBytesReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBytes not implemented") +} +func (UnimplementedTestServer) GetInt(context.Context, *GetIntRequest) (*GetIntReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInt not implemented") +} +func (UnimplementedTestServer) GetIntArr(context.Context, *GetIntArrRequest) (*GetIntArrReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIntArr not implemented") +} +func (UnimplementedTestServer) GetIntSlice(context.Context, *GetIntSliceRequest) (*GetIntSliceReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIntSlice not implemented") +} +func (UnimplementedTestServer) SendNested(context.Context, *SendNestedRequest) (*SendNestedReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendNested not implemented") +} +func (UnimplementedTestServer) SendStruct(context.Context, *SendStructRequest) (*SendStructReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendStruct not implemented") +} +func (UnimplementedTestServer) mustEmbedUnimplementedTestServer() {} +func (UnimplementedTestServer) testEmbeddedByValue() {} + +// UnsafeTestServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TestServer will +// result in compilation errors. +type UnsafeTestServer interface { + mustEmbedUnimplementedTestServer() +} + +func RegisterTestServer(s grpc.ServiceRegistrar, srv TestServer) { + // If the following call pancis, it indicates UnimplementedTestServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Test_ServiceDesc, srv) +} + +func _Test_GetBytes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBytesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).GetBytes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_GetBytes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).GetBytes(ctx, req.(*GetBytesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_GetInt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIntRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).GetInt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_GetInt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).GetInt(ctx, req.(*GetIntRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_GetIntArr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIntArrRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).GetIntArr(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_GetIntArr_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).GetIntArr(ctx, req.(*GetIntArrRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_GetIntSlice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIntSliceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).GetIntSlice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_GetIntSlice_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).GetIntSlice(ctx, req.(*GetIntSliceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_SendNested_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendNestedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).SendNested(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_SendNested_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).SendNested(ctx, req.(*SendNestedRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_SendStruct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendStructRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).SendStruct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Test_SendStruct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).SendStruct(ctx, req.(*SendStructRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Test_ServiceDesc is the grpc.ServiceDesc for Test service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Test_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "loop.test.Test", + HandlerType: (*TestServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetBytes", + Handler: _Test_GetBytes_Handler, + }, + { + MethodName: "GetInt", + Handler: _Test_GetInt_Handler, + }, + { + MethodName: "GetIntArr", + Handler: _Test_GetIntArr_Handler, + }, + { + MethodName: "GetIntSlice", + Handler: _Test_GetIntSlice_Handler, + }, + { + MethodName: "SendNested", + Handler: _Test_SendNested_Handler, + }, + { + MethodName: "SendStruct", + Handler: _Test_SendStruct_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "service.proto", +} diff --git a/pkg/loop/internal/generator/testdata/gen/wrap/rpc.go b/pkg/loop/internal/generator/testdata/gen/wrap/rpc.go new file mode 100644 index 000000000..ed38fd811 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/gen/wrap/rpc.go @@ -0,0 +1,389 @@ + +// Code generated by genrpc; DO NOT EDIT. +package test + +import ( + "context" + "fmt" + + pb "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/pb" + testdata "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata" + evm "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + evm2 "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives/evm" +) + +type Client struct{ rpc pb.TestClient } + +func NewClient(r pb.TestClient) *Client { return &Client{rpc: r} } + + +func (c *Client) GetBytes( + ctx context.Context, b []byte, +) ([]byte, error) { + + req := &pb.GetBytesRequest{ + B: b, + } + + rep, err := c.rpc.GetBytes(ctx, req) + if err != nil { var zero []byte; return zero, err + } + // pb []byte -> domain []byte + return rep.Result, nil +} + +func (c *Client) GetInt( + ctx context.Context, +) (uint64, error) { + + req := &pb.GetIntRequest{ + } + + rep, err := c.rpc.GetInt(ctx, req) + if err != nil { var zero uint64; return zero, err + } + return uint64(rep.Result), nil +} + +func (c *Client) GetIntArr( + ctx context.Context, +) ([10]int, error) { + + req := &pb.GetIntArrRequest{ + } + + rep, err := c.rpc.GetIntArr(ctx, req) + if err != nil { var zero [10]int; return zero, err + } + // pb []scalar -> domain [N]T + var out [10]int + if rep.Result != nil { + for i := range out { + if i < len(rep.Result) { + out[i] = int(rep.Result[i]) + } + } + } + return out, nil +} + +func (c *Client) GetIntSlice( + ctx context.Context, +) ([]int, error) { + + req := &pb.GetIntSliceRequest{ + } + + rep, err := c.rpc.GetIntSlice(ctx, req) + if err != nil { var zero []int; return zero, err + } + // pb []scalar -> domain []scalar (cast if needed) + if rep.Result == nil { var zero []int; return zero, nil } + out := make([]int, len(rep.Result)) + for i := range rep.Result { + out[i] = int(rep.Result[i]) + } + return out, nil +} + +func (c *Client) SendNested( + ctx context.Context, ns testdata.NestedStruct, +) (testdata.NestedStruct, error) { + + req := &pb.SendNestedRequest{ + Ns: toPB_NestedStruct(ns), + } + + rep, err := c.rpc.SendNested(ctx, req) + if err != nil { var zero testdata.NestedStruct; return zero, err + } + return fromPB_NestedStruct(rep.Result), nil +} + +func (c *Client) SendStruct( + ctx context.Context, ms testdata.MyStruct, +) (testdata.MyStruct, error) { + + req := &pb.SendStructRequest{ + Ms: toPB_MyStruct(ms), + } + + rep, err := c.rpc.SendStruct(ctx, req) + if err != nil { var zero testdata.MyStruct; return zero, err + } + return fromPB_MyStruct(rep.Result), nil +} + + +// ---- pb<->domain converters for user messages ---- +func toPB_Address(in evm2.Address) *pb.Address { + out := &pb.Address{} + // domain [N]byte/uint8 -> pb []byte + out.Address = in.Address[:] + return out +} + +func fromPB_Address(in *pb.Address) evm2.Address { + var out evm2.Address + if in == nil { return out } + // pb []byte -> domain [N]byte/uint8 + if in.Address != nil { + copy(out.Address[:], in.Address) + } + return out +} +func toPB_BoolExpression(in query.BoolExpression) *pb.BoolExpression { + out := &pb.BoolExpression{} + if in.Expressions != nil { + out.Expressions = make([]*pb.Expression, len(in.Expressions)) + for i, v := range in.Expressions { out.Expressions[i] = toPB_Expression(v) } + } + out.BoolOperator = int64(in.BoolOperator) + return out +} + +func fromPB_BoolExpression(in *pb.BoolExpression) query.BoolExpression { + var out query.BoolExpression + if in == nil { return out } + if in.Expressions != nil { + out.Expressions = make([]query.Expression, len(in.Expressions)) + for i, v := range in.Expressions { + out.Expressions[i] = fromPB_Expression(v) + } + } + out.BoolOperator = query.BoolOperator(in.BoolOperator) + return out +} +func toPB_EventByTopic(in evm2.EventByTopic) *pb.EventByTopic { + out := &pb.EventByTopic{} + out.Topic = uint64(in.Topic) + if in.HashedValueComparers != nil { + out.HashedValueComparers = make([]*pb.HashedValueComparator, len(in.HashedValueComparers)) + for i, v := range in.HashedValueComparers { out.HashedValueComparers[i] = toPB_HashedValueComparator(v) } + } + return out +} + +func fromPB_EventByTopic(in *pb.EventByTopic) evm2.EventByTopic { + var out evm2.EventByTopic + if in == nil { return out } + out.Topic = uint64(in.Topic) + if in.HashedValueComparers != nil { + out.HashedValueComparers = make([]evm2.HashedValueComparator, len(in.HashedValueComparers)) + for i, v := range in.HashedValueComparers { + out.HashedValueComparers[i] = fromPB_HashedValueComparator(v) + } + } + return out +} +func toPB_Expression(in query.Expression) *pb.Expression { + out := &pb.Expression{} + out.Primitive = toPB_Primitive(in.Primitive) + out.BoolExpression = toPB_BoolExpression(in.BoolExpression) + return out +} + +func fromPB_Expression(in *pb.Expression) query.Expression { + var out query.Expression + if in == nil { return out } + out.Primitive = fromPB_Primitive(in.Primitive) + out.BoolExpression = fromPB_BoolExpression(in.BoolExpression) + return out +} +func toPB_HashedValueComparator(in evm2.HashedValueComparator) *pb.HashedValueComparator { + out := &pb.HashedValueComparator{} + // domain []T -> pb [][]byte (T may be fixed [N]byte) + if in.Values != nil { + out.Values = make([][]byte, len(in.Values)) + for i := range in.Values { + out.Values[i] = in.Values[i][:] + } + } + out.Operator = int64(in.Operator) + return out +} + +func fromPB_HashedValueComparator(in *pb.HashedValueComparator) evm2.HashedValueComparator { + var out evm2.HashedValueComparator + if in == nil { return out } + // pb [][]byte -> domain []T (T may be fixed [N]byte) + if in.Values != nil { + out.Values = make([]evm.Hash, len(in.Values)) + for i := range in.Values { + var e evm.Hash + // Length-check omitted here to keep converter pure; client/server paths enforce it and can return error. + copy(e[:], in.Values[i]) + out.Values[i] = e + } + } + out.Operator = primitives.ComparisonOperator(in.Operator) + return out +} +func toPB_MyStruct(in testdata.MyStruct) *pb.MyStruct { + out := &pb.MyStruct{} + out.B = in.B + out.Prim = string(in.Prim) + out.Expr = toPB_Expression(in.Expr) + return out +} + +func fromPB_MyStruct(in *pb.MyStruct) testdata.MyStruct { + var out testdata.MyStruct + if in == nil { return out } + // pb []byte -> domain []byte + out.B = in.B + out.Prim = primitives.ConfidenceLevel(in.Prim) + out.Expr = fromPB_Expression(in.Expr) + return out +} +func toPB_NestedStruct(in testdata.NestedStruct) *pb.NestedStruct { + out := &pb.NestedStruct{} + out.F1 = toPB_MyStruct(in.F1) + out.F2 = toPB_MyStruct(in.F2) + return out +} + +func fromPB_NestedStruct(in *pb.NestedStruct) testdata.NestedStruct { + var out testdata.NestedStruct + if in == nil { return out } + out.F1 = fromPB_MyStruct(in.F1) + out.F2 = fromPB_MyStruct(in.F2) + return out +} +func toPB_Primitive(in primitives.Primitive) *pb.Primitive { + if in == nil { return nil } + switch v := in.(type) { + case *evm2.Address: + return &pb.Primitive{ + Kind: &pb.Primitive_Address{ Address: toPB_Address(*v) }, + } + case *evm2.EventByTopic: + return &pb.Primitive{ + Kind: &pb.Primitive_EventByTopic{ EventByTopic: toPB_EventByTopic(*v) }, + } + default: + return nil + } +} + +func fromPB_Primitive(in *pb.Primitive) primitives.Primitive { + if in == nil { return nil } + switch k := in.Kind.(type) { + case *pb.Primitive_Address: + { + v := fromPB_Address(k.Address) + return &v + } + case *pb.Primitive_EventByTopic: + { + v := fromPB_EventByTopic(k.EventByTopic) + return &v + } + default: + return nil + } +} + +// ---- Optional helpers for fixed-size arrays (with length checks) ---- + +// toAddress converts a byte slice into an evm.Address with length check. +func toAddress(b []byte) (evm.Address, error) { + var out evm.Address + if len(b) != len(out) { return out, fmt.Errorf("invalid evm.Address length: got %d want %d", len(b), len(out)) } + copy(out[:], b) + return out, nil +} + +// toHash converts a byte slice into an evm.Hash with length check. +func toHash(b []byte) (evm.Hash, error) { + var out evm.Hash + if len(b) != len(out) { return out, fmt.Errorf("invalid evm.Hash length: got %d want %d", len(b), len(out)) } + copy(out[:], b) + return out, nil +} + +// server +type Server struct { + pb.UnimplementedTestServer + impl interface{ + GetBytes(ctx context.Context, b []byte) ([]byte, error) + GetInt(ctx context.Context) (uint64, error) + GetIntArr(ctx context.Context) ([10]int, error) + GetIntSlice(ctx context.Context) ([]int, error) + SendNested(ctx context.Context, ns testdata.NestedStruct) (testdata.NestedStruct, error) + SendStruct(ctx context.Context, ms testdata.MyStruct) (testdata.MyStruct, error) + } +} + +func NewServer(impl any) *Server { + return &Server{impl: impl.(interface{ + GetBytes(ctx context.Context, b []byte) ([]byte, error) + GetInt(ctx context.Context) (uint64, error) + GetIntArr(ctx context.Context) ([10]int, error) + GetIntSlice(ctx context.Context) ([]int, error) + SendNested(ctx context.Context, ns testdata.NestedStruct) (testdata.NestedStruct, error) + SendStruct(ctx context.Context, ms testdata.MyStruct) (testdata.MyStruct, error) + })} +} + + +func (s *Server) GetBytes(ctx context.Context, req *pb.GetBytesRequest) (*pb.GetBytesReply, error) { + var b []byte = req.B + res, err := s.impl.GetBytes(ctx, b) + if err != nil { return nil, err } + rep := &pb.GetBytesReply{} + rep.Result = res + return rep, nil +} + +func (s *Server) GetInt(ctx context.Context, req *pb.GetIntRequest) (*pb.GetIntReply, error) { + res, err := s.impl.GetInt(ctx) + if err != nil { return nil, err } + rep := &pb.GetIntReply{} + rep.Result = uint64(res) + return rep, nil +} + +func (s *Server) GetIntArr(ctx context.Context, req *pb.GetIntArrRequest) (*pb.GetIntArrReply, error) { + res, err := s.impl.GetIntArr(ctx) + if err != nil { return nil, err } + rep := &pb.GetIntArrReply{} + // domain [N]T -> pb []scalar (array cannot be nil) + rep.Result = make([]int64, len(res)) + for i := range res { rep.Result[i] = int64(res[i]) } + return rep, nil +} + +func (s *Server) GetIntSlice(ctx context.Context, req *pb.GetIntSliceRequest) (*pb.GetIntSliceReply, error) { + res, err := s.impl.GetIntSlice(ctx) + if err != nil { return nil, err } + rep := &pb.GetIntSliceReply{} + // domain slice -> pb slice (scalar) + if res != nil { + rep.Result = make([]int64, len(res)) + for i := range res { rep.Result[i] = int64(res[i]) } + } + return rep, nil +} + +func (s *Server) SendNested(ctx context.Context, req *pb.SendNestedRequest) (*pb.SendNestedReply, error) { + var ns testdata.NestedStruct = fromPB_NestedStruct(req.Ns) + res, err := s.impl.SendNested(ctx, ns) + if err != nil { return nil, err } + rep := &pb.SendNestedReply{} + rep.Result = toPB_NestedStruct(res) + return rep, nil +} + +func (s *Server) SendStruct(ctx context.Context, req *pb.SendStructRequest) (*pb.SendStructReply, error) { + var ms testdata.MyStruct = fromPB_MyStruct(req.Ms) + res, err := s.impl.SendStruct(ctx, ms) + if err != nil { return nil, err } + rep := &pb.SendStructReply{} + rep.Result = toPB_MyStruct(res) + return rep, nil +} + + diff --git a/pkg/loop/internal/generator/testdata/gen/wrap/rpc_test.go b/pkg/loop/internal/generator/testdata/gen/wrap/rpc_test.go new file mode 100644 index 000000000..3c6912dbf --- /dev/null +++ b/pkg/loop/internal/generator/testdata/gen/wrap/rpc_test.go @@ -0,0 +1,250 @@ +// Code generated by genrpc tests; DO NOT EDIT. +package test + +import ( + "context" + "net" + "reflect" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + + pb "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/pb" + testdata "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata" + evm "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + evm2 "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives/evm" +) + +const bufSize = 1024 * 1024 + +// ---- fake impl that returns preloaded values per method ---- +type fakeImpl struct { + ret_GetBytes []byte + ret_GetInt uint64 + ret_GetIntArr [10]int + ret_GetIntSlice []int + ret_SendNested testdata.NestedStruct + ret_SendStruct testdata.MyStruct +} +func (f *fakeImpl) GetBytes(ctx context.Context, b []byte) ([]byte, error) { + return f.ret_GetBytes, nil +} +func (f *fakeImpl) GetInt(ctx context.Context) (uint64, error) { + return f.ret_GetInt, nil +} +func (f *fakeImpl) GetIntArr(ctx context.Context) ([10]int, error) { + return f.ret_GetIntArr, nil +} +func (f *fakeImpl) GetIntSlice(ctx context.Context) ([]int, error) { + return f.ret_GetIntSlice, nil +} +func (f *fakeImpl) SendNested(ctx context.Context, ns testdata.NestedStruct) (testdata.NestedStruct, error) { + return f.ret_SendNested, nil +} +func (f *fakeImpl) SendStruct(ctx context.Context, ms testdata.MyStruct) (testdata.MyStruct, error) { + return f.ret_SendStruct, nil +} + +// ---- fixtures (depth-limited) for user-defined messages ---- + +// Public, depth-1 convenience wrapper +func fixture_Address() evm2.Address { return fixture_Address_depth(3) } + +func fixture_Address_depth(d int) evm2.Address { + var out evm2.Address + if d <= 0 { return out } + // fixed-size [N]byte-like (named array type) + { + var e evm.Address + copy(e[:], []byte{9,8,7,6}) + out.Address = e + } + return out +} + +// Public, depth-1 convenience wrapper +func fixture_BoolExpression() query.BoolExpression { return fixture_BoolExpression_depth(3) } + +func fixture_BoolExpression_depth(d int) query.BoolExpression { + var out query.BoolExpression + if d <= 0 { return out } + out.Expressions = make([]query.Expression, 2) + out.Expressions[0] = fixture_Expression_depth(d-1) + out.Expressions[1] = fixture_Expression_depth(d-1) + // numeric scalar (cast) + out.BoolOperator = query.BoolOperator(2) + return out +} + +// Public, depth-1 convenience wrapper +func fixture_EventByTopic() evm2.EventByTopic { return fixture_EventByTopic_depth(3) } + +func fixture_EventByTopic_depth(d int) evm2.EventByTopic { + var out evm2.EventByTopic + if d <= 0 { return out } + // numeric scalar (cast) + out.Topic = uint64(2) + out.HashedValueComparers = make([]evm2.HashedValueComparator, 2) + out.HashedValueComparers[0] = fixture_HashedValueComparator_depth(d-1) + out.HashedValueComparers[1] = fixture_HashedValueComparator_depth(d-1) + return out +} + +// Public, depth-1 convenience wrapper +func fixture_Expression() query.Expression { return fixture_Expression_depth(3) } + +func fixture_Expression_depth(d int) query.Expression { + var out query.Expression + if d <= 0 { return out } + out.Primitive = fixture_Primitive_depth(d-1) + out.BoolExpression = fixture_BoolExpression_depth(d-1) + return out +} + +// Public, depth-1 convenience wrapper +func fixture_HashedValueComparator() evm2.HashedValueComparator { return fixture_HashedValueComparator_depth(3) } + +func fixture_HashedValueComparator_depth(d int) evm2.HashedValueComparator { + var out evm2.HashedValueComparator + if d <= 0 { return out } + // []bytes (element may be []byte or fixed-size [N]byte-like) + out.Values = make([]evm.Hash, 2) + { + var e0 evm.Hash; copy(e0[:], []byte{1,2,3,4}) + var e1 evm.Hash; copy(e1[:], []byte{5,6,7,8}) + out.Values[0], out.Values[1] = e0, e1 + } + // numeric scalar (cast) + out.Operator = primitives.ComparisonOperator(2) + return out +} + +// Public, depth-1 convenience wrapper +func fixture_MyStruct() testdata.MyStruct { return fixture_MyStruct_depth(3) } + +func fixture_MyStruct_depth(d int) testdata.MyStruct { + var out testdata.MyStruct + if d <= 0 { return out } + // []byte + out.B = []byte{9,8,7,6} + out.Prim = primitives.ConfidenceLevel("fixture") + out.Expr = fixture_Expression_depth(d-1) + return out +} + +// Public, depth-1 convenience wrapper +func fixture_NestedStruct() testdata.NestedStruct { return fixture_NestedStruct_depth(3) } + +func fixture_NestedStruct_depth(d int) testdata.NestedStruct { + var out testdata.NestedStruct + if d <= 0 { return out } + out.F1 = fixture_MyStruct_depth(d-1) + out.F2 = fixture_MyStruct_depth(d-1) + return out +} + +// ---- fixtures for interface oneofs (pick first case), depth-limited ---- + +// Public, depth-1 convenience wrapper +func fixture_Primitive() primitives.Primitive { return fixture_Primitive_depth(1) } + +func fixture_Primitive_depth(d int) primitives.Primitive { + if d <= 0 { return nil } + { + v := fixture_Address_depth(d-1) + return &v + } +} + +// ---- single suite with subtests (one server/client for all) ---- +func Test_Test_DomainRoundtrip(t *testing.T) { + impl := &fakeImpl{} + + lis := bufconn.Listen(bufSize) + s := grpc.NewServer() + pb.RegisterTestServer(s, NewServer(impl)) + go func() { _ = s.Serve(lis) }() + t.Cleanup(func() { s.Stop(); _ = lis.Close() }) + + dialer := func(ctx context.Context, _ string) (net.Conn, error) { return lis.Dial() } + conn, err := grpc.DialContext(context.Background(), "bufnet", + grpc.WithContextDialer(dialer), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { t.Fatalf("dial: %v", err) } + t.Cleanup(func() { _ = conn.Close() }) + + c := NewClient(pb.NewTestClient(conn)) + t.Run("GetBytes happy path", func(t *testing.T) { + ctx := context.Background() + // want: []byte + want := []byte{10,11} + impl.ret_GetBytes = want + var b []byte = []byte{4,5,6} + got, err := c.GetBytes(ctx, b) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_GetBytes) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_GetBytes) + } + }) + t.Run("GetInt happy path", func(t *testing.T) { + ctx := context.Background() + want := uint64(2) + impl.ret_GetInt = want + got, err := c.GetInt(ctx) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_GetInt) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_GetInt) + } + }) + t.Run("GetIntArr happy path", func(t *testing.T) { + ctx := context.Background() + // want: [N]T (fixed-size array) + var want [10]int + for i := range want { want[i] = int(i+1) } + impl.ret_GetIntArr = want + got, err := c.GetIntArr(ctx) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_GetIntArr) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_GetIntArr) + } + }) + t.Run("GetIntSlice happy path", func(t *testing.T) { + ctx := context.Background() + // want: []scalar + want := []int{ int(1), int(2), int(3) } + impl.ret_GetIntSlice = want + got, err := c.GetIntSlice(ctx) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_GetIntSlice) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_GetIntSlice) + } + }) + t.Run("SendNested happy path", func(t *testing.T) { + ctx := context.Background() + // want: Message + want := fixture_NestedStruct() + impl.ret_SendNested = want + var ns testdata.NestedStruct = fixture_NestedStruct() + got, err := c.SendNested(ctx, ns) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_SendNested) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_SendNested) + } + }) + t.Run("SendStruct happy path", func(t *testing.T) { + ctx := context.Background() + // want: Message + want := fixture_MyStruct() + impl.ret_SendStruct = want + var ms testdata.MyStruct = fixture_MyStruct() + got, err := c.SendStruct(ctx, ms) + if err != nil { t.Fatalf("rpc error: %v", err) } + if !reflect.DeepEqual(got, impl.ret_SendStruct) { + t.Fatalf("result mismatch:\n got = %#v\n want = %#v", got, impl.ret_SendStruct) + } + }) +} diff --git a/pkg/loop/internal/generator/testdata/generate.go b/pkg/loop/internal/generator/testdata/generate.go new file mode 100644 index 000000000..d6f44f7f1 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/generate.go @@ -0,0 +1,3 @@ +//go:generate bash -c "set -euo pipefail; mkdir -p ./gen/pb ./gen/wrap && go run ../../../cmd/genwiring --pkg . --interface TestFace --config config.yaml --service Test --proto-pkg loop.test --proto-go-package github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/pb --proto-out ./gen/pb/service.proto --go-out ./gen/wrap --go-pkg github.com/smartcontractkit/chainlink-common/pkg/loop/internal/generator/testdata/gen/wrap" +//go:generate bash -c "set -euo pipefail; protoc -I ./gen/pb --go_out=paths=source_relative:./gen/pb --go-grpc_out=paths=source_relative:./gen/pb ./gen/pb/service.proto" +package testdata diff --git a/pkg/loop/internal/generator/testdata/testface.go b/pkg/loop/internal/generator/testdata/testface.go new file mode 100644 index 000000000..6ac8d5b63 --- /dev/null +++ b/pkg/loop/internal/generator/testdata/testface.go @@ -0,0 +1,32 @@ +package testdata + +import ( + "context" + + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" +) + +type Nested interface { + GetInt(ctx context.Context) (uint64, error) + GetIntSlice(ctx context.Context) ([]int, error) +} + +type TestFace interface { + Nested + GetIntArr(ctx context.Context) ([10]int, error) + GetBytes(ctx context.Context, b []byte) ([]byte, error) + SendStruct(ctx context.Context, ms MyStruct) (MyStruct, error) + SendNested(ctx context.Context, ns NestedStruct) (NestedStruct, error) +} + +type MyStruct struct { + B []byte + Prim primitives.ConfidenceLevel + Expr query.Expression +} + +type NestedStruct struct { + F1 MyStruct + F2 MyStruct +}