1 import Base 2 import ./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 13 def Ty.Res(-A: Data) -> Type: 14 Result<&2, &2, String, A> 15 16 def 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 23 def Ty.at(+path: String, key: String) -> String: 24 Ty.at.go(path, key, String.is_empty(path)) 25 26 # Scalars 27 # ------- 28 29 def 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 36 def 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 43 def 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 50 def 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. 58 def 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). 66 def Ty.json(j: J.Json, path: String) -> Result<&2, &2, String, J.Json>: 67 Done{j} 68 69 def Ty.from_str(s: String) -> J.Json: 70 J.JStr{s} 71 72 def Ty.from_bool(b: Bool) -> J.Json: 73 J.JBool{b} 74 75 def Ty.from_u32(n: U32) -> J.Json: 76 J.JNum{U32.show(n)} 77 78 def Ty.from_num(t: String) -> J.Json: 79 J.JNum{t} 80 81 def Ty.from_json(j: J.Json) -> J.Json: 82 j 83 84 # Fields 85 # ------ 86 87 def 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. 96 def 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 100 def 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. 109 def 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 113 def 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 124 def 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. 135 def 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 142 def Ty.put(~A: Data, ~enc: A -> J.Json, key: String, x: A) -> Maybe<&2, J.KV>: 143 Some{J.KV{key, enc(x)}} 144 145 def 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 152 def 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 159 def 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 166 def 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. 176 def Ty.obj(ms: List<&2, Maybe<&2, J.KV>>) -> J.Json: 177 J.Json.obj(Ty.fields(ms))