src/typed.bend
bend-kube-runtime@0.1.1.0 · 177 lines · 0 definitions
1import Base
2import bend-kube-runtime@0.1.1.0/src/json.bend as J
3
4# Typed
5# =====
6#
7# What generated types (tools/gen.py) decode and encode with: typed views
8# of JSON objects, as k8s-openapi and kube-derive give kube-rs. A decoder
9# answers the value or an error naming the field's path ("spec.ports[2]
10# .port: expected an integer"); an encoder builds the JSON back, leaving
11# out absent optional fields and empty lists, as omitempty does.
12
13def Ty.Res(-A: Data) -> Type:
14 Result<&2, &2, String, A>
15
16def Ty.at.go(path: String, key: String, root: Bool) -> String:
17 match root:
18 case True{}:
19 key
20 case False{}:
21 path ++ "." ++ key
22
23def Ty.at(+path: String, key: String) -> String:
24 Ty.at.go(path, key, String.is_empty(path))
25
26# Scalars
27# -------
28
29def Ty.str(j: J.Json, path: String) -> Result<&2, &2, String, String>:
30 match j:
31 case J.JStr{s}:
32 Done{s}
33 case _:
34 Fail{path ++ ": expected a string"}
35
36def Ty.bool(j: J.Json, path: String) -> Result<&2, &2, String, Bool>:
37 match j:
38 case J.JBool{b}:
39 Done{b}
40 case _:
41 Fail{path ++ ": expected a boolean"}
42
43def Ty.u32.read(m: Maybe<&2, U32>, path: String) -> Result<&2, &2, String, U32>:
44 match m:
45 case Some{n}:
46 Done{n}
47 case None{}:
48 Fail{path ++ ": expected an integer from 0 to 4294967295"}
49
50def Ty.u32(j: J.Json, path: String) -> Result<&2, &2, String, U32>:
51 match j:
52 case J.JNum{t}:
53 Ty.u32.read(U32.read(t), path)
54 case _:
55 Fail{path ++ ": expected an integer"}
56
57# A number kept as its text (int64, floats): lossless.
58def Ty.num(j: J.Json, path: String) -> Result<&2, &2, String, String>:
59 match j:
60 case J.JNum{t}:
61 Done{t}
62 case _:
63 Fail{path ++ ": expected a number"}
64
65# Anything, untyped (maps, preserved unknown fields).
66def Ty.json(j: J.Json, path: String) -> Result<&2, &2, String, J.Json>:
67 Done{j}
68
69def Ty.from_str(s: String) -> J.Json:
70 J.JStr{s}
71
72def Ty.from_bool(b: Bool) -> J.Json:
73 J.JBool{b}
74
75def Ty.from_u32(n: U32) -> J.Json:
76 J.JNum{U32.show(n)}
77
78def Ty.from_num(t: String) -> J.Json:
79 J.JNum{t}
80
81def Ty.from_json(j: J.Json) -> J.Json:
82 j
83
84# Fields
85# ------
86
87def Ty.req.go(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, +j: J.Json,
88 +path: String, missing: Bool) -> Result<&2, &2, String, A>:
89 match missing:
90 case True{}:
91 Fail{path ++ ": required"}
92 case False{}:
93 dec(j, path)
94
95# A required field.
96def Ty.req(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, +j: J.Json, +key: String,
97 +path: String) -> Result<&2, &2, String, A>:
98 Ty.req.go(~A, ~dec, J.Json.get(j, key), Ty.at(path, key), J.Json.is_null(J.Json.get(j, key)))
99
100def Ty.opt.go(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, +j: J.Json,
101 +path: String, missing: Bool) -> Result<&2, &2, String, Maybe<&2, A>>:
102 match missing:
103 case True{}:
104 Done{None{}}
105 case False{}:
106 Result.map(&2, &2, String, A, Maybe<&2, A>, x => Some{x}, dec(j, path))
107
108# An optional field: None when absent or null.
109def Ty.opt(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, +j: J.Json, +key: String,
110 +path: String) -> Result<&2, &2, String, Maybe<&2, A>>:
111 Ty.opt.go(~A, ~dec, J.Json.get(j, key), Ty.at(path, key), J.Json.is_null(J.Json.get(j, key)))
112
113def Ty.list.go(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>,
114 xs: List<&2, J.Json>, +path: String, +i: U32) -> Result<&2, &2, String, List<&2, A>>:
115 match xs:
116 case Nil{}:
117 Done{Nil{}}
118 case Con{x, t}:
119 do Result<&2, &2, String, List<&2, A>>:
120 h : A <- dec(x, path ++ "[" ++ U32.show(i) ++ "]")
121 r : List<&2, A> <- Ty.list.go(~A, ~dec, t, path, (i + 1 : U32))
122 return h <> r
123
124def Ty.list.of(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, j: J.Json,
125 +path: String) -> Result<&2, &2, String, List<&2, A>>:
126 match j:
127 case J.JNull{}:
128 Done{Nil{}}
129 case J.JArr{xs}:
130 Ty.list.go(~A, ~dec, J.Json.chain(xs), path, 0)
131 case _:
132 Fail{path ++ ": expected a list"}
133
134# A list field: empty when absent.
135def Ty.list(~A: Data, ~dec: J.Json -> String -> Result<&2, &2, String, A>, +j: J.Json, +key: String,
136 +path: String) -> Result<&2, &2, String, List<&2, A>>:
137 Ty.list.of(~A, ~dec, J.Json.get(j, key), Ty.at(path, key))
138
139# Building
140# --------
141
142def Ty.put(~A: Data, ~enc: A -> J.Json, key: String, x: A) -> Maybe<&2, J.KV>:
143 Some{J.KV{key, enc(x)}}
144
145def Ty.put_opt(~A: Data, ~enc: A -> J.Json, key: String, m: Maybe<&2, A>) -> Maybe<&2, J.KV>:
146 match m:
147 case Some{x}:
148 Some{J.KV{key, enc(x)}}
149 case None{}:
150 None{}
151
152def Ty.encs(~A: Data, ~enc: A -> J.Json, xs: List<&2, A>) -> List<&2, J.Json>:
153 match xs:
154 case Nil{}:
155 Nil{}
156 case Con{x, t}:
157 enc(x) <> Ty.encs(~A, ~enc, t)
158
159def Ty.put_list(~A: Data, ~enc: A -> J.Json, key: String, xs: List<&2, A>) -> Maybe<&2, J.KV>:
160 match xs:
161 case Nil{}:
162 None{}
163 case +ys:
164 Some{J.KV{key, J.Json.arr(Ty.encs(~A, ~enc, ys))}}
165
166def Ty.fields(ms: List<&2, Maybe<&2, J.KV>>) -> List<&2, J.KV>:
167 match ms:
168 case Nil{}:
169 Nil{}
170 case Con{Some{kv}, t}:
171 kv <> Ty.fields(t)
172 case Con{None{}, t}:
173 Ty.fields(t)
174
175# An object from its present fields.
176def Ty.obj(ms: List<&2, Maybe<&2, J.KV>>) -> J.Json:
177 J.Json.obj(Ty.fields(ms))