use std::{fmt, marker::PhantomData}; use serde::{ de::{self, MapAccess, Visitor}, Deserialize, Deserializer, }; pub trait LDObject { fn from_iri(iri: &str) -> Self; fn get_iri(&self) -> String; } impl LDObject for Option where T: LDObject, { fn from_iri(iri: &str) -> Self { todo!() } fn get_iri(&self) -> String { todo!() } } // Allows a value that's a string to be expanded into an object (move string into id property via From) // AND the serialization of that object itself pub fn expand_partial<'de, T, D>(deserializer: D) -> Result where T: Deserialize<'de> + LDObject, D: Deserializer<'de>, { struct BaseExpander(PhantomData T>); impl<'de, T> Visitor<'de> for BaseExpander where T: Deserialize<'de> + LDObject, { type Value = T; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string or map") } fn visit_str(self, value: &str) -> Result where E: de::Error, { Ok(LDObject::from_iri(value)) } fn visit_map(self, map: M) -> Result where M: MapAccess<'de>, { Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) } } deserializer.deserialize_any(BaseExpander(PhantomData)) } pub(super) fn expand_partial_into_vec<'de, T, D>(deserializer: D) -> Result, D::Error> where T: Deserialize<'de> + LDObject, D: Deserializer<'de>, { struct BaseExpander(PhantomData T>); impl<'de, T> Visitor<'de> for BaseExpander where T: Deserialize<'de> + LDObject, { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string or map") } fn visit_str(self, value: &str) -> Result where E: de::Error, { Ok(vec![LDObject::from_iri(value)]) } fn visit_map(self, map: M) -> Result where M: MapAccess<'de>, { Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)).map(|v| vec![v]) } fn visit_seq(self, visitor: S) -> Result where S: serde::de::SeqAccess<'de>, { Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(visitor)) } } deserializer.deserialize_any(BaseExpander(PhantomData)) } // Allows deserialization of a single item into a vector of that item // As long as they implement the From trait pub(super) fn into_vec<'de, D, Out>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, Out: serde::Deserialize<'de>, { struct VecVisitor(PhantomData>); impl<'de, Out> Visitor<'de> for VecVisitor where Out: serde::Deserialize<'de>, { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let name = std::any::type_name::(); formatter.write_str(format!("{} or Vec<{}>", name, name).as_str()) } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { Deserialize::deserialize(de::value::StrDeserializer::new(value)).map(|v| vec![v]) } fn visit_map(self, map: A) -> Result where A: MapAccess<'de>, { Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)).map(|v| vec![v]) } fn visit_seq(self, visitor: S) -> Result where S: serde::de::SeqAccess<'de>, { Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(visitor)) } } deserializer.deserialize_any(VecVisitor(PhantomData)) } #[cfg(test)] mod tests { use flabk_derive::LD; use serde::Deserialize; use super::expand_partial; #[test] fn expand_partial_populates_iri_from_string() { #[derive(Deserialize, LD, Default)] struct Context { pub id: String, pub context: bool, } #[derive(Deserialize)] struct WithContext { #[serde(deserialize_with = "expand_partial")] pub context: Context, } const JSONLD_INPUT: &str = r#"{"context": "https://www.w3.org/ns/activitystreams"}"#; let result = serde_json::from_str::(JSONLD_INPUT).expect("deserializing with expand"); assert!(result.context.id == "https://www.w3.org/ns/activitystreams"); assert!(result.context.context == false); } #[test] fn expand_partial_expands_into_object_fully() { #[derive(Deserialize, LD, Default)] struct Expanded { pub id: String, pub truth: bool, } #[derive(Deserialize)] struct Expandable { #[serde(deserialize_with = "expand_partial")] pub expansive: Expanded, } const JSONLD_INPUT: &str = r#"{"expansive": { "id": "1", "truth": true }}"#; let result = serde_json::from_str::(JSONLD_INPUT).expect("deserializing with expand"); assert!(result.expansive.id == "1"); assert!(result.expansive.truth); } }