diff --git a/src/ibroadcast/library/library.gleam b/src/ibroadcast/library/library.gleam index 49fcb70..9e30eef 100644 --- a/src/ibroadcast/library/library.gleam +++ b/src/ibroadcast/library/library.gleam @@ -9,7 +9,7 @@ import ibroadcast/request.{DecodeFailed} import ibroadcast/authed_request.{type RequestConfig} import ibroadcast/request_params.{type RequestParams} import ibroadcast/http.{type Requestor} -import ibroadcast/map_format +import ibroadcast/library_format pub type Album { Album( @@ -118,7 +118,7 @@ fn settings_decoder() { } fn albums_decoder() { - map_format.decoder(map_format.string_int_decoder(), album_decoder()) + library_format.decoder(library_format.string_int_decoder(), album_decoder()) } fn album_decoder() { @@ -135,7 +135,7 @@ fn album_decoder() { } fn artists_decoder() { - map_format.decoder(map_format.string_int_decoder(), artist_decoder()) + library_format.decoder(library_format.string_int_decoder(), artist_decoder()) } fn artist_decoder() { @@ -153,7 +153,7 @@ fn artist_decoder() { } fn tracks_decoder() { - map_format.decoder(map_format.string_int_decoder(), track_decoder()) + library_format.decoder(library_format.string_int_decoder(), track_decoder()) } fn track_decoder() { @@ -163,9 +163,18 @@ fn track_decoder() { use title <- result.try(dynamic.element(2, dynamic.string)(data)) use genre <- result.try(dynamic.element(3, dynamic.string)(data)) use length <- result.try(dynamic.element(4, dynamic.int)(data)) - use album_id <- result.try(dynamic.element(5, dynamic.int)(data)) - use artwork_id <- result.try(dynamic.element(6, dynamic.int)(data)) - use artist_id <- result.try(dynamic.element(7, dynamic.int)(data)) + use album_id <- result.try(dynamic.element( + 5, + library_format.string_or_int_decoder(), + )(data)) + use artwork_id <- result.try(dynamic.element( + 6, + library_format.string_or_int_decoder(), + )(data)) + use artist_id <- result.try(dynamic.element( + 7, + library_format.string_or_int_decoder(), + )(data)) use enid <- result.try(dynamic.element(8, dynamic.int)(data)) use uploaded_on <- result.try(dynamic.element(9, dynamic.string)(data)) use trashed <- result.try(dynamic.element(10, dynamic.bool)(data)) diff --git a/src/ibroadcast/library_format.gleam b/src/ibroadcast/library_format.gleam new file mode 100644 index 0000000..66ba096 --- /dev/null +++ b/src/ibroadcast/library_format.gleam @@ -0,0 +1,59 @@ +import gleam/result +import gleam/dict.{type Dict} +import gleam/int +import gleam/dynamic.{type Decoder} +import gleam/list +import gleam/string + +/// This decoder will attempt to decode a gleam `Dict` using the provided decoders. +pub fn decoder( + key_decoder: Decoder(k), + val_decoder: Decoder(v), +) -> Decoder(Dict(k, v)) { + fn(data) { + // First decode into a `Dict(Dynamic, Dynamic)` + use dynamic_dict <- result.try(dynamic.dict( + dynamic.dynamic, + dynamic.dynamic, + )(data)) + + let entries = dict.to_list(dynamic_dict) + + // Fold over that dynamic entries list. The accumulator will be the desired + // `Dict(k, v)` + use data, #(dyn_key, dyn_val) <- list.try_fold(entries, dict.new()) + + // Attempt to decode the current value + case key_decoder(dyn_key), val_decoder(dyn_val) { + // If it succeeds insert the new entry + Ok(key), Ok(val) -> Ok(dict.insert(data, key, val)) + + // If the key is not an int, ignore it (there are special string keys that + // we want to ignore) + Error(_), _ -> Ok(data) + + _, Error(ret2) -> + Error([dynamic.DecodeError("Decodable value", string.inspect(ret2), [])]) + } + } +} + +/// A decoder that accepts strings that contain integers. +pub fn string_int_decoder() -> Decoder(Int) { + fn(data) { + use strval <- result.try(dynamic.string(data)) + int.parse(strval) + |> result.replace_error([ + dynamic.DecodeError( + expected: "A string representing an int", + found: strval, + path: [], + ), + ]) + } +} + +/// A decoder that accepts strings that contain integers or integers directly. +pub fn string_or_int_decoder() -> Decoder(Int) { + dynamic.any([dynamic.int, string_int_decoder()]) +} diff --git a/src/ibroadcast/map_format.gleam b/src/ibroadcast/map_format.gleam deleted file mode 100644 index 4ad79a2..0000000 --- a/src/ibroadcast/map_format.gleam +++ /dev/null @@ -1,43 +0,0 @@ -import gleam/result -import gleam/dict.{type Dict} -import gleam/int -import gleam/dynamic.{type Decoder} - -/// This decoder will attempt to decode a gleam `Map` using the provided decoders. -/// If either a key or a value fails to decode, that entry is just ignored. -pub fn decoder( - key_decoder: Decoder(k), - val_decoder: Decoder(v), -) -> Decoder(Dict(k, v)) { - fn(data) { - // First decode into a `Map(Dynamic, Dynamic)` - use dynamic_map <- result.map(dynamic.dict(dynamic.dynamic, dynamic.dynamic)( - data, - )) - // Fold over that dynamic map. The accumulator will be the desired `Map(k, v)` - use map, dyn_key, dyn_val <- dict.fold(dynamic_map, dict.new()) - - // Attempt to decode the current value - case key_decoder(dyn_key), val_decoder(dyn_val) { - // If it succeeds insert the new entry - Ok(key), Ok(val) -> dict.insert(map, key, val) - // Otherwise just ignore it and carry on - _, _ -> map - } - } -} - -/// A decoder that accepts strings that contain integers. -pub fn string_int_decoder() -> Decoder(Int) { - fn(data) { - use strval <- result.try(dynamic.string(data)) - int.parse(strval) - |> result.replace_error([ - dynamic.DecodeError( - expected: "A string representing an int", - found: strval, - path: [], - ), - ]) - } -}