Reputation: 21
I am trying to eliminate the need of proto files for running the gRPC/rpc services by directly parsing the interface that user will provide. This interface will contain the list of methods that will be exposed over the RPC connection.
For example:
package main
import "gofr.dev/pkg/gofr"
// ExampleService Define the service interface with GoFr context
type ExampleService interface {
GetExampleData(ctx \*gofr.Context, request ExampleRequest) (ExampleResponse, error)
}
// ExampleRequest Define the request struct
type ExampleRequest struct {
ID int `json:"id"`
Name string `json:"name"`
}
// ExampleResponse Define the response struct
type ExampleResponse struct {
Status string `json:"status"`
Message string `json:"message"`
}
Now the plan is to parse this interface to extract out the necessary fields, methods and structs. I have made a parser that extracts out the information from interface and then populate it in this struct:
type ParsedInterface struct {
InterfaceName string
Methods []ParsedMethod
}
type ParsedMethod struct {
Name string
InputParams []ParsedParam
ReturnTypes []ParsedType
}
type ParsedParam struct {
Name string
Type string
}
type ParsedType struct {
Name string
Fields []ParsedField
}
type ParsedField struct {
Name string
Type string
}
This is working correctly till here, but for my third step :
i.e. to generate the go code for the gRPC client and server side i am using this generator.go
package main
import (
"bytes"
"go/format"
"text/template"
)
// GenerateCode generates the client and server code
func GenerateCode(parsedInterface *ParsedInterface) (string, string, error) {
clientTemplate := `
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
)
type {{.InterfaceName}}Client struct {
cc *grpc.ClientConn
}
func New{{.InterfaceName}}Client(cc *grpc.ClientConn) *{{.InterfaceName}}Client {
return &{{.InterfaceName}}Client{cc: cc}
}
{{range .Methods}}
func (c *{{$.InterfaceName}}Client) {{.Name}}(ctx context.Context, req interface{}) (interface{}, error) {
out := new(interface{})
err := c.cc.Invoke(ctx, "/{{$.InterfaceName}}/{{.Name}}", req, out)
if err != nil {
return nil, err
}
return out, nil
}
{{end}}
`
serverTemplate := `
package main
import (
"context"
"fmt"
"reflect"
"google.golang.org/grpc"
)
type {{.InterfaceName}}Server struct {
impl interface{}
}
func New{{.InterfaceName}}Server(impl interface{}) *{{.InterfaceName}}Server {
return &{{.InterfaceName}}Server{impl: impl}
}
{{range .Methods}}
func (s *{{$.InterfaceName}}Server) {{.Name}}(ctx context.Context, req struct {
{{range .InputParams}}{{.Name}} {{.Type}}
{{end}}
}) ({{range .ReturnTypes}}{{.Name}} {{.Type}}{{end}}, error) {
method := reflect.ValueOf(s.impl).MethodByName("{{.Name}}")
if !method.IsValid() {
return {{range .ReturnTypes}}{{.Name}}, fmt.Errorf("method {{.Name}} not found")
}
in := []reflect.Value{reflect.ValueOf(ctx)}
{{range .InputParams}}in = append(in, reflect.ValueOf(req.{{.Name}})){{end}}
out := method.Call(in)
if len(out) != {{len .ReturnTypes}}+1 {
return {{range .ReturnTypes}}{{.Name}}, fmt.Errorf("unexpected number of return values")
}
if err, ok := out[{{len .ReturnTypes}}].Interface().(error); ok && err != nil {
return {{range .ReturnTypes}}{{.Name}}, err
}
resp := struct {
{{range .ReturnTypes}}
{{.Name}} {{.Type}}
{{end}}
}{}
{{range $i, $type := .ReturnTypes}}
{{if $type.Fields}}
resp.{{.Name}} = {{.Type}}{
{{range $field := $type.Fields}}
{{$field.Name}}: out[{{$i}}].Elem().FieldByName("{{$field.Name}}").Interface().({{$field.Type}}),
{{end}}
}
{{else}}
resp.{{.Name}} = out[{{$i}}].Interface().({{$type.Type}})
{{end}}
{{end}}
return resp, nil
}
{{end}}
func (s *{{.InterfaceName}}Server) Register(grpcServer *grpc.Server) {
{{.InterfaceName}}_RegisterService(grpcServer, s.impl)
}
`
clientTmpl, err := template.New("client").Parse(clientTemplate)
if err != nil {
return "", "", err
}
serverTmpl, err := template.New("server").Parse(serverTemplate)
if err != nil {
return , "", err
}
var clientBuffer, serverBuffer bytes.Buffer
err = clientTmpl.Execute(&clientBuffer, parsedInterface)
if err != nil {
return "", "", err
}
err = serverTmpl.Execute(&serverBuffer, parsedInterface)
if err != nil {
return "", "", err
}
clientCode, err := format.Source(clientBuffer.Bytes())
if err != nil {
return "", "", err
}
serverCode, err := format.Source(serverBuffer.Bytes())
if err != nil {
return "", "", err
}
return string(clientCode), string(serverCode), nil
}
For the client side i am able to generate the code successfully, but the server-side template is giving me this error:
template: server:60: unexpected EOF
I want to generate both the client and server-side code using these templates but i doubt maybe there is some error inside them.
So for example, the client-side code generated by this method is:
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
)
type ExampleServiceClient struct {
cc *grpc.ClientConn
}
func NewExampleServiceClient(cc *grpc.ClientConn) *ExampleServiceClient {
return &ExampleServiceClient{cc: cc}
}
func (c *ExampleServiceClient) GetExampleData(ctx context.Context, req interface{}) (interface{}, error) {
out := new(interface{})
err := c.cc.Invoke(ctx, "/ExampleService/GetExampleData", req, out)
if err != nil {
return nil, err
}
return out, nil
}
But for server side code getting error:
template: server:60: unexpected EOF
Upvotes: 1
Views: 196