From 3cf057b7f830a1e32b4d0503ecda335f6b52cea4 Mon Sep 17 00:00:00 2001 From: Oliver Linnarsson Date: Tue, 18 Jun 2024 16:11:44 +0200 Subject: [PATCH] feat: Improve DX for working with ctx --- src/handles/ctx.gleam | 91 ++++++++++++++++++++++++++++++++++ test/unit_tests/ctx_test.gleam | 78 +++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 test/unit_tests/ctx_test.gleam diff --git a/src/handles/ctx.gleam b/src/handles/ctx.gleam index ddf6712..39cb79c 100644 --- a/src/handles/ctx.gleam +++ b/src/handles/ctx.gleam @@ -1,3 +1,7 @@ +import gleam/dict +import gleam/dynamic +import gleam/list + pub type Prop { Prop(key: String, value: Value) } @@ -10,3 +14,90 @@ pub type Value { Dict(value: List(Prop)) List(value: List(Value)) } + +/// Transforms String, Int, Float, Bool, List, & Dict to their coresponding ctx.Value type. +/// +/// Anny other types will panic +/// +/// ## Examples +/// +/// ```gleam +/// import handles/ctx +/// +/// ctx.from("Hello World") +/// // -> ctx.Str("Hello World") +/// ``` +/// +/// ```gleam +/// ctx.from(42) +/// // -> ctx.Int(42) +/// ``` +/// +/// ```gleam +/// ctx.from(3.14) +/// // -> ctx.Float(3.14) +/// ``` +/// +/// ```gleam +/// ctx.from([1, 2, 3]) +/// // -> ctx.List([ctx.Int(1), ctx.Int(2), ctx.Int(3)]) +/// ``` +/// +/// ```gleam +/// ctx.from([ +/// 42 |> dynamic.from, +/// 3.14 |> dynamic.from, +/// "Hello" |> dynamic.from, +/// ]) +/// // -> ctx.List([ctx.Int(42), ctx.Float(3.14), ctx.Str("Hello")]) +/// ``` +pub fn from(value: a) -> Value { + from_dynamic(value |> dynamic.from) +} + +fn from_dynamic(value: dynamic.Dynamic) -> Value { + case value |> dynamic.classify { + "String" -> { + let assert Ok(val) = value |> dynamic.string + Str(val) + } + "Int" -> { + let assert Ok(val) = value |> dynamic.int + Int(val) + } + "Float" -> { + let assert Ok(val) = value |> dynamic.float + Float(val) + } + "Bool" -> { + let assert Ok(val) = value |> dynamic.bool + Bool(val) + } + "Dict" -> { + let assert Ok(val) = + value |> dynamic.dict(dynamic.string, dynamic.dynamic) + from_dict(val, from_dynamic) + } + "List" -> { + let assert Ok(val) = value |> dynamic.list(dynamic.dynamic) + from_list(val, from_dynamic) + } + _ -> panic as "Unable to construct ctx from dynamic value" + } +} + +fn from_dict( + source: dict.Dict(String, a), + value_mapper: fn(a) -> Value, +) -> Value { + source + |> dict.to_list + |> list.map(fn(it) { Prop(it.0, value_mapper(it.1)) }) + |> Dict +} + +fn from_list(source: List(a), value_mapper: fn(a) -> Value) -> Value { + source + |> list.map(value_mapper) + |> List +} diff --git a/test/unit_tests/ctx_test.gleam b/test/unit_tests/ctx_test.gleam new file mode 100644 index 0000000..7eaec46 --- /dev/null +++ b/test/unit_tests/ctx_test.gleam @@ -0,0 +1,78 @@ +import gleam/dict +import gleam/dynamic +import gleam/iterator +import gleeunit/should +import handles/ctx +import handles/internal/ctx_utils + +const expected_string = "expected" + +fn gen_levels(levels_to_go: Int) { + case levels_to_go { + 0 -> ctx.Str(expected_string) + _ -> ctx.Dict([ctx.Prop("prop", gen_levels(levels_to_go - 1))]) + } +} + +pub fn drill_shallow_test() { + ctx.Dict([ctx.Prop("prop", ctx.Str(expected_string))]) + |> ctx_utils.drill_ctx(["prop"], _) + |> should.be_ok + |> should.equal(ctx.Str(expected_string)) +} + +pub fn drill_deep_test() { + let depth = 100 + iterator.repeat("prop") + |> iterator.take(depth) + |> iterator.to_list + |> ctx_utils.drill_ctx(gen_levels(depth)) + |> should.be_ok + |> should.equal(ctx.Str(expected_string)) +} + +pub fn from_string_test() { + ctx.from("foo") + |> should.equal(ctx.Str("foo")) +} + +pub fn from_int_test() { + ctx.from(42) + |> should.equal(ctx.Int(42)) +} + +pub fn from_float_test() { + ctx.from(3.14) + |> should.equal(ctx.Float(3.14)) +} + +pub fn from_bool_test() { + ctx.from(True) + |> should.equal(ctx.Bool(True)) +} + +pub fn from_list_test() { + ctx.from([1, 2, 3]) + |> should.equal(ctx.List([ctx.Int(1), ctx.Int(2), ctx.Int(3)])) +} + +pub fn from_dict_test() { + ctx.from( + dict.new() + |> dict.insert("first", 1) + |> dict.insert("second", 2) + |> dict.insert("third", 3), + ) + |> should.equal( + ctx.Dict([ + ctx.Prop("first", ctx.Int(1)), + ctx.Prop("second", ctx.Int(2)), + ctx.Prop("third", ctx.Int(3)), + ]), + ) +} + +pub fn from_mixed_types_test() { + ctx.from([42 |> dynamic.from, 3.14 |> dynamic.from, "Hello" |> dynamic.from]) + |> should.equal(ctx.List([ctx.Int(42), ctx.Float(3.14), ctx.Str("Hello")])) +}