import gleam/dynamic import gleam/json import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import json/blueprint/schema.{type SchemaDefinition, Type} as jsch pub type Decoder(t) = #(dynamic.Decoder(t), SchemaDefinition) pub type FieldDecoder(t) = #(dynamic.Decoder(t), #(String, SchemaDefinition)) pub fn generate_json_schema(decoder: Decoder(t)) -> json.Json { let #(_, schema) = decoder jsch.to_json(jsch.new_schema(schema)) } pub fn get_dynamic_decoder(decoder: Decoder(t)) -> dynamic.Decoder(t) { let #(dyn_decoder, _) = decoder dyn_decoder } pub fn decode( using decoder: Decoder(t), from json_string: String, ) -> Result(t, json.DecodeError) { let #(dyn_decoder, _) = decoder json.decode(from: json_string, using: dyn_decoder) } pub fn string() -> Decoder(String) { #(dynamic.string, Type(jsch.StringType)) } pub fn int() -> Decoder(Int) { #(dynamic.int, Type(jsch.IntegerType)) } pub fn float() -> Decoder(Float) { #(dynamic.float, Type(jsch.NumberType)) } pub fn bool() -> Decoder(Bool) { #(dynamic.bool, Type(jsch.BooleanType)) } pub fn list(of decoder_type: Decoder(inner)) -> Decoder(List(inner)) { let #(decoder, schema) = decoder_type #(dynamic.list(decoder), jsch.Array(Some(schema))) } pub fn optional(of decode: Decoder(inner)) -> Decoder(Option(inner)) { let #(decoder, schema) = decode #(dynamic.optional(decoder), jsch.Nullable(schema)) } pub fn field(named name: String, of inner_type: Decoder(t)) -> FieldDecoder(t) { let #(decoder, schema) = inner_type #(dynamic.field(name, decoder), #(name, schema)) } pub fn optional_field( named name: String, of inner_type: Decoder(t), ) -> FieldDecoder(Option(t)) { let #(decoder, schema) = inner_type #(dynamic.optional_field(name, decoder), #(name, jsch.Nullable(schema))) } /// Function to encode a union type into a JSON object. /// The function takes a value and an encoder function that returns a tuple of the type name and the JSON value. /// ///> [!IMPORTANT] ///> Make sure to update the decoder function accordingly. /// /// ## Example /// ```gleam /// type Shape { /// Circle(Float) /// Rectangle(Float, Float) /// } /// /// let shape_encoder = union_type_encoder(fn(shape) { /// case shape { /// Circle(radius) -> #("circle", json.object([#("radius", json.float(radius))])) /// Rectangle(width, height) -> #( /// "rectangle", /// json.object([ /// #("width", json.float(width)), /// #("height", json.float(height)) /// ]) /// ) /// } /// }) /// ``` /// /// pub fn union_type_encoder( value of: a, encoder_fn encoder_fn: fn(a) -> #(String, json.Json), ) -> json.Json { let #(field_name, json_value) = encoder_fn(of) json.object([#("type", json.string(field_name)), #("data", json_value)]) } /// Function to defined a decoder for a union types. /// The function takes a list of decoders for each possible type of the union. /// ///> [!IMPORTANT] ///> Make sure to add tests for every possible type of the union because it is not possible to check for exhaustiveness in the case. /// /// ## Example /// ```gleam /// type Shape { /// Circle(Float) /// Rectangle(Float, Float) /// } /// /// let shape_decoder = union_type_decoder([ /// #("circle", decode1(Circle, field("radius", float()))), /// #("rectangle", decode2(Rectangle, /// field("width", float()), /// field("height", float()) /// )) /// ]) /// ``` /// pub fn union_type_decoder( constructor_decoders decoders: List(#(String, Decoder(a))), ) -> Decoder(a) { let constructor = fn(type_str: String, data: dynamic.Dynamic) -> Result( a, List(dynamic.DecodeError), ) { decoders |> list.find_map(fn(dec) { case dec.0 == type_str { True -> { let #(dyn_decoder, _) = dec.1 Ok(dyn_decoder(data)) } _ -> Error([]) } }) |> result.map_error(fn(_) { let valid_types = decoders |> list.map(fn(dec) { dec.0 }) |> string.join(", ") [ dynamic.DecodeError( expected: "valid constructor type, one of: " <> valid_types, found: type_str, path: [], ), ] }) |> result.flatten } let enum_decoder = fn(data) { dynamic.decode2( constructor, dynamic.field("type", dynamic.string), dynamic.field("data", dynamic.dynamic), )(data) |> result.flatten } #( enum_decoder, list.map(decoders, fn(field_dec) { let #(name, dec) = field_dec jsch.Object( [#("type", jsch.Enum([json.string(name)])), #("data", dec.1)], Some(False), Some(["type", "data"]), ) }) |> jsch.OneOf, ) } pub fn map(decoder decoder: Decoder(a), over foo: fn(a) -> b) -> Decoder(b) { let #(dyn_dec, schema) = decoder #(fn(input) { result.map(dyn_dec(input), foo) }, schema) } pub fn tuple2( first decode1: Decoder(a), second decode2: Decoder(b), ) -> Decoder(#(a, b)) { let #(decoder1, schema1) = decode1 let #(decoder2, schema2) = decode2 #( dynamic.tuple2(decoder1, decoder2), jsch.DetailedArray( None, Some([schema1, schema2]), Some(2), Some(2), None, None, None, None, ), ) } pub fn tuple3( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), ) -> Decoder(#(a, b, c)) { let #(decoder1, schema1) = decode1 let #(decoder2, schema2) = decode2 let #(decoder3, schema3) = decode3 #( dynamic.tuple3(decoder1, decoder2, decoder3), jsch.DetailedArray( None, Some([schema1, schema2, schema3]), Some(3), Some(3), None, None, None, None, ), ) } pub fn tuple4( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), ) -> Decoder(#(a, b, c, d)) { let #(decoder1, schema1) = decode1 let #(decoder2, schema2) = decode2 let #(decoder3, schema3) = decode3 let #(decoder4, schema4) = decode4 #( dynamic.tuple4(decoder1, decoder2, decoder3, decoder4), jsch.DetailedArray( None, Some([schema1, schema2, schema3, schema4]), Some(4), Some(4), None, None, None, None, ), ) } pub fn tuple5( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), fifth decode5: Decoder(e), ) -> Decoder(#(a, b, c, d, e)) { let #(decoder1, schema1) = decode1 let #(decoder2, schema2) = decode2 let #(decoder3, schema3) = decode3 let #(decoder4, schema4) = decode4 let #(decoder5, schema5) = decode5 #( dynamic.tuple5(decoder1, decoder2, decoder3, decoder4, decoder5), jsch.DetailedArray( None, Some([schema1, schema2, schema3, schema4, schema5]), Some(5), Some(5), None, None, None, None, ), ) } pub fn tuple6( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), fifth decode5: Decoder(e), sixth decode6: Decoder(f), ) -> Decoder(#(a, b, c, d, e, f)) { let #(decoder1, schema1) = decode1 let #(decoder2, schema2) = decode2 let #(decoder3, schema3) = decode3 let #(decoder4, schema4) = decode4 let #(decoder5, schema5) = decode5 let #(decoder6, schema6) = decode6 #( dynamic.tuple6(decoder1, decoder2, decoder3, decoder4, decoder5, decoder6), jsch.DetailedArray( None, Some([schema1, schema2, schema3, schema4, schema5, schema6]), Some(6), Some(6), None, None, None, None, ), ) } fn create_object_schema( fields: List(#(String, SchemaDefinition)), ) -> SchemaDefinition { jsch.Object( fields, Some(False), Some( list.filter_map(fields, fn(field_dec) { case field_dec { #(_, jsch.Nullable(_)) -> Error(Nil) #(name, _) -> Ok(name) } }), ), ) } pub fn decode0(constructor: t) -> Decoder(t) { // TODO: Disabled for now. For so reason the when running in the JS target the check fails with the following error: // > DecodeError(expected: "{}", found: "//js({})", ...) // // let check = dynamic.from(dict.from_list([])) #( fn(_value) { Ok(constructor) // case value { // x if x == check -> { // Ok(constructor) // } // x -> // Error([ // dynamic.DecodeError( // expected: "{}", // found: string.inspect(x), // path: [], // ), // ]) // } }, jsch.Object([], Some(False), None), ) } pub fn decode1(constructor: fn(t1) -> t, t1: FieldDecoder(t1)) -> Decoder(t) { let #(decoder, schema) = t1 #(dynamic.decode1(constructor, decoder), create_object_schema([schema])) } pub fn decode2( constructor: fn(t1, t2) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 #( dynamic.decode2(constructor, decoder1, decoder2), create_object_schema([schema1, schema2]), ) } pub fn decode3( constructor: fn(t1, t2, t3) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 #( dynamic.decode3(constructor, decoder1, decoder2, decoder3), create_object_schema([schema1, schema2, schema3]), ) } pub fn decode4( constructor: fn(t1, t2, t3, t4) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 #( dynamic.decode4(constructor, decoder1, decoder2, decoder3, decoder4), create_object_schema([schema1, schema2, schema3, schema4]), ) } pub fn decode5( constructor: fn(t1, t2, t3, t4, t5) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), t5: FieldDecoder(t5), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 let #(decoder5, schema5) = t5 #( dynamic.decode5( constructor, decoder1, decoder2, decoder3, decoder4, decoder5, ), create_object_schema([schema1, schema2, schema3, schema4, schema5]), ) } pub fn decode6( constructor: fn(t1, t2, t3, t4, t5, t6) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), t5: FieldDecoder(t5), t6: FieldDecoder(t6), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 let #(decoder5, schema5) = t5 let #(decoder6, schema6) = t6 #( dynamic.decode6( constructor, decoder1, decoder2, decoder3, decoder4, decoder5, decoder6, ), create_object_schema([schema1, schema2, schema3, schema4, schema5, schema6]), ) } pub fn decode7( constructor: fn(t1, t2, t3, t4, t5, t6, t7) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), t5: FieldDecoder(t5), t6: FieldDecoder(t6), t7: FieldDecoder(t7), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 let #(decoder5, schema5) = t5 let #(decoder6, schema6) = t6 let #(decoder7, schema7) = t7 #( dynamic.decode7( constructor, decoder1, decoder2, decoder3, decoder4, decoder5, decoder6, decoder7, ), create_object_schema([ schema1, schema2, schema3, schema4, schema5, schema6, schema7, ]), ) } pub fn decode8( constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), t5: FieldDecoder(t5), t6: FieldDecoder(t6), t7: FieldDecoder(t7), t8: FieldDecoder(t8), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 let #(decoder5, schema5) = t5 let #(decoder6, schema6) = t6 let #(decoder7, schema7) = t7 let #(decoder8, schema8) = t8 #( dynamic.decode8( constructor, decoder1, decoder2, decoder3, decoder4, decoder5, decoder6, decoder7, decoder8, ), create_object_schema([ schema1, schema2, schema3, schema4, schema5, schema6, schema7, schema8, ]), ) } pub fn decode9( constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8, t9) -> t, t1: FieldDecoder(t1), t2: FieldDecoder(t2), t3: FieldDecoder(t3), t4: FieldDecoder(t4), t5: FieldDecoder(t5), t6: FieldDecoder(t6), t7: FieldDecoder(t7), t8: FieldDecoder(t8), t9: FieldDecoder(t9), ) -> Decoder(t) { let #(decoder1, schema1) = t1 let #(decoder2, schema2) = t2 let #(decoder3, schema3) = t3 let #(decoder4, schema4) = t4 let #(decoder5, schema5) = t5 let #(decoder6, schema6) = t6 let #(decoder7, schema7) = t7 let #(decoder8, schema8) = t8 let #(decoder9, schema9) = t9 #( dynamic.decode9( constructor, decoder1, decoder2, decoder3, decoder4, decoder5, decoder6, decoder7, decoder8, decoder9, ), create_object_schema([ schema1, schema2, schema3, schema4, schema5, schema6, schema7, schema8, schema9, ]), ) }