src/rpc.bend
ezmcp@0.1.0.0 · 312 lines · 36 definitions
1
2
3
4
5import Base
6import emerging-ezjson@1.2.0.0/main.bend as Json
7import emerging-ezjson@1.2.0.0/src/value.bend as Value
8
9
10type Msg is Data:
11 Ask{id: Value.Json, method: String, params: Value.Json}
12 Tell{method: String, params: Value.Json}
13 Answer{id: Value.Json}
14 Junk{id: Value.Json, why: String}
15
16
17type Head is Data:
18 Head{obj: Bool, v2: Bool, has_id: Bool, id_ok: Bool, id: Value.Json,
19 method: Maybe<&2, String>, params: Value.Json, has_result: Bool,
20 has_error: Bool}
21
22
23type Code is Data:
24 ParseError{}
25 InvalidRequest{}
26 MethodNotFound{}
27 InvalidParams{}
28 InternalError{}
29
30
31type Out is Data:
32 Ok{result: Value.Json}
33 Err{code: Code, message: String}
34
35
36type Reply is Data:
37 Got{id: Value.Json, result: Value.Json}
38 Refused{id: Value.Json, code: String, message: String}
39 Unread{why: String}
40
41
42
43
44def digits.and(ok: Bool, rest: Unit -> Bool) -> Bool:
45 match ok:
46 case True{}:
47 rest(Unit{})
48 case False{}:
49 False{}
50
51
52def digits.go(text: String) -> Bool:
53 match text:
54 case SNil{}:
55 True{}
56 case SCon{head, tail}:
57 digits.and(Char.is_digit(head), _u => digits.go(tail))
58
59
60def digits(text: String) -> Bool:
61 match text:
62 case SNil{}:
63 False{}
64 case SCon{head, tail}:
65 digits.and(Char.is_digit(head), _u => digits.go(tail))
66
67
68
69def int.text.of(head: Char, tail: String, minus: Bool) -> Bool:
70 match minus:
71 case True{}:
72 digits(tail)
73 case False{}:
74 digits(SCon{head, tail})
75
76
77def int.text(text: String) -> Bool:
78 match text:
79 case SNil{}:
80 False{}
81 case SCon{+head, tail}:
82 int.text.of(head, tail, Char.is_eq(head, '-'))
83
84def id.num(meth: Maybe<&2, String>) -> Bool:
85 match meth:
86 case None{}:
87 False{}
88 case Some{raw}:
89 int.text(raw)
90
91def id.str(meth: Maybe<&2, String>, +val: Value.Json) -> Bool:
92 match meth:
93 case Some{_s}:
94 True{}
95 case None{}:
96 id.num(Json.as_num(val))
97
98
99
100def id.ok(+val: Value.Json) -> Bool:
101 id.str(Json.as_str(val), val)
102
103
104
105def is.v2(meth: Maybe<&2, String>) -> Bool:
106 match meth:
107 case None{}:
108 False{}
109 case Some{s}:
110 String.eq(s, "2.0")
111
112
113def head(+val: Value.Json) -> Head:
114 Head{Json.is_obj(val), is.v2(Json.as_str(Json.get(val, "jsonrpc"))),
115 Json.has(val, "id"), id.ok(Json.get(val, "id")), Json.get(val, "id"),
116 Json.as_str(Json.get(val, "method")), Json.get(val, "params"),
117 Json.has(val, "result"), Json.has(val, "error")}
118
119
120
121def head.id(has_id: Bool, id_ok: Bool, id: Value.Json) -> Value.Json:
122 Bool.pick(Value.Json, Bool.and(has_id, id_ok), id, Json.null())
123
124
125
126def decide.method(
127 has_id: Bool,
128 id_ok: Bool,
129 id: Value.Json,
130 method: String,
131 params: Value.Json
132) -> Msg:
133 match has_id:
134 case False{}:
135 Tell{method, params}
136 case True{}:
137 Bool.pick(Msg, id_ok, Ask{id, method, params},
138 Junk{Json.null(), "Invalid Request: id must be a string or an integer"})
139
140
141def one(lead: Bool, byte: Bool) -> Bool:
142 match lead byte:
143 case True{} False{}:
144 True{}
145 case False{} True{}:
146 True{}
147 case x y:
148 False{}
149
150
151
152def decide.answer(
153 +has_id: Bool,
154 +id_ok: Bool,
155 +id: Value.Json,
156 has_result: Bool,
157 has_error: Bool
158) -> Msg:
159 Bool.pick(Msg, Bool.and(Bool.and(has_id, id_ok),
160 one(has_result, has_error)),
161 Answer{id}, Junk{head.id(has_id, id_ok, id), "Invalid Request: no method"})
162
163def decide.kind(
164 has_id: Bool,
165 +id_ok: Bool,
166 +id: Value.Json,
167 method: Maybe<&2, String>,
168 params: Value.Json,
169 has_result: Bool,
170 has_error: Bool
171) -> Msg:
172 match method:
173 case Some{m}:
174 decide.method(has_id, id_ok, id, m, params)
175 case None{}:
176 decide.answer(has_id, id_ok, id, has_result, has_error)
177
178def decide.v2(
179 v2: Bool,
180 +has_id: Bool,
181 +id_ok: Bool,
182 +id: Value.Json,
183 method: Maybe<&2, String>,
184 params: Value.Json,
185 has_result: Bool,
186 has_error: Bool
187) -> Msg:
188 match v2:
189 case False{}:
190 Junk{head.id(has_id, id_ok, id),
191 "Invalid Request: jsonrpc must be \"2.0\""}
192 case True{}:
193 decide.kind(has_id, id_ok, id, method, params, has_result, has_error)
194
195
196def decide(head: Head) -> Msg:
197 match head:
198 case Head{obj, v2, has_id, id_ok, id, method, params, has_result, has_error}:
199 Bool.pick(Msg, obj,
200 decide.v2(v2, has_id, id_ok, id, method, params, has_result,
201 has_error),
202 Junk{Json.null(), "Invalid Request: not a JSON-RPC object"})
203
204
205def classify(val: Value.Json) -> Msg:
206 decide(head(val))
207
208
209
210
211def code.num(cb: Code) -> String:
212 match cb:
213 case ParseError{}:
214 "-32700"
215 case InvalidRequest{}:
216 "-32600"
217 case MethodNotFound{}:
218 "-32601"
219 case InvalidParams{}:
220 "-32602"
221 case InternalError{}:
222 "-32603"
223
224
225def error.obj(cb: Code, message: String) -> Value.Json:
226 Json.obj([("code", Json.num(code.num(cb))), ("message", Json.str(message))])
227
228
229def reply(id: Value.Json, out: Out) -> Value.Json:
230 match out:
231 case Ok{result}:
232 Json.obj([("jsonrpc", Json.str("2.0")), ("id", id), ("result", result)])
233 case Err{c, message}:
234 Json.obj([("jsonrpc", Json.str("2.0")), ("id", id),
235 ("error", error.obj(c, message))])
236
237
238def request(id: Value.Json, method: String, params: Value.Json) -> Value.Json:
239 Json.obj([("jsonrpc", Json.str("2.0")), ("id", id),
240 ("method", Json.str(method)), ("params", params)])
241
242
243def notice(method: String, params: Value.Json) -> Value.Json:
244 Json.obj([("jsonrpc", Json.str("2.0")), ("method", Json.str(method)),
245 ("params", params)])
246
247
248
249
250type Back is Data:
251 Back{v2: Bool, id: Value.Json, has_result: Bool, result: Value.Json,
252 has_error: Bool, code: Maybe<&2, String>, message: Maybe<&2, String>}
253
254def back.refused(
255 id: Value.Json,
256 code: Maybe<&2, String>,
257 message: Maybe<&2, String>
258) -> Reply:
259 match code message:
260 case Some{c} Some{m}:
261 Refused{id, c, m}
262 case c m:
263 Unread{"the error has no integer code or no message"}
264
265def back.has(
266 id: Value.Json,
267 has_result: Bool,
268 result: Value.Json,
269 has_error: Bool,
270 code: Maybe<&2, String>,
271 message: Maybe<&2, String>
272) -> Reply:
273 match has_result has_error:
274 case True{} False{}:
275 Got{id, result}
276 case False{} True{}:
277 back.refused(id, code, message)
278 case r e:
279 Unread{"a reply must hold exactly one of result and error"}
280
281
282def back.decide(byte: Back) -> Reply:
283 match byte:
284 case Back{v2, id, has_result, result, has_error, code, message}:
285 Bool.pick(Reply, v2,
286 back.has(id, has_result, result, has_error, code, message),
287 Unread{"not a JSON-RPC 2.0 reply"})
288
289def back.code(meth: Maybe<&2, String>) -> Maybe<&2, String>:
290 match meth:
291 case None{}:
292 None{}
293 case Some{+raw}:
294 Bool.pick(Maybe<&2, String>, int.text(raw), Some{raw}, None{})
295
296
297def back(+val: Value.Json) -> Back:
298 Back{is.v2(Json.as_str(Json.get(val, "jsonrpc"))), Json.get(val, "id"),
299 Json.has(val, "result"), Json.get(val, "result"), Json.has(val, "error"),
300 back.code(Json.as_num(Json.get(Json.get(val, "error"), "code"))),
301 Json.as_str(Json.get(Json.get(val, "error"), "message"))}
302
303def read.parsed(meth: Maybe<&2, Value.Json>) -> Reply:
304 match meth:
305 case None{}:
306 Unread{"the reply is not JSON"}
307 case Some{v}:
308 back.decide(back(v))
309
310
311def read(text: String) -> Reply:
312 read.parsed(Json.parse(text))