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