~bigbes/tarantool

tarantool-protobuf

ref: b1273f1bd5ffa459ac5ff714a6dd75d405b8d43c tarantool-protobuf/cmd/protoc-gen-tarantool/internal/gen/service.go -rw-r--r-- 8.4 KiB
b1273f1b — Eugene Blikh json: canonical lowerCamelCase + NullValue WKT descriptor 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package gen

import (
	"google.golang.org/protobuf/compiler/protogen"
)

// streamKind classifies an RPC method for codegen branching.
type streamKind int

const (
	kindUnary streamKind = iota
	kindServerStream
	kindClientStream
	kindBidi
)

func classify(m *protogen.Method) streamKind {
	cs, ss := m.Desc.IsStreamingClient(), m.Desc.IsStreamingServer()
	switch {
	case cs && ss:
		return kindBidi
	case ss:
		return kindServerStream
	case cs:
		return kindClientStream
	default:
		return kindUnary
	}
}

// emitService emits the descriptor + client + server factory for a single
// gRPC service. The descriptor is mode-independent; client/server factories
// reference the same per-message _encode/_decode functions that the rest of
// the module already provides.
func emitService(w *writer, file *protogen.File, svc *protogen.Service, imports map[string]string, prefix string) {
	name := string(svc.Desc.Name())
	fullName := string(svc.Desc.FullName())
	selfPath := luaPackagePath(file.Desc, prefix)

	w.line("-- Service: %s", fullName)
	w.line("M.%s_service = {", name)
	w.line("    name = %q,", fullName)
	w.line("    full_name = %q,", "/"+fullName)
	w.line("    methods = {")
	for _, m := range svc.Methods {
		mname := string(m.Desc.Name())
		w.line("        %s = {", mname)
		w.line("            name = %q,", mname)
		w.line("            full_name = %q,", "/"+fullName+"/"+mname)
		w.line("            input = %s,", typeRef(file, m.Input.Desc, selfPath, imports, "_descriptor", prefix))
		w.line("            output = %s,", typeRef(file, m.Output.Desc, selfPath, imports, "_descriptor", prefix))
		if m.Desc.IsStreamingClient() {
			w.line("            client_streaming = true,")
		}
		if m.Desc.IsStreamingServer() {
			w.line("            server_streaming = true,")
		}
		w.line("        },")
	}
	w.line("    },")
	w.line("}")
	w.line("")

	emitServiceClient(w, file, svc, imports, prefix, selfPath)
	emitServiceServer(w, file, svc, imports, prefix, selfPath)
}

// emitServiceClient emits a constructor `function M.<Service>_client(transport)`
// that returns a table with one entry per RPC method:
//
//   - Unary methods are direct functions: `client.SayHello(req, ctx) -> reply`
//   - Streaming methods return a stream object (see pb.grpc for the shape):
//     `client.StreamHellos(req, ctx) -> {recv, cancel}`
//     `client.CollectHellos(ctx)    -> {send, close_send, recv, cancel}`
//     `client.Chat(ctx)             -> {send, close_send, recv, cancel}`
func emitServiceClient(w *writer, file *protogen.File, svc *protogen.Service, imports map[string]string, prefix string, selfPath string) {
	name := string(svc.Desc.Name())
	w.line("function M.%s_client(transport)", name)
	w.line("    if transport == nil then error(\"%s_client: transport is required\", 0) end", name)
	w.line("    return {")
	for _, m := range svc.Methods {
		mname := string(m.Desc.Name())
		path := "/" + string(svc.Desc.FullName()) + "/" + mname
		inputEnc := typeRef(file, m.Input.Desc, selfPath, imports, "_encode", prefix)
		outputDec := typeRef(file, m.Output.Desc, selfPath, imports, "_decode", prefix)

		switch classify(m) {
		case kindUnary:
			w.line("        %s = function(req, ctx)", mname)
			w.line("            local req_bytes = %s(req)", inputEnc)
			w.line("            local resp_bytes = transport:unary(%q, req_bytes, ctx)", path)
			w.line("            return %s(resp_bytes)", outputDec)
			w.line("        end,")
		case kindServerStream:
			w.line("        %s = function(req, ctx)", mname)
			w.line("            local req_bytes = %s(req)", inputEnc)
			w.line("            local raw = transport:server_stream(%q, req_bytes, ctx)", path)
			w.line("            return pb.grpc.wrap_server_stream(raw, %s)", outputDec)
			w.line("        end,")
		case kindClientStream:
			w.line("        %s = function(ctx)", mname)
			w.line("            local raw = transport:client_stream(%q, ctx)", path)
			w.line("            return pb.grpc.wrap_call(raw, %s, %s)", inputEnc, outputDec)
			w.line("        end,")
		case kindBidi:
			w.line("        %s = function(ctx)", mname)
			w.line("            local raw = transport:bidi(%q, ctx)", path)
			w.line("            return pb.grpc.wrap_call(raw, %s, %s)", inputEnc, outputDec)
			w.line("        end,")
		}
	}
	w.line("    }")
	w.line("end")
	w.line("")
}

// emitServiceServer emits `function M.<Service>_server(impl)` returning
// {service, methods, streams}. `methods` holds unary handlers keyed by
// path; `streams` holds streaming handlers keyed by path. Each streaming
// entry is `{kind = '...', handler = function(req_bytes, server_view, ctx)}`
// — see pb.grpc for the transport's expectations.
//
// User-supplied impl functions speak decoded messages; the generated
// wrappers handle the per-message encode/decode boundary so user code
// stays free of wire details.
func emitServiceServer(w *writer, file *protogen.File, svc *protogen.Service, imports map[string]string, prefix string, selfPath string) {
	name := string(svc.Desc.Name())
	w.line("function M.%s_server(impl)", name)
	w.line("    if type(impl) ~= 'table' then error(\"%s_server: impl table is required\", 0) end", name)
	w.line("    return {")
	w.line("        service = M.%s_service,", name)
	w.line("        methods = {")
	for _, m := range svc.Methods {
		if classify(m) != kindUnary {
			continue
		}
		mname := string(m.Desc.Name())
		path := "/" + string(svc.Desc.FullName()) + "/" + mname
		inputDec := typeRef(file, m.Input.Desc, selfPath, imports, "_decode", prefix)
		outputEnc := typeRef(file, m.Output.Desc, selfPath, imports, "_encode", prefix)

		w.line("            [%q] = function(req_bytes, ctx)", path)
		w.line("                local handler = impl.%s", mname)
		w.line("                if handler == nil then error(\"%s.%s: handler missing\", 0) end", name, mname)
		w.line("                local req = %s(req_bytes)", inputDec)
		w.line("                local resp = handler(req, ctx)")
		w.line("                return %s(resp)", outputEnc)
		w.line("            end,")
	}
	w.line("        },")
	w.line("        streams = {")
	for _, m := range svc.Methods {
		kind := classify(m)
		if kind == kindUnary {
			continue
		}
		mname := string(m.Desc.Name())
		path := "/" + string(svc.Desc.FullName()) + "/" + mname
		inputDec := typeRef(file, m.Input.Desc, selfPath, imports, "_decode", prefix)
		outputEnc := typeRef(file, m.Output.Desc, selfPath, imports, "_encode", prefix)

		switch kind {
		case kindServerStream:
			w.line("            [%q] = {", path)
			w.line("                kind = 'server_stream',")
			w.line("                handler = function(req_bytes, server_view, ctx)")
			w.line("                    local handler = impl.%s", mname)
			w.line("                    if handler == nil then error(\"%s.%s: handler missing\", 0) end", name, mname)
			w.line("                    local req = %s(req_bytes)", inputDec)
			w.line("                    local wrapped = pb.grpc.wrap_server_view(server_view, nil, %s)", outputEnc)
			w.line("                    handler(req, wrapped, ctx)")
			w.line("                end,")
			w.line("            },")
		case kindClientStream:
			w.line("            [%q] = {", path)
			w.line("                kind = 'client_stream',")
			w.line("                handler = function(_, server_view, ctx)")
			w.line("                    local handler = impl.%s", mname)
			w.line("                    if handler == nil then error(\"%s.%s: handler missing\", 0) end", name, mname)
			w.line("                    local wrapped = pb.grpc.wrap_server_view(server_view, %s, nil)", inputDec)
			w.line("                    local resp = handler(wrapped, ctx)")
			w.line("                    if resp == nil then error(\"%s.%s: handler returned nil response\", 0) end", name, mname)
			w.line("                    server_view:send(%s(resp))", outputEnc)
			w.line("                end,")
			w.line("            },")
		case kindBidi:
			w.line("            [%q] = {", path)
			w.line("                kind = 'bidi',")
			w.line("                handler = function(_, server_view, ctx)")
			w.line("                    local handler = impl.%s", mname)
			w.line("                    if handler == nil then error(\"%s.%s: handler missing\", 0) end", name, mname)
			w.line("                    local wrapped = pb.grpc.wrap_server_view(server_view, %s, %s)", inputDec, outputEnc)
			w.line("                    handler(wrapped, ctx)")
			w.line("                end,")
			w.line("            },")
		}
	}
	w.line("        },")
	w.line("    }")
	w.line("end")
	w.line("")
}