use {
crate::{
diver::Diver,
errors::IqInternalError,
*,
},
serde::{
Serialize,
de::DeserializeOwned,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IqFormat {
Primitive,
Json,
JsonPretty,
Size,
}
pub fn extract_string_checked<T: Serialize, P: IqPath>(
source: &T,
path: P,
format: IqFormat,
) -> Result<Option<String>, IqError> {
let keys: Vec<&str> = path.keys().collect();
let mut diver = Diver::new(&keys, format);
if keys.is_empty() {
match format {
IqFormat::Primitive => {
diver.set_return_next_primitive();
}
IqFormat::Json => {
return serde_json::to_string(source)
.map_err(IqError::Json)
.map(Some);
}
IqFormat::JsonPretty => {
return serde_json::to_string_pretty(source)
.map_err(IqError::Json)
.map(Some);
}
IqFormat::Size => {
return Ok(Sizer::count(source).map(|n| n.to_string()));
}
}
}
match source.serialize(&mut diver) {
Ok(()) => Ok(None), Err(IqInternalError::Found(json)) => Ok(Some(json)),
Err(IqInternalError::Message(msg)) => Err(IqError::Serde(msg)),
Err(IqInternalError::Json(err)) => Err(IqError::Json(err)),
Err(IqInternalError::IndexExpected) => Ok(None), Err(IqInternalError::LengthRequired) => Ok(None), Err(IqInternalError::OutOfBounds) => Ok(None), Err(IqInternalError::Count(n)) => Ok(Some(n.to_string())),
Err(IqInternalError::NoCount) => Ok(None),
}
}
pub fn extract_string<T: Serialize, P: IqPath>(
source: &T,
path: P,
format: IqFormat,
) -> Option<String> {
extract_string_checked(source, path, format).unwrap_or(None)
}
pub fn extract_json<T: Serialize, P: IqPath>(
source: &T,
path: P,
) -> Option<String> {
extract_string(source, path, IqFormat::Json)
}
pub fn extract_json_pretty<T: Serialize, P: IqPath>(
source: &T,
path: P,
) -> Option<String> {
extract_string(source, path, IqFormat::JsonPretty)
}
pub fn extract_primitive<T: Serialize, P: IqPath>(
source: &T,
path: P,
) -> Option<String> {
extract_string(source, path, IqFormat::Primitive)
}
pub fn extract_value<T: Serialize, P: IqPath, V: DeserializeOwned>(
source: &T,
path: P,
) -> Result<Option<V>, IqError> {
let json = extract_string_checked(source, path, IqFormat::Json)?;
let value = json.map(|json| serde_json::from_str(&json)).transpose()?;
Ok(value)
}
pub fn extract_size<T: Serialize, P: IqPath>(
source: &T,
path: P,
) -> Option<usize> {
let keys: Vec<&str> = path.keys().collect();
if keys.first().map_or(true, |s| s.is_empty()) {
return Sizer::count(source);
}
let mut diver = Diver::new(&keys, IqFormat::Size);
match source.serialize(&mut diver) {
Err(IqInternalError::Count(n)) => Some(n),
Err(IqInternalError::NoCount) => None, _ => None, }
}
pub fn size_of<T: Serialize>(source: &T) -> Option<usize> {
Sizer::count(source)
}
#[test]
fn test_extract_by_index() {
#[derive(Debug, PartialEq, Serialize, Clone)]
struct Thing {
pub coord: (&'static str, i16),
pub name: String,
pub v: Vec<i16>,
}
#[derive(Debug, PartialEq, Serialize, Clone)]
struct Container {
pub things: Vec<Thing>,
}
let t1 = Thing {
coord: ("Earth", 4),
name: "some name".to_string(),
v: vec![1, 2, 3, 4],
};
let t2 = Thing {
coord: ("Mars", 7),
name: "other name".to_string(),
v: vec![],
};
let con = Container {
things: vec![t1.clone(), t2.clone()],
};
assert_eq!(
con.extract_primitive("things.1.coord.0"),
Some("Mars".to_string())
);
assert_eq!(
con.extract_primitive("things.-1.coord.0"),
Some("Mars".to_string())
);
assert_eq!(
con.extract_primitive("things.-2.v.2"),
Some("3".to_string())
);
assert_eq!(
con.extract_primitive("things.-2.v.-4"),
Some("1".to_string())
);
assert_eq!(con.extract_primitive("things.-2.v.-5"), None);
assert_eq!(con.extract_primitive("things.-1.v.-1"), None);
}
#[test]
fn test_extract_size() {
#[derive(Debug, PartialEq, Serialize)]
struct Thing {
pub coord: (&'static str, i16),
pub name: String,
pub v: Vec<i16>,
}
let thing = Thing {
coord: ("Earth", 4),
name: "some name".to_string(),
v: vec![1, 2, 3, 4],
};
assert_eq!(extract_size(&thing, "coord").unwrap(), 2);
assert_eq!(extract_size(&thing, "coord.0").unwrap(), 5);
assert_eq!(extract_size(&thing, vec!["coord", "0"]).unwrap(), 5);
assert_eq!(extract_size(&thing, "name").unwrap(), 9);
assert_eq!(extract_size(&thing, "v").unwrap(), 4);
assert_eq!(extract_size(&thing, "").unwrap(), 3);
assert_eq!(extract_size(&thing, vec![]).unwrap(), 3);
}
#[test]
fn test_extract_value_on_empty_path() {
#[derive(Debug, PartialEq, Serialize, serde::Deserialize)]
struct Apple {
name: String,
v: Vec<i16>,
}
let apple = Apple {
name: "some name".to_string(),
v: vec![1, 2, 3, 4],
};
let extracted = extract_value(&apple, vec![]).unwrap();
assert_eq!(extracted, Some(apple));
}
#[test]
fn test_invalid_extract_string() {
#[derive(Debug, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
struct Principal {
federated: String,
}
#[allow(dead_code)]
#[derive(Debug, PartialEq, serde::Serialize)]
#[serde(rename_all = "PascalCase")]
enum PrincipalFilter {
Principal(Principal),
NotPrincipal(Principal),
}
#[derive(Debug, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
struct Statement {
#[serde(flatten, skip_serializing_if = "Option::is_none")]
principal_filter: Option<PrincipalFilter>,
}
let statement = Statement {
principal_filter: Some(PrincipalFilter::Principal(Principal {
federated: "some name".to_string(),
})),
};
let v: Option<String> = statement.extract_primitive("Principal.Federated");
assert!(v.is_some()); let v: Option<String> = statement.extract_primitive("Principal");
assert_eq!(v, None); let v: Option<String> = statement.extract_primitive(vec![]);
assert_eq!(v, None); }