Skip to content

Commit

Permalink
feat: dict
Browse files Browse the repository at this point in the history
  • Loading branch information
Hackder committed Aug 29, 2024
1 parent 83b5fbe commit b282795
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
33 changes: 33 additions & 0 deletions src/toy.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gleam/dict
import gleam/dynamic
import gleam/float
import gleam/int
Expand Down Expand Up @@ -296,6 +297,38 @@ pub fn list(item: Decoder(a)) -> Decoder(List(a)) {
})
}

/// Decodes a `Dict` using the provided key and value decoders
pub fn dict(
key_type: Decoder(a),
value_type: Decoder(b),
) -> Decoder(dict.Dict(a, b)) {
Decoder(fn(value) {
use <- fn(next) { #(dict.new(), next()) }
use map <- result.try(decode_map(value))
use pairs <- result.try(
map
|> dict.to_list
|> list.try_map(fn(pair) {
let #(k, v) = pair
use k <- result.try(
map_errors(key_type, prepend_path(_, ["keys"])).run(k).1,
)
use v <- result.try(
map_errors(value_type, prepend_path(_, ["values"])).run(v).1,
)
Ok(#(k, v))
}),
)
Ok(dict.from_list(pairs))
})
}

@external(erlang, "toy_ffi", "decode_map")
@external(javascript, "./toy_ffi.mjs", "decode_map")
fn decode_map(
a: dynamic.Dynamic,
) -> Result(dict.Dict(dynamic.Dynamic, dynamic.Dynamic), List(ToyError))

@external(erlang, "toy_ffi", "is_nullish")
@external(javascript, "./toy_ffi.mjs", "is_nullish")
fn is_nullish(data: a) -> Bool
Expand Down
4 changes: 3 additions & 1 deletion src/toy_ffi.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-module(toy_ffi).
-export([index/2, is_nullish/1, decode_option/1]).
-export([index/2, is_nullish/1, decode_option/1, decode_map/1]).

index(Tuple, Index) when is_tuple(Tuple) andalso is_integer(Index) ->
try
Expand Down Expand Up @@ -33,3 +33,5 @@ decode_option(Value) ->
_ -> {error, nil}
end.

decode_map(Data) when is_map(Data) -> {ok, Data};
decode_map(Data) -> {error, [{toy_error, {invalid_type, <<"Dict">>, gleam_stdlib:classify_dynamic(Data)}, []}]}.
35 changes: 34 additions & 1 deletion src/toy_ffi.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Ok, Error } from "./gleam.mjs";
import { Ok, Error, List } from "./gleam.mjs";
import { Some, None } from "../gleam_stdlib/gleam/option.mjs";
import { default as Dict } from "../gleam_stdlib/dict.mjs";
import { classify_dynamic } from "../gleam_stdlib/gleam_stdlib.mjs";
import { ToyError, InvalidType } from "./toy.mjs";

const NOTHING = Symbol.for("nothing");

Expand Down Expand Up @@ -46,3 +48,34 @@ export function decode_option(value) {

return new Error(undefined);
}

function decoder_error(expected, actual) {
return new Error(
List.fromArray([
new ToyError(
new InvalidType(expected, classify_dynamic(actual)),
List.fromArray([]),
),
]),
);
}

export function decode_map(data) {
if (data instanceof Dict) {
return new Ok(data);
}
if (data instanceof Map || data instanceof WeakMap) {
return new Ok(Dict.fromMap(data));
}
if (data == null) {
return decoder_error("Dict", data);
}
if (typeof data !== "object") {
return decoder_error("Dict", data);
}
const proto = Object.getPrototypeOf(data);
if (proto === Object.prototype || proto === null) {
return new Ok(Dict.fromObject(data));
}
return decoder_error("Dict", data);
}
36 changes: 36 additions & 0 deletions test/toy_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,42 @@ pub fn empty_simple_record_test() {
)
}

pub fn dict_valid_test() {
let decoder = toy.dict(toy.string, toy.int)

let data = dict.from_list([#("hi", 1), #("another", 2)])

data
|> dynamic.from
|> toy.decode(decoder)
|> should.equal(Ok(data))
}

pub fn dict_invalid_test() {
let decoder = toy.dict(toy.string, toy.int)

dynamic.from(Nil)
|> toy.decode(decoder)
|> should.equal(Error([toy.ToyError(toy.InvalidType("Dict", "Nil"), [])]))
}

pub fn dict_invalid_value_test() {
let decoder = toy.dict(toy.string, toy.int)

let data =
dict.from_list([
#("hi", dynamic.from(1)),
#("another", dynamic.from("thing")),
])

data
|> dynamic.from
|> toy.decode(decoder)
|> should.equal(
Error([toy.ToyError(toy.InvalidType("Int", "String"), ["values"])]),
)
}

pub type Sizing {
Automatic
Fixed(width: Float, height: Float)
Expand Down

0 comments on commit b282795

Please sign in to comment.