use serde_json::{Map, Value};
const ROWS: &str = "rows";
pub(crate) fn text(obj: &Map<String, Value>, key: &str) -> Result<String, String> {
obj.get(key)
.and_then(Value::as_str)
.map(str::to_owned)
.ok_or_else(|| format!("missing or non-string field {key:?}"))
}
pub(crate) fn flag(obj: &Map<String, Value>, key: &str) -> Result<bool, String> {
obj.get(key)
.and_then(Value::as_bool)
.ok_or_else(|| format!("missing or non-boolean field {key:?}"))
}
pub(crate) fn secs(obj: &Map<String, Value>, key: &str) -> Result<i64, String> {
obj.get(key)
.and_then(Value::as_i64)
.ok_or_else(|| format!("missing or non-integer field {key:?}"))
}
pub(crate) fn count(obj: &Map<String, Value>, key: &str) -> Result<u64, String> {
obj.get(key)
.and_then(Value::as_u64)
.ok_or_else(|| format!("missing or non-integer field {key:?}"))
}
pub(crate) fn exit(obj: &Map<String, Value>) -> Result<i32, String> {
let n = secs(obj, "exit")?;
i32::try_from(n).map_err(|_| "field \"exit\" out of range".to_owned())
}
pub(crate) fn opt_text(obj: &Map<String, Value>, key: &str) -> Result<Option<String>, String> {
match obj.get(key) {
None | Some(Value::Null) => Ok(None),
Some(_) => text(obj, key).map(Some),
}
}
pub(crate) fn opt_exit(obj: &Map<String, Value>, key: &str) -> Result<Option<i32>, String> {
let Some(value) = obj.get(key).filter(|held| !held.is_null()) else {
return Ok(None);
};
let n = value
.as_i64()
.ok_or_else(|| format!("missing or non-integer field {key:?}"))?;
i32::try_from(n)
.map(Some)
.map_err(|_| format!("field {key:?} out of range"))
}
pub(crate) fn rows<T>(
obj: &Map<String, Value>,
read: impl Fn(&Value) -> Result<T, String>,
) -> Result<Vec<T>, String> {
list(obj, ROWS, read)
}
pub(crate) fn list<T>(
obj: &Map<String, Value>,
key: &str,
read: impl Fn(&Value) -> Result<T, String>,
) -> Result<Vec<T>, String> {
obj.get(key)
.and_then(Value::as_array)
.ok_or_else(|| format!("missing or non-array field {key:?}"))?
.iter()
.map(read)
.collect()
}
#[cfg(test)]
mod tests;