Fix album view crashing due to strings being used for some int IDs

This commit is contained in:
Mikko Ahlroth 2024-02-25 22:00:02 +02:00
parent 3144af69e4
commit 22f8574973
3 changed files with 75 additions and 50 deletions

View file

@ -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))

View file

@ -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()])
}

View file

@ -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: [],
),
])
}
}