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 absent_is_false(obj: &Map<String, Value>, key: &str) -> Result<bool, String> {
match obj.get(key) {
None => Ok(false),
Some(_) => flag(obj, key),
}
}
pub(crate) fn opt_secs(obj: &Map<String, Value>, key: &str) -> Result<Option<i64>, String> {
match obj.get(key) {
None | Some(Value::Null) => Ok(None),
Some(_) => secs(obj, key).map(Some),
}
}
pub(crate) fn opt_count(obj: &Map<String, Value>, key: &str) -> Result<Option<u64>, String> {
match obj.get(key) {
None | Some(Value::Null) => Ok(None),
Some(_) => count(obj, key).map(Some),
}
}
pub(crate) fn opt_flag(obj: &Map<String, Value>, key: &str) -> Result<Option<bool>, String> {
match obj.get(key) {
None | Some(Value::Null) => Ok(None),
Some(_) => flag(obj, key).map(Some),
}
}
pub(crate) fn strings(obj: &Map<String, Value>, key: &str) -> Result<Vec<String>, String> {
match obj.get(key) {
None => Ok(Vec::new()),
Some(_) => list(obj, key, |value| {
value
.as_str()
.map(str::to_owned)
.ok_or_else(|| format!("field {key:?}: a non-string element"))
}),
}
}
pub(crate) fn nested<T>(
obj: &Map<String, Value>,
key: &str,
read: impl Fn(&Map<String, Value>) -> Result<T, String>,
) -> Result<Option<T>, String> {
match obj.get(key) {
None | Some(Value::Null) => Ok(None),
Some(value) => value
.as_object()
.ok_or_else(|| format!("non-object field {key:?}"))
.and_then(read)
.map(Some),
}
}
pub(crate) fn object<T>(
obj: &Map<String, Value>,
key: &str,
read: impl Fn(&Map<String, Value>) -> Result<T, String>,
) -> Result<T, String> {
obj.get(key)
.and_then(Value::as_object)
.ok_or_else(|| format!("missing or non-object field {key:?}"))
.and_then(read)
}
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;