use super::JsonValueExt;
use crate::{
datetime::{self, Date, DateTime, Time},
helper,
openapi,
validation::Validation,
JsonValue, Map, Record, Uuid,
};
use rust_decimal::Decimal;
use std::{
borrow::Cow,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
num::{ParseFloatError, ParseIntError},
str::{FromStr, ParseBoolError},
time::Duration,
};
use url::{self, Url};
pub trait JsonObjectExt {
fn get_bool(&self, key: &str) -> Option<bool>;
fn get_u8(&self, key: &str) -> Option<u8>;
fn get_u16(&self, key: &str) -> Option<u16>;
fn get_u32(&self, key: &str) -> Option<u32>;
fn get_u64(&self, key: &str) -> Option<u64>;
fn get_usize(&self, key: &str) -> Option<usize>;
fn get_i8(&self, key: &str) -> Option<i8>;
fn get_i16(&self, key: &str) -> Option<i16>;
fn get_i32(&self, key: &str) -> Option<i32>;
fn get_i64(&self, key: &str) -> Option<i64>;
fn get_isize(&self, key: &str) -> Option<isize>;
fn get_f32(&self, key: &str) -> Option<f32>;
fn get_f64(&self, key: &str) -> Option<f64>;
fn get_str(&self, key: &str) -> Option<&str>;
fn get_uuid(&self, key: &str) -> Option<Uuid>;
fn get_date(&self, key: &str) -> Option<Date>;
fn get_time(&self, key: &str) -> Option<Time>;
fn get_datetime(&self, key: &str) -> Option<DateTime>;
fn get_duration(&self, key: &str) -> Option<Duration>;
fn get_array(&self, key: &str) -> Option<&Vec<JsonValue>>;
fn get_u64_array(&self, key: &str) -> Option<Vec<u64>>;
fn get_i64_array(&self, key: &str) -> Option<Vec<i64>>;
fn get_f32_array(&self, key: &str) -> Option<Vec<f32>>;
fn get_f64_array(&self, key: &str) -> Option<Vec<f64>>;
fn get_str_array(&self, key: &str) -> Option<Vec<&str>>;
fn get_map_array(&self, key: &str) -> Option<Vec<&Map>>;
fn get_object(&self, key: &str) -> Option<&Map>;
fn parse_bool(&self, key: &str) -> Option<Result<bool, ParseBoolError>>;
fn parse_u8(&self, key: &str) -> Option<Result<u8, ParseIntError>>;
fn parse_u16(&self, key: &str) -> Option<Result<u16, ParseIntError>>;
fn parse_u32(&self, key: &str) -> Option<Result<u32, ParseIntError>>;
fn parse_u64(&self, key: &str) -> Option<Result<u64, ParseIntError>>;
fn parse_usize(&self, key: &str) -> Option<Result<usize, ParseIntError>>;
fn parse_i8(&self, key: &str) -> Option<Result<i8, ParseIntError>>;
fn parse_i16(&self, key: &str) -> Option<Result<i16, ParseIntError>>;
fn parse_i32(&self, key: &str) -> Option<Result<i32, ParseIntError>>;
fn parse_i64(&self, key: &str) -> Option<Result<i64, ParseIntError>>;
fn parse_isize(&self, key: &str) -> Option<Result<isize, ParseIntError>>;
fn parse_f32(&self, key: &str) -> Option<Result<f32, ParseFloatError>>;
fn parse_f64(&self, key: &str) -> Option<Result<f64, ParseFloatError>>;
fn parse_string(&self, key: &str) -> Option<Cow<'_, str>>;
fn parse_array<T: FromStr>(&self, key: &str) -> Option<Vec<T>>;
fn parse_str_array(&self, key: &str) -> Option<Vec<&str>>;
fn parse_enum_values(&self, key: &str) -> Option<Vec<JsonValue>>;
fn parse_object(&self, key: &str) -> Option<&Map>;
fn parse_uuid(&self, key: &str) -> Option<Result<Uuid, uuid::Error>>;
fn parse_decimal(&self, key: &str) -> Option<Result<Decimal, rust_decimal::Error>>;
fn parse_date(&self, key: &str) -> Option<Result<Date, chrono::format::ParseError>>;
fn parse_time(&self, key: &str) -> Option<Result<Time, chrono::format::ParseError>>;
fn parse_datetime(&self, key: &str) -> Option<Result<DateTime, chrono::format::ParseError>>;
fn parse_duration(&self, key: &str) -> Option<Result<Duration, datetime::ParseDurationError>>;
fn parse_url(&self, key: &str) -> Option<Result<Url, url::ParseError>>;
fn parse_ip(&self, key: &str) -> Option<Result<IpAddr, AddrParseError>>;
fn parse_ipv4(&self, key: &str) -> Option<Result<Ipv4Addr, AddrParseError>>;
fn parse_ipv6(&self, key: &str) -> Option<Result<Ipv6Addr, AddrParseError>>;
fn pointer(&self, pointer: &str) -> Option<&JsonValue>;
fn upsert(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Option<JsonValue>;
fn to_query_string(&self) -> String;
fn into_avro_record(self) -> Record;
fn from_entry(key: impl Into<String>, value: impl Into<JsonValue>) -> Self;
fn data_entry(value: Map) -> Self;
fn data_entries(values: Vec<Map>) -> Self;
fn data_item(value: impl Into<JsonValue>) -> Self;
fn data_items<T: Into<JsonValue>>(values: Vec<T>) -> Self;
}
impl JsonObjectExt for Map {
#[inline]
fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key).and_then(|v| v.as_bool())
}
#[inline]
fn get_u8(&self, key: &str) -> Option<u8> {
self.get(key)
.and_then(|v| v.as_u64())
.and_then(|i| u8::try_from(i).ok())
}
#[inline]
fn get_u16(&self, key: &str) -> Option<u16> {
self.get(key)
.and_then(|v| v.as_u64())
.and_then(|i| u16::try_from(i).ok())
}
#[inline]
fn get_u32(&self, key: &str) -> Option<u32> {
self.get(key)
.and_then(|v| v.as_u64())
.and_then(|i| u32::try_from(i).ok())
}
#[inline]
fn get_u64(&self, key: &str) -> Option<u64> {
self.get(key).and_then(|v| v.as_u64())
}
#[inline]
fn get_usize(&self, key: &str) -> Option<usize> {
self.get(key)
.and_then(|v| v.as_u64())
.and_then(|i| usize::try_from(i).ok())
}
#[inline]
fn get_i8(&self, key: &str) -> Option<i8> {
self.get(key)
.and_then(|v| v.as_i64())
.and_then(|i| i8::try_from(i).ok())
}
#[inline]
fn get_i16(&self, key: &str) -> Option<i16> {
self.get(key)
.and_then(|v| v.as_i64())
.and_then(|i| i16::try_from(i).ok())
}
#[inline]
fn get_i32(&self, key: &str) -> Option<i32> {
self.get(key)
.and_then(|v| v.as_i64())
.and_then(|i| i32::try_from(i).ok())
}
#[inline]
fn get_i64(&self, key: &str) -> Option<i64> {
self.get(key).and_then(|v| v.as_i64())
}
#[inline]
fn get_isize(&self, key: &str) -> Option<isize> {
self.get(key)
.and_then(|v| v.as_i64())
.and_then(|i| isize::try_from(i).ok())
}
#[inline]
fn get_f32(&self, key: &str) -> Option<f32> {
self.get(key).and_then(|v| v.as_f64()).map(|f| f as f32)
}
#[inline]
fn get_f64(&self, key: &str) -> Option<f64> {
self.get(key).and_then(|v| v.as_f64())
}
#[inline]
fn get_str(&self, key: &str) -> Option<&str> {
self.get(key).and_then(|v| v.as_str())
}
#[inline]
fn get_uuid(&self, key: &str) -> Option<Uuid> {
self.get_str(key).and_then(|s| s.parse().ok())
}
#[inline]
fn get_date(&self, key: &str) -> Option<Date> {
self.get_str(key).and_then(|s| s.parse().ok())
}
#[inline]
fn get_time(&self, key: &str) -> Option<Time> {
self.get_str(key).and_then(|s| s.parse().ok())
}
#[inline]
fn get_datetime(&self, key: &str) -> Option<DateTime> {
self.get_str(key).and_then(|s| s.parse().ok())
}
#[inline]
fn get_duration(&self, key: &str) -> Option<Duration> {
self.get_str(key)
.and_then(|s| datetime::parse_duration(s).ok())
}
#[inline]
fn get_array(&self, key: &str) -> Option<&Vec<JsonValue>> {
self.get(key).and_then(|v| v.as_array())
}
#[inline]
fn get_u64_array(&self, key: &str) -> Option<Vec<u64>> {
self.get_array(key)
.map(|values| values.iter().filter_map(|v| v.as_u64()).collect())
}
#[inline]
fn get_i64_array(&self, key: &str) -> Option<Vec<i64>> {
self.get_array(key)
.map(|values| values.iter().filter_map(|v| v.as_i64()).collect())
}
#[inline]
fn get_f32_array(&self, key: &str) -> Option<Vec<f32>> {
self.get_array(key).map(|values| {
values
.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect()
})
}
#[inline]
fn get_f64_array(&self, key: &str) -> Option<Vec<f64>> {
self.get_array(key)
.map(|values| values.iter().filter_map(|v| v.as_f64()).collect())
}
#[inline]
fn get_str_array(&self, key: &str) -> Option<Vec<&str>> {
self.get_array(key)
.map(|values| values.iter().filter_map(|v| v.as_str()).collect())
}
#[inline]
fn get_map_array(&self, key: &str) -> Option<Vec<&Map>> {
self.get_array(key).map(|values| {
values
.iter()
.filter_map(|v| v.as_object())
.collect::<Vec<_>>()
})
}
#[inline]
fn get_object(&self, key: &str) -> Option<&Map> {
self.get(key).and_then(|v| v.as_object())
}
fn parse_bool(&self, key: &str) -> Option<Result<bool, ParseBoolError>> {
let value = self.get(key);
value
.and_then(|v| v.as_bool())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_u8(&self, key: &str) -> Option<Result<u8, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_u64())
.and_then(|i| u8::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_u16(&self, key: &str) -> Option<Result<u16, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_u64())
.and_then(|i| u16::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_u32(&self, key: &str) -> Option<Result<u32, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_u64())
.and_then(|i| u32::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_u64(&self, key: &str) -> Option<Result<u64, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_u64())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_usize(&self, key: &str) -> Option<Result<usize, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_u64())
.and_then(|i| usize::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_i8(&self, key: &str) -> Option<Result<i8, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_i64())
.and_then(|i| i8::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_i16(&self, key: &str) -> Option<Result<i16, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_i64())
.and_then(|i| i16::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_i32(&self, key: &str) -> Option<Result<i32, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_i64())
.and_then(|i| i32::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_i64(&self, key: &str) -> Option<Result<i64, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_i64())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_isize(&self, key: &str) -> Option<Result<isize, ParseIntError>> {
let value = self.get(key);
value
.and_then(|v| v.as_i64())
.and_then(|i| isize::try_from(i).ok())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_f32(&self, key: &str) -> Option<Result<f32, ParseFloatError>> {
let value = self.get(key);
value
.and_then(|v| v.as_f64())
.map(|f| Ok(f as f32))
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_f64(&self, key: &str) -> Option<Result<f64, ParseFloatError>> {
let value = self.get(key);
value
.and_then(|v| v.as_f64())
.map(Ok)
.or_else(|| value.and_then(|v| v.as_str()).map(|s| s.parse()))
}
fn parse_string(&self, key: &str) -> Option<Cow<'_, str>> {
self.get(key)
.and_then(|v| {
v.as_str()
.map(|s| Cow::Borrowed(s.trim()))
.or_else(|| Some(v.to_string().into()))
})
.filter(|s| !s.is_empty())
}
fn parse_array<T: FromStr>(&self, key: &str) -> Option<Vec<T>> {
self.get(key)
.and_then(|v| match v {
JsonValue::String(s) => helper::parse_str_array(s, ',')
.into_iter()
.filter_map(|s| (!s.is_empty()).then_some(Cow::Borrowed(s)))
.collect::<Vec<_>>()
.into(),
JsonValue::Array(vec) => {
Some(vec.iter().filter_map(|v| v.parse_string()).collect())
}
_ => None,
})
.and_then(|values| {
let vec = values
.iter()
.filter_map(|s| s.parse().ok())
.collect::<Vec<_>>();
(!vec.is_empty()).then_some(vec)
})
}
fn parse_str_array(&self, key: &str) -> Option<Vec<&str>> {
self.get(key)
.and_then(|v| match v {
JsonValue::String(s) => Some(helper::parse_str_array(s, ',')),
JsonValue::Array(v) => Some(v.iter().filter_map(|v| v.as_str()).collect()),
_ => None,
})
.and_then(|values| {
let vec = values.iter().map(|s| s.trim()).collect::<Vec<_>>();
(!vec.is_empty()).then_some(vec)
})
}
fn parse_enum_values(&self, key: &str) -> Option<Vec<JsonValue>> {
self.get(key)
.and_then(|v| match v {
JsonValue::String(s) => {
let values = helper::parse_str_array(s, '|');
let vec = values
.iter()
.map(|s| {
let s = s.trim();
if let Ok(integer) = s.parse::<i64>() {
JsonValue::Number(integer.into())
} else {
JsonValue::String(s.to_owned())
}
})
.collect::<Vec<_>>();
Some(vec)
}
JsonValue::Array(vec) => Some(vec.clone()),
_ => None,
})
.filter(|vec| !vec.is_empty())
}
#[inline]
fn parse_object(&self, key: &str) -> Option<&Map> {
self.get_object(key).filter(|o| !o.is_empty())
}
fn parse_uuid(&self, key: &str) -> Option<Result<Uuid, uuid::Error>> {
self.get_str(key)
.map(|s| s.trim_start_matches("urn:uuid:"))
.filter(|s| !s.chars().all(|c| c == '0' || c == '-'))
.map(|s| s.parse())
}
#[inline]
fn parse_decimal(&self, key: &str) -> Option<Result<Decimal, rust_decimal::Error>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_date(&self, key: &str) -> Option<Result<Date, chrono::format::ParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_time(&self, key: &str) -> Option<Result<Time, chrono::format::ParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_datetime(&self, key: &str) -> Option<Result<DateTime, chrono::format::ParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_duration(&self, key: &str) -> Option<Result<Duration, datetime::ParseDurationError>> {
self.get_str(key).map(datetime::parse_duration)
}
#[inline]
fn parse_url(&self, key: &str) -> Option<Result<Url, url::ParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_ip(&self, key: &str) -> Option<Result<IpAddr, AddrParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_ipv4(&self, key: &str) -> Option<Result<Ipv4Addr, AddrParseError>> {
self.get_str(key).map(|s| s.parse())
}
#[inline]
fn parse_ipv6(&self, key: &str) -> Option<Result<Ipv6Addr, AddrParseError>> {
self.get_str(key).map(|s| s.parse())
}
fn pointer(&self, pointer: &str) -> Option<&JsonValue> {
let Some(path) = pointer.strip_prefix('/') else {
return None;
};
if let Some(position) = path.find('/') {
let (key, pointer) = path.split_at(position);
self.get(key)?.pointer(pointer)
} else {
self.get(path)
}
}
#[inline]
fn upsert(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Option<JsonValue> {
self.insert(key.into(), value.into())
}
#[inline]
fn to_query_string(&self) -> String {
serde_qs::to_string(&self).unwrap_or_default()
}
fn into_avro_record(self) -> Record {
let mut record = Record::with_capacity(self.len());
for (field, value) in self.into_iter() {
record.push((field, value.into()));
}
record
}
#[inline]
fn from_entry(key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
let mut map = Map::with_capacity(1);
map.insert(key.into(), value.into());
map
}
#[inline]
fn data_entry(value: Map) -> Self {
let mut map = Map::with_capacity(1);
map.insert("entry".to_owned(), value.into());
map
}
#[inline]
fn data_entries(values: Vec<Map>) -> Self {
let mut map = Map::with_capacity(2);
map.insert("num_entries".to_owned(), values.len().into());
map.insert("entries".to_owned(), values.into());
map
}
#[inline]
fn data_item(value: impl Into<JsonValue>) -> Self {
let mut map = Map::with_capacity(1);
map.insert("item".to_owned(), value.into());
map
}
#[inline]
fn data_items<T: Into<JsonValue>>(values: Vec<T>) -> Self {
let mut map = Map::with_capacity(2);
map.insert("num_items".to_owned(), values.len().into());
map.insert("items".to_owned(), values.into());
map
}
}
#[cfg(test)]
mod tests {
use crate::{
extension::{JsonObjectExt, JsonValueExt},
Map,
};
#[test]
fn it_parses_str_array() {
let mut map = Map::new();
map.upsert("roles", vec!["admin", "", "worker"]);
assert_eq!(
map.get_str_array("roles"),
Some(vec!["admin", "", "worker"])
);
assert_eq!(
map.parse_str_array("roles"),
Some(vec!["admin", "", "worker"])
);
assert_eq!(
map.parse_array::<String>("roles"),
Some(vec!["admin".to_owned(), "worker".to_owned()])
);
}
#[test]
fn it_lookups_json_value() {
let mut map = Map::new();
map.upsert("entries", vec![Map::from_entry("name", "alice")]);
map.upsert("total", 1);
assert_eq!(map.pointer("total"), None);
assert_eq!(map.pointer("/total").and_then(|v| v.as_usize()), Some(1));
assert_eq!(
map.pointer("/entries/0/name").and_then(|v| v.as_str()),
Some("alice")
);
}
}