rpcmsg.bend
cswap-bend-cli@0.1.0.0 · 241 lines · 35 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
4import cswap-bend-cli@0.1.0.0/fmt/out.bend as O
5
6
7
8
9
10
11
12
13
14
15
16type Req is Data:
17 ReqOk{id: Json.Json, argv: List<&2, String>}
18 ReqBad{id: Json.Json, code: String, msg: String}
19
20
21
22type Bad is Data:
23 Bad{code: String, msg: String}
24
25def or_null(m: Maybe<&2, Json.Json>) -> Json.Json:
26 match m:
27 case Some{v}:
28 v
29 case None{}:
30 Json.JNull{}
31
32def item_str(+h: Json.Json) -> Result<&2, &2, String, String>:
33 match h:
34 case Json.JStr{s}:
35 Done{s}
36 case Json.JNum{t}:
37 Done{t}
38 case _:
39 Fail{"args must be strings or numbers"}
40
41def cons_str(x: Result<&2, &2, String, String>, rest: Result<&2, &2, String, List<&2, String>>) -> Result<&2, &2, String, List<&2, String>>:
42 match x rest:
43 case Done{a} Done{b}:
44 Done{a <> b}
45 case Fail{e} _:
46 Fail{e}
47 case _ Fail{e}:
48 Fail{e}
49
50def strs_of(items: Json.Json) -> Result<&2, &2, String, List<&2, String>>:
51 match items:
52 case Json.JCons{h, t}:
53 cons_str(item_str(h), strs_of(t))
54 case _:
55 Done{[]}
56
57def args_of(+v: Json.Json) -> Result<&2, &2, String, List<&2, String>>:
58 match v:
59 case Json.JNull{}:
60 Done{[]}
61 case Json.JArr{items}:
62 strs_of(items)
63 case _:
64 Fail{"args must be an array"}
65
66def dashes(+k: String) -> String:
67 Bool.pick(String, String.starts_with(k, "-"), k, "--" ++ k)
68
69def opt_flag(+k: String, +v: Json.Json) -> Result<&2, &2, String, List<&2, String>>:
70 match v:
71 case Json.JBool{True{}}:
72 Done{[dashes(k)]}
73 case Json.JBool{False{}}:
74 Done{[]}
75 case Json.JNull{}:
76 Done{[]}
77 case Json.JStr{s}:
78 Done{[dashes(k), s]}
79 case Json.JNum{t}:
80 Done{[dashes(k), t]}
81 case _:
82 Fail{"option '" ++ k ++ "' must be a string, a number or a boolean"}
83
84def cat(x: Result<&2, &2, String, List<&2, String>>, rest: Result<&2, &2, String, List<&2, String>>) -> Result<&2, &2, String, List<&2, String>>:
85 match x rest:
86 case Done{a} Done{b}:
87 Done{List.append(&2, String, a, b)}
88 case Fail{e} _:
89 Fail{e}
90 case _ Fail{e}:
91 Fail{e}
92
93def flags_of(fields: Json.Json) -> Result<&2, &2, String, List<&2, String>>:
94 match fields:
95 case Json.JField{k, v, t}:
96 cat(opt_flag(k, v), flags_of(t))
97 case _:
98 Done{[]}
99
100def opts_of(+v: Json.Json) -> Result<&2, &2, String, List<&2, String>>:
101 match v:
102 case Json.JNull{}:
103 Done{[]}
104 case Json.JObj{f}:
105 flags_of(f)
106 case _:
107 Fail{"opts must be an object"}
108
109
110
111def allowed(+v: String) -> Bool:
112 String.eq(v, "list") || String.eq(v, "status") || String.eq(v, "switch") || String.eq(v, "add") || String.eq(v, "add-token") || String.eq(v, "remove")
113 || String.eq(v, "import-usage") || String.eq(v, "config") || String.eq(v, "profile") || String.eq(v, "upgrade") || String.eq(v, "help")
114
115def refused(+v: String) -> String:
116 Bool.pick(String, String.eq(v, "auto"), "auto is not available over rpc: the poller is the one decider (run `cswap auto`)",
117 Bool.pick(String, String.eq(v, "tui") || String.eq(v, "watch"), "interactive commands are not available over rpc",
118 Bool.pick(String, String.eq(v, "menubar"), "menubar is not supported in cswap-bend", "unknown command")))
119
120def cmd_ok(+v: String, +name: String) -> Result<&2, &2, Bad, String>:
121 Bool.pick(Result<&2, &2, Bad, String>, allowed(v), Done{v},
122 Bool.pick(Result<&2, &2, Bad, String>, String.eq(v, "auto") || String.eq(v, "tui") || String.eq(v, "watch") || String.eq(v, "menubar"),
123 Fail{Bad{"not_allowed", refused(v)}}, Fail{Bad{"unknown_command", "unknown command '" ++ name ++ "'"}}))
124
125
126def canon(+n: String) -> String:
127 Bool.pick(String, String.eq(n, "ls"), "list", Bool.pick(String, String.eq(n, "rm"), "remove", Bool.pick(String, String.eq(n, "update"), "upgrade", n)))
128
129
130def cmd_word(+name: String) -> Result<&2, &2, Bad, String>:
131 Bool.pick(Result<&2, &2, Bad, String>, String.eq(name, "version"), Done{"--version"}, cmd_ok(canon(name), name))
132
133def cmd_of(+m: Maybe<&2, Json.Json>) -> Result<&2, &2, Bad, String>:
134 match m:
135 case Some{Json.JStr{s}}:
136 cmd_word(s)
137 case _:
138 Fail{Bad{"bad_request", "a request needs a \"cmd\" string"}}
139
140
141
142def bad(+id: Json.Json, +code: String, +msg: String) -> Req:
143 ReqBad{id, code, msg}
144
145def build3(+id: Json.Json, +w: String, a: Result<&2, &2, String, List<&2, String>>, o: Result<&2, &2, String, List<&2, String>>) -> Req:
146 match a o:
147 case Done{xs} Done{fs}:
148 ReqOk{id, w <> List.append(&2, String, xs, List.append(&2, String, fs, ["--no-input"]))}
149 case Fail{e} _:
150 bad(id, "bad_request", e)
151 case _ Fail{e}:
152 bad(id, "bad_request", e)
153
154def build2(+id: Json.Json, c: Result<&2, &2, Bad, String>, +j: Json.Json) -> Req:
155 match c:
156 case Fail{Bad{code, msg}}:
157 bad(id, code, msg)
158 case Done{w}:
159 build3(id, w, args_of(or_null(Json.get("args", j))), opts_of(or_null(Json.get("opts", j))))
160
161def build(+j: Json.Json) -> Req:
162 build2(or_null(Json.get("id", j)), cmd_of(Json.get("cmd", j)), j)
163
164
165def plan(+j: Json.Json) -> Req:
166 match j:
167 case Json.JObj{f}:
168 build(j)
169 case _:
170 bad(Json.JNull{}, "bad_request", "a request is an object: {\"id\": ..., \"cmd\": ..., \"args\": [...], \"opts\": {...}}")
171
172
173
174def ok_resp(+id: Json.Json, +result: Json.Json) -> Json.Json:
175 Json.of_pairs([("id", id), ("ok", Json.JBool{True{}}), ("result", result)])
176
177def err_resp(+id: Json.Json, +code: String, +msg: String, +exit: U32) -> Json.Json:
178 Json.of_pairs([("id", id), ("ok", Json.JBool{False{}}), ("error", Json.of_pairs([("code", Json.JStr{code}), ("message", Json.JStr{msg}), ("exit", Json.JNum{U32.show(exit)})]))])
179
180def last_part(ps: List<&2, String>) -> String:
181 match ps:
182 case Nil{}:
183 ""
184 case Con{h, Nil{}}:
185 h
186 case Con{h, Con{h2, t}}:
187 last_part(Con{h2, t})
188
189def cut_pick(hit: Bool, rest: String, more: String) -> String:
190 match hit:
191 case True{}:
192 rest
193 case False{}:
194 more
195
196
197def cut_error(s: String, +whole: String) -> String:
198 match s:
199 case SNil{}:
200 whole
201 case SCon{+c, +t}:
202 cut_pick(String.starts_with(s, "error: "), String.drop(s, 7n), cut_error(t, whole))
203
204
205def after_error(+err: String) -> String:
206 cut_error(String.trim(last_part(String.lines(String.trim(err)))), String.trim(last_part(String.lines(String.trim(err)))))
207
208def str_at(+j: Json.Json, +k: String) -> String:
209 Maybe.default(&2, String, Json.as_string(or_null(Json.get(k, j))), "")
210
211def fail_doc(+id: Json.Json, +exit: U32, +d: Json.Json, +err: String) -> Json.Json:
212 Bool.pick(Json.Json, Maybe.is_some(&2, Json.Json, Json.get("error", d)),
213 err_resp(id, str_at(or_null(Json.get("error", d)), "type"), str_at(or_null(Json.get("error", d)), "message"), exit),
214 err_resp(id, Bool.pick(String, U32.is_eq(exit, 2), "usage", "error"), after_error(err), exit))
215
216def err_of(m: Maybe<&2, Json.Json>, +id: Json.Json, +exit: U32, +err: String) -> Json.Json:
217 match m:
218 case Some{d}:
219 fail_doc(id, exit, d, err)
220 case None{}:
221 err_resp(id, Bool.pick(String, U32.is_eq(exit, 2), "usage", "error"), after_error(err), exit)
222
223def ok_of(m: Maybe<&2, Json.Json>, +id: Json.Json) -> Json.Json:
224 match m:
225 case Some{d}:
226 ok_resp(id, d)
227 case None{}:
228 ok_resp(id, Json.JObj{Json.JNil{}})
229
230def response2(good: Bool, +id: Json.Json, +exit: U32, +err: String, m: Maybe<&2, Json.Json>) -> Json.Json:
231 match good:
232 case True{}:
233 ok_of(m, id)
234 case False{}:
235 err_of(m, id, exit, err)
236
237
238def response(+id: Json.Json, o: Cmd.Out) -> Json.Json:
239 match o:
240 case Cmd.Out{+code, out, +err}:
241 response2(U32.is_eq(code, 0), id, code, err, O.doc_of(Cmd.Out{code, out, err}))