Trading card city builder game?

give up on coders and just do it manually

+45 -486
-13
api/src/codable.gleam
··· 1 - import gleam/dict.{type Dict} 2 - 3 - pub type Codable { 4 - Nil 5 - Bool(Bool) 6 - Int(Int) 7 - Float(Float) 8 - String(String) 9 - Binary(BitArray) 10 - List(List(Codable)) 11 - Record(Dict(String, Codable)) 12 - Struct(String, Codable) 13 - }
-47
api/src/codec.gleam
··· 1 - import coder.{type Coder} 2 - import gleam/result 3 - 4 - pub type CodecError(decoding_error, encoding_error) { 5 - DecodingError(decoding_error) 6 - EncodingError(encoding_error) 7 - } 8 - 9 - pub opaque type Codec(from, to, from_error, to_error) { 10 - Codec( 11 - from_coder: Coder(from, from_error, from_error), 12 - to_coder: Coder(to, to_error, to_error), 13 - ) 14 - } 15 - 16 - pub fn codec( 17 - from from_coder: Coder(from, from_error, from_error), 18 - to to_coder: Coder(to, to_error, to_error), 19 - ) -> Codec(from, to, from_error, to_error) { 20 - Codec(from_coder:, to_coder:) 21 - } 22 - 23 - pub fn encode( 24 - codec: Codec(from, to, from_error, to_error), 25 - data: from, 26 - ) -> Result(to, CodecError(from_error, to_error)) { 27 - use codable <- result.try( 28 - data |> coder.decode(codec.from_coder) |> result.map_error(DecodingError), 29 - ) 30 - use encoding <- result.try( 31 - codable |> coder.encode(codec.to_coder) |> result.map_error(EncodingError), 32 - ) 33 - Ok(encoding) 34 - } 35 - 36 - pub fn decode( 37 - codec: Codec(from, to, from_error, to_error), 38 - data: to, 39 - ) -> Result(from, CodecError(to_error, from_error)) { 40 - use codable <- result.try( 41 - data |> coder.decode(codec.to_coder) |> result.map_error(DecodingError), 42 - ) 43 - use decoding <- result.try( 44 - codable |> coder.encode(codec.from_coder) |> result.map_error(EncodingError), 45 - ) 46 - Ok(decoding) 47 - }
-405
api/src/coder.gleam
··· 1 - import codable.{type Codable} 2 - import gleam/bit_array 3 - import gleam/dict.{type Dict} 4 - import gleam/dynamic/decode 5 - import gleam/function 6 - import gleam/json.{type Json} as gleam_json 7 - import gleam/list 8 - import gleam/option 9 - import gleam/result 10 - @target(erlang) 11 - import glepack/data as glepack_data 12 - @target(erlang) 13 - import glepack/decode as glepack_decode 14 - @target(erlang) 15 - import glepack/encode as glepack_encode 16 - import glepack/error as glepack_error 17 - 18 - pub type Encoder(encoding, error) = 19 - fn(Codable) -> Result(encoding, error) 20 - 21 - pub type Decoder(encoding, error) = 22 - fn(encoding) -> Result(Codable, error) 23 - 24 - pub opaque type Coder(encoding, encode_err, decode_err) { 25 - Coder( 26 - encode: Encoder(encoding, encode_err), 27 - decode: Decoder(encoding, decode_err), 28 - ) 29 - } 30 - 31 - pub fn encode( 32 - data: Codable, 33 - codec: Coder(encoding, encode_err, decode_err), 34 - ) -> Result(encoding, encode_err) { 35 - codec.encode(data) 36 - } 37 - 38 - pub fn decode( 39 - data: encoding, 40 - codec: Coder(encoding, encode_err, decode_err), 41 - ) -> Result(Codable, decode_err) { 42 - codec.decode(data) 43 - } 44 - 45 - pub opaque type EnumEncoderBuilder(t, ee) { 46 - EnumEncoderBuilder(cases: List(VariantCoder(t, EncoderError(ee))), error: ee) 47 - } 48 - 49 - pub opaque type VariantCoder(t, ee) { 50 - VariantCoder(tag: String, encoder: Encoder(t, ee)) 51 - } 52 - 53 - pub type EncoderError(e) { 54 - ExpectedNil 55 - ExpectedBool 56 - ExpectedInt 57 - ExpectedFloat 58 - ExpectedString 59 - ExpectedBinary 60 - ExpectedList 61 - ExpectedStruct 62 - ListEncoderError(e) 63 - StructEncoderError(String, e) 64 - StructVariantError(e) 65 - } 66 - 67 - pub type Bijection(t, u) { 68 - Bijection(from: fn(t) -> u, to: fn(u) -> t) 69 - } 70 - 71 - pub fn map_encoder( 72 - encoder: Encoder(t, e), 73 - transform: fn(t) -> u, 74 - ) -> Encoder(u, e) { 75 - fn(codable) { 76 - use value <- result.map(encoder(codable)) 77 - transform(value) 78 - } 79 - } 80 - 81 - pub fn map_decoder( 82 - decoder: Decoder(u, e), 83 - transform: fn(t) -> u, 84 - ) -> Decoder(t, e) { 85 - fn(value) { decoder(transform(value)) } 86 - } 87 - 88 - pub fn nil(codable: Codable) -> Result(Nil, EncoderError(e)) { 89 - case codable { 90 - codable.Nil -> Ok(Nil) 91 - _ -> Error(ExpectedNil) 92 - } 93 - } 94 - 95 - pub fn bool(codable: Codable) -> Result(Bool, EncoderError(e)) { 96 - case codable { 97 - codable.Bool(value) -> Ok(value) 98 - _ -> Error(ExpectedBool) 99 - } 100 - } 101 - 102 - pub fn int(codable: Codable) -> Result(Int, EncoderError(e)) { 103 - case codable { 104 - codable.Int(value) -> Ok(value) 105 - _ -> Error(ExpectedInt) 106 - } 107 - } 108 - 109 - pub fn float(codable: Codable) -> Result(Float, EncoderError(e)) { 110 - case codable { 111 - codable.Float(value) -> Ok(value) 112 - _ -> Error(ExpectedFloat) 113 - } 114 - } 115 - 116 - pub fn string(codable: Codable) -> Result(String, EncoderError(e)) { 117 - case codable { 118 - codable.String(value) -> Ok(value) 119 - _ -> Error(ExpectedString) 120 - } 121 - } 122 - 123 - pub fn binary(codable: Codable) -> Result(BitArray, EncoderError(e)) { 124 - case codable { 125 - codable.Binary(value) -> Ok(value) 126 - _ -> Error(ExpectedBinary) 127 - } 128 - } 129 - 130 - pub fn list(coder: Coder(t, EncoderError(ee), de)) { 131 - fn(codable: Codable) -> Result(List(t), EncoderError(ee)) { 132 - case codable { 133 - codable.List(value) -> 134 - list.map(value, coder.encode) 135 - |> result.all() 136 - _ -> Error(ExpectedList) 137 - } 138 - } 139 - } 140 - 141 - pub fn record(coder: Coder(t, EncoderError(ee), de)) { 142 - fn(codable: Codable) -> Result(Dict(String, t), EncoderError(ee)) { 143 - case codable { 144 - codable.Record(value) -> 145 - dict.to_list(value) 146 - |> list.map(fn(kv) { 147 - let #(k, v) = kv 148 - use v <- result.map(coder.encode(v)) 149 - #(k, v) 150 - }) 151 - |> result.all() 152 - |> result.map(dict.from_list) 153 - _ -> Error(ExpectedList) 154 - } 155 - } 156 - } 157 - 158 - pub fn enum(error: ee) -> EnumEncoderBuilder(t, ee) { 159 - EnumEncoderBuilder(cases: [], error:) 160 - } 161 - 162 - pub fn variant( 163 - enum_encoder: EnumEncoderBuilder(t, ee), 164 - tag: String, 165 - encoder: Encoder(t, EncoderError(ee)), 166 - ) -> EnumEncoderBuilder(t, ee) { 167 - EnumEncoderBuilder(..enum_encoder, cases: [ 168 - VariantCoder(tag, encoder), 169 - ..enum_encoder.cases 170 - ]) 171 - } 172 - 173 - fn encode_variant( 174 - tag: String, 175 - payload: Codable, 176 - cases: List(VariantCoder(t, EncoderError(ee))), 177 - error: ee, 178 - ) -> Result(t, EncoderError(ee)) { 179 - case cases { 180 - [] -> Error(StructVariantError(error)) 181 - [VariantCoder(case_tag, encoder), ..cases] -> { 182 - case case_tag == tag { 183 - True -> encoder(payload) 184 - False -> encode_variant(tag, payload, cases, error) 185 - } 186 - } 187 - } 188 - } 189 - 190 - pub fn encode_with( 191 - enum_encoder: EnumEncoderBuilder(t, ee), 192 - decoder: fn(t) -> Result(Codable, de), 193 - ) -> Coder(t, EncoderError(ee), de) { 194 - Coder( 195 - encode: fn(input) { 196 - case input { 197 - codable.Struct(tag, payload) -> 198 - encode_variant(tag, payload, enum_encoder.cases, enum_encoder.error) 199 - _ -> Error(ExpectedStruct) 200 - } 201 - }, 202 - decode: decoder, 203 - ) 204 - } 205 - 206 - pub type JsonError { 207 - JsonError(gleam_json.DecodeError) 208 - } 209 - 210 - pub const json = Coder(encode: encode_json, decode: decode_json) 211 - 212 - fn codable_to_json(codable: Codable) -> Json { 213 - case codable { 214 - codable.Nil -> gleam_json.null() 215 - codable.Bool(bool) -> gleam_json.bool(bool) 216 - codable.Int(int) -> gleam_json.int(int) 217 - codable.Float(float) -> gleam_json.float(float) 218 - codable.String(string) -> gleam_json.string(string) 219 - codable.List(list) -> gleam_json.array(list, codable_to_json) 220 - codable.Binary(binary) -> 221 - gleam_json.object([ 222 - #("#type", gleam_json.string("binary")), 223 - #("#payload", gleam_json.string(bit_array.base64_encode(binary, False))), 224 - ]) 225 - codable.Record(record) -> 226 - gleam_json.object([ 227 - #("#type", gleam_json.string("record")), 228 - #( 229 - "#payload", 230 - gleam_json.dict(record, function.identity, codable_to_json), 231 - ), 232 - ]) 233 - codable.Struct(tag, value) -> 234 - gleam_json.object([ 235 - #("#type", gleam_json.string("struct")), 236 - #("#tag", gleam_json.string(tag)), 237 - #("#payload", codable_to_json(value)), 238 - ]) 239 - } 240 - } 241 - 242 - fn json_to_codable() -> decode.Decoder(Codable) { 243 - let typed_decoder = { 244 - use type_key <- decode.field("#type", decode.string) 245 - case type_key { 246 - "binary" -> { 247 - use payload <- decode.field("#payload", decode.string) 248 - case bit_array.base64_decode(payload) { 249 - Ok(binary) -> decode.success(codable.Binary(binary)) 250 - Error(_) -> 251 - decode.failure(codable.Binary(<<>>), "base64 encoded string") 252 - } 253 - } 254 - "record" -> { 255 - use payload <- decode.field( 256 - "#payload", 257 - decode.dict(decode.string, json_to_codable()), 258 - ) 259 - decode.success(codable.Record(payload)) 260 - } 261 - "struct" -> { 262 - use tag <- decode.field("#tag", decode.string) 263 - use payload <- decode.field("#payload", json_to_codable()) 264 - decode.success(codable.Struct(tag, payload)) 265 - } 266 - _ -> decode.failure(codable.Nil, "`binary`, `struct`, or `record`") 267 - } 268 - } 269 - use opt <- decode.map( 270 - decode.optional( 271 - decode.one_of(typed_decoder, [ 272 - decode.map(decode.bool, codable.Bool), 273 - decode.map(decode.float, codable.Float), 274 - decode.map(decode.int, codable.Int), 275 - decode.map(decode.string, codable.String), 276 - decode.map(decode.list(json_to_codable()), codable.List), 277 - ]), 278 - ), 279 - ) 280 - case opt { 281 - option.Some(value) -> value 282 - option.None -> codable.Nil 283 - } 284 - } 285 - 286 - fn encode_json(codable: Codable) -> Result(String, JsonError) { 287 - codable 288 - |> codable_to_json() 289 - |> gleam_json.to_string() 290 - |> Ok() 291 - } 292 - 293 - fn decode_json(json_string: String) -> Result(Codable, JsonError) { 294 - gleam_json.parse(json_string, json_to_codable()) 295 - |> result.map_error(JsonError) 296 - } 297 - 298 - pub type MessagePackError { 299 - MessagePackError(glepack_error.DecodeError) 300 - NonTotalMessage(Codable, BitArray) 301 - MapKeysMustBeStrings 302 - StructTagMustBeString 303 - UnknownExtension(Int) 304 - } 305 - 306 - @target(erlang) 307 - pub const messagepack = Coder( 308 - encode: encode_messagepack, 309 - decode: decode_messagepack, 310 - ) 311 - 312 - @target(erlang) 313 - fn codable_to_messagepack(codable: Codable) -> glepack_data.Value { 314 - case codable { 315 - codable.Nil -> glepack_data.Nil 316 - codable.Bool(bool) -> glepack_data.Boolean(bool) 317 - codable.Int(int) -> glepack_data.Integer(int) 318 - codable.Float(float) -> glepack_data.Float(float) 319 - codable.String(string) -> glepack_data.String(string) 320 - codable.Binary(binary) -> glepack_data.Binary(binary) 321 - codable.List(list) -> 322 - glepack_data.Array(list.map(list, codable_to_messagepack)) 323 - codable.Record(dict) -> 324 - dict 325 - |> dict.to_list() 326 - |> list.map(fn(tuple) { 327 - let #(key, value) = tuple 328 - #(glepack_data.String(key), codable_to_messagepack(value)) 329 - }) 330 - |> dict.from_list() 331 - |> glepack_data.Map() 332 - codable.Struct(tag, value) -> { 333 - let assert Ok(key) = glepack_encode.string(tag) 334 - let assert Ok(value) = encode_messagepack(value) 335 - glepack_data.Extension(0, bit_array.append(key, value)) 336 - } 337 - } 338 - } 339 - 340 - @target(erlang) 341 - fn messagepack_to_codable( 342 - value: glepack_data.Value, 343 - ) -> Result(Codable, MessagePackError) { 344 - case value { 345 - glepack_data.Nil -> Ok(codable.Nil) 346 - glepack_data.Boolean(bool) -> Ok(codable.Bool(bool)) 347 - glepack_data.Integer(int) -> Ok(codable.Int(int)) 348 - glepack_data.Float(float) -> Ok(codable.Float(float)) 349 - glepack_data.String(string) -> Ok(codable.String(string)) 350 - glepack_data.Binary(binary) -> Ok(codable.Binary(binary)) 351 - glepack_data.Array(array) -> { 352 - list.map(array, messagepack_to_codable) 353 - |> result.all() 354 - |> result.map(codable.List) 355 - } 356 - glepack_data.Map(map) -> 357 - map 358 - |> dict.to_list() 359 - |> list.map(fn(tuple) { 360 - case tuple { 361 - #(glepack_data.String(string), value) -> { 362 - use codable <- result.try(messagepack_to_codable(value)) 363 - Ok(#(string, codable)) 364 - } 365 - _ -> Error(MapKeysMustBeStrings) 366 - } 367 - }) 368 - |> result.all() 369 - |> result.map(fn(pairs) { pairs |> dict.from_list() |> codable.Record() }) 370 - glepack_data.Extension(0, bit_array) -> { 371 - use #(tag, trailer) <- result.try( 372 - glepack_decode.value(bit_array) |> result.map_error(MessagePackError), 373 - ) 374 - use tag <- result.try(case tag { 375 - glepack_data.String(tag) -> Ok(tag) 376 - _ -> Error(StructTagMustBeString) 377 - }) 378 - use payload <- result.try(decode_messagepack(trailer)) 379 - Ok(codable.Struct(tag, payload)) 380 - } 381 - glepack_data.Extension(ext, _) -> { 382 - Error(UnknownExtension(ext)) 383 - } 384 - } 385 - } 386 - 387 - @target(erlang) 388 - fn encode_messagepack(codable: Codable) -> Result(BitArray, MessagePackError) { 389 - let assert Ok(bits) = 390 - codable_to_messagepack(codable) 391 - |> glepack_encode.value() 392 - Ok(bits) 393 - } 394 - 395 - @target(erlang) 396 - fn decode_messagepack(bit_array: BitArray) -> Result(Codable, MessagePackError) { 397 - use #(value, trailer) <- result.try( 398 - glepack_decode.value(bit_array) |> result.map_error(MessagePackError), 399 - ) 400 - use value <- result.try(messagepack_to_codable(value)) 401 - case trailer { 402 - <<>> -> Ok(value) 403 - _ -> Error(NonTotalMessage(value, trailer)) 404 - } 405 - }
+45 -21
api/src/request.gleam
··· 1 - import codable 2 - import coder 1 + import gleam/dynamic/decode 2 + import gleam/json 3 3 4 4 pub opaque type Request { 5 5 Authenticate(auth_token: String) 6 6 DebugAddCard(card_id: String) 7 7 } 8 8 9 - pub type Error { 10 - InvalidTag 9 + pub fn to_text(request: Request) -> String { 10 + json.to_string(case request { 11 + Authenticate(auth_token) -> 12 + json.object([ 13 + #("#type", json.string("struct")), 14 + #("#tag", json.string("Authenticate")), 15 + #("#payload", json.string(auth_token)), 16 + ]) 17 + DebugAddCard(card_id) -> 18 + json.object([ 19 + #("#type", json.string("struct")), 20 + #("#tag", json.string("DebugAddCard")), 21 + #("#payload", json.string(card_id)), 22 + ]) 23 + }) 11 24 } 12 25 13 - pub fn coder() -> coder.Coder(Request, coder.EncoderError(Error), e) { 14 - coder.enum(InvalidTag) 15 - |> coder.variant( 16 - "Authenticate", 17 - coder.map_encoder(coder.string, Authenticate), 18 - ) 19 - |> coder.variant( 20 - "DebugAddCard", 21 - coder.map_encoder(coder.string, DebugAddCard), 22 - ) 23 - |> coder.encode_with(fn(request) { 24 - Ok(case request { 25 - Authenticate(auth_token) -> 26 - codable.Struct("Authenticate", codable.String(auth_token)) 27 - DebugAddCard(card_id) -> 28 - codable.Struct("DebugAddCard", codable.String(card_id)) 29 - }) 26 + pub fn from_text(text: String) -> Result(Request, json.DecodeError) { 27 + json.parse(text, { 28 + use ty <- decode.field("#type", decode.string) 29 + case ty { 30 + "struct" -> { 31 + use tag <- decode.field("#tag", decode.string) 32 + case tag { 33 + "Authenticate" -> { 34 + use payload <- decode.field("#payload", decode.string) 35 + decode.success(Authenticate(payload)) 36 + } 37 + "DebugAddCard" -> { 38 + use payload <- decode.field("#payload", decode.string) 39 + decode.success(DebugAddCard(payload)) 40 + } 41 + _ -> { 42 + decode.failure(Authenticate(""), "valid #tag") 43 + } 44 + } 45 + } 46 + _ -> { 47 + decode.failure(Authenticate(""), "#type == 'struct'") 48 + } 49 + } 30 50 }) 51 + } 52 + 53 + pub type Error { 54 + InvalidTag 31 55 } 32 56 33 57 pub fn authenticate(auth_token: String) -> Request {