Skip to main content

ld_core/
macros.rs

1/// Implements the Linked-Data resource traits for a `serde` type, mapping it
2/// onto a canonical `rdf:JSON` literal.
3///
4/// The type must implement `serde::Serialize` and `serde::Deserialize`. The
5/// generated impls serialize the value to JSON, canonicalize it, and store it
6/// as a literal of type [`RDF_JSON`](crate::RDF_JSON).
7#[macro_export]
8macro_rules! json_literal {
9	($ty:ty) => {
10		impl<I: $crate::rdfx::Interpretation> $crate::LinkedDataResource<I> for $ty {
11			fn interpretation(
12				&self,
13				_interpretation: &mut I,
14			) -> $crate::ResourceInterpretation<'_, I> {
15				use $crate::{CowRdfTerm, OwnedRdfTerm, RdfLiteral, ResourceInterpretation};
16
17				let mut value = $crate::jstrict::to_value(self).unwrap();
18				value.canonicalize();
19
20				ResourceInterpretation::Uninterpreted(Some(CowRdfTerm::Owned(
21					OwnedRdfTerm::Literal(RdfLiteral::Json(value)),
22				)))
23			}
24		}
25
26		impl<I: $crate::rdfx::Interpretation> $crate::LinkedDataPredicateObjects<I> for $ty {
27			fn visit_objects<S>(&self, mut visitor: S) -> Result<S::Ok, S::Error>
28			where
29				S: $crate::PredicateObjectsVisitor<I>,
30			{
31				visitor.object(self)?;
32				visitor.end()
33			}
34		}
35
36		impl<I: $crate::rdfx::Interpretation> $crate::LinkedDataDeserializePredicateObjects<I>
37			for $ty
38		where
39			I: $crate::rdfx::interpretation::ReverseInterpretation,
40			I::Resource: $crate::rdfx::Resource,
41		{
42			fn deserialize_objects_in<'a, D>(
43				interpretation: &I,
44				dataset: &D,
45				graph: Option<&I::Resource>,
46				objects: impl IntoIterator<Item = &'a I::Resource>,
47				context: $crate::Context<I>,
48			) -> Result<Self, $crate::FromLinkedDataError>
49			where
50				I::Resource: 'a,
51				D: $crate::rdfx::dataset::PatternMatchingDataset<Subject = I::Resource>,
52			{
53				let mut objects = objects.into_iter();
54				match objects.next() {
55					Some(object) => {
56						if objects.next().is_none() {
57							<Self as $crate::LinkedDataDeserializeSubject<I>>::deserialize_subject_in(
58																		interpretation, dataset, graph, object, context,
59																	)
60						} else {
61							Err($crate::FromLinkedDataError::TooManyValues(
62								context.into_iris(interpretation),
63							))
64						}
65					}
66					None => Err($crate::FromLinkedDataError::MissingRequiredValue(
67						context.into_iris(interpretation),
68					)),
69				}
70			}
71		}
72
73		impl<I: $crate::rdfx::Interpretation> $crate::LinkedDataSubject<I> for $ty {
74			fn visit_subject<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
75			where
76				S: $crate::SubjectVisitor<I>,
77			{
78				visitor.end()
79			}
80		}
81
82		impl<I: $crate::rdfx::Interpretation> $crate::LinkedDataDeserializeSubject<I> for $ty
83		where
84			I: $crate::rdfx::interpretation::ReverseInterpretation,
85			I::Resource: $crate::rdfx::Resource,
86		{
87			fn deserialize_subject_in<D>(
88				interpretation: &I,
89				_dataset: &D,
90				_graph: Option<&I::Resource>,
91				resource: &I::Resource,
92				context: $crate::Context<I>,
93			) -> Result<Self, $crate::FromLinkedDataError>
94			where
95				D: $crate::rdfx::dataset::PatternMatchingDataset<Subject = I::Resource>,
96			{
97				let mut literal_ty = None;
98				for l in $crate::rdfx::interpretation::ReverseInterpretation::literals_of(
99					interpretation,
100					resource,
101				) {
102					match l.type_ {
103						$crate::rdfx::CowLiteralType::Any(ref ty) => {
104							let ty_iri = ty.as_iri();
105							if ty_iri.as_str() == $crate::RDF_JSON.as_str() {
106								use $crate::jstrict::Parse;
107								let (json, _) = $crate::jstrict::Value::parse_str(&l.value)
108									.map_err(|_| {
109										$crate::FromLinkedDataError::InvalidLiteral(
110											context.into_iris(interpretation),
111										)
112									})?;
113
114								return $crate::jstrict::from_value(json).map_err(|_| {
115									$crate::FromLinkedDataError::InvalidLiteral(
116										context.into_iris(interpretation),
117									)
118								});
119							} else {
120								literal_ty = Some(ty_iri.into())
121							}
122						}
123						$crate::rdfx::CowLiteralType::LangString(_)
124						| $crate::rdfx::CowLiteralType::DirLangString { .. } => {
125							literal_ty = Some($crate::rdfx::RDF_LANG_STRING.into())
126						}
127					}
128				}
129
130				match literal_ty {
131					Some(ty) => Err($crate::FromLinkedDataError::LiteralTypeMismatch {
132						context: context.into_iris(interpretation),
133						expected: Some(Box::new($crate::RDF_JSON.into())),
134						found: Box::new(ty),
135					}),
136					None => Err($crate::FromLinkedDataError::ExpectedLiteral(
137						context.into_iris(interpretation),
138					)),
139				}
140			}
141		}
142	};
143}
144
145#[cfg(test)]
146mod test {
147	#[derive(Debug, serde::Serialize, serde::Deserialize)]
148	struct Test {
149		field: String,
150	}
151
152	json_literal!(Test);
153
154	#[test]
155	fn json_literal_macro_accepts_a_serde_type() {
156		let test = Test {
157			field: "value".to_owned(),
158		};
159		assert_eq!(test.field, "value");
160	}
161}