1use std::{borrow::Cow, hash::Hash};
3
4use json_ld::expansion::Action;
5use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject};
6
7pub use json_ld;
8pub use json_ld::*;
9use serde::{Deserialize, Serialize};
10use ssi_rdf::{
11 generator, interpretation::WithGenerator, Interpretation, LdEnvironment, Vocabulary,
12 VocabularyMut,
13};
14
15mod contexts;
16pub use contexts::*;
17
18pub trait JsonLdLoaderProvider {
20 type Loader: json_ld::Loader;
21
22 fn loader(&self) -> &Self::Loader;
23}
24
25impl<E: JsonLdLoaderProvider> JsonLdLoaderProvider for &E {
26 type Loader = E::Loader;
27
28 fn loader(&self) -> &Self::Loader {
29 E::loader(*self)
30 }
31}
32
33#[derive(Debug, thiserror::Error)]
34pub enum JsonLdError {
35 #[error("expansion error: {0}")]
36 Expansion(#[from] json_ld::expansion::Error),
37
38 #[error("interpretation error: {0}")]
39 Interpretation(#[from] linked_data::IntoQuadsError),
40}
41
42#[repr(transparent)]
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(transparent)]
45pub struct CompactJsonLd(pub json_syntax::Value);
46
47impl CompactJsonLd {
48 pub fn from_value_ref(value: &json_syntax::Value) -> &Self {
49 unsafe { std::mem::transmute(value) }
50 }
51}
52
53pub trait Expandable: Sized {
55 type Error: std::fmt::Display;
56
57 type Expanded<I: Interpretation, V: Vocabulary>: LinkedData<I, V>
58 where
59 I: Interpretation,
60 V: VocabularyMut,
61 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
62 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
63
64 #[allow(async_fn_in_trait)]
65 async fn expand_with<I, V>(
66 &self,
67 ld: &mut LdEnvironment<V, I>,
68 loader: &impl Loader,
69 ) -> Result<Self::Expanded<I, V>, Self::Error>
70 where
71 I: Interpretation,
72 V: VocabularyMut,
73 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
74 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
75
76 #[allow(async_fn_in_trait)]
77 async fn expand(
78 &self,
79 loader: &impl Loader,
80 ) -> Result<Self::Expanded<WithGenerator<generator::Blank>, ()>, Self::Error> {
81 let mut ld = LdEnvironment::default();
82 self.expand_with(&mut ld, loader).await
83 }
84}
85
86impl Expandable for CompactJsonLd {
87 type Error = JsonLdError;
88 type Expanded<I, V>
89 = json_ld::ExpandedDocument<V::Iri, V::BlankId>
90 where
91 I: Interpretation,
92 V: VocabularyMut,
93 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
94 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
95
96 async fn expand_with<I, V>(
97 &self,
98 ld: &mut LdEnvironment<V, I>,
99 loader: &impl Loader,
100 ) -> Result<Self::Expanded<I, V>, Self::Error>
101 where
102 I: Interpretation,
103 V: VocabularyMut,
104 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
105 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
106 {
107 let expanded = self
108 .0
109 .expand_full(
110 &mut ld.vocabulary,
111 Default::default(),
112 None,
113 loader,
114 json_ld::expansion::Options {
115 policy: json_ld::expansion::Policy {
116 invalid: Action::Reject,
117 allow_undefined: false,
118 ..Default::default()
119 },
120 ..Default::default()
121 },
122 (),
123 )
124 .await?;
125
126 Ok(expanded)
127 }
128}
129
130pub trait JsonLdObject {
132 fn json_ld_context(&self) -> Option<Cow<json_ld::syntax::Context>> {
134 None
135 }
136}
137
138impl JsonLdObject for CompactJsonLd {
139 fn json_ld_context(&self) -> Option<Cow<json_ld::syntax::Context>> {
140 json_syntax::from_value(self.0.as_object()?.get("@context").next()?.clone())
141 .map(Cow::Owned)
142 .ok()
143 }
144}
145
146pub trait JsonLdNodeObject: JsonLdObject {
147 fn json_ld_type(&self) -> JsonLdTypes {
148 JsonLdTypes::default()
149 }
150}
151
152#[derive(Debug)]
153pub struct JsonLdTypes<'a> {
154 static_: &'static [&'static str],
155 non_static: Cow<'a, [String]>,
156}
157
158impl Default for JsonLdTypes<'_> {
159 fn default() -> Self {
160 Self::new(&[], Cow::Owned(vec![]))
161 }
162}
163
164impl<'a> JsonLdTypes<'a> {
165 pub fn new(static_: &'static [&'static str], non_static: Cow<'a, [String]>) -> Self {
166 Self {
167 static_,
168 non_static,
169 }
170 }
171
172 pub fn len(&self) -> usize {
173 self.static_.len() + self.non_static.len()
174 }
175
176 pub fn is_empty(&self) -> bool {
177 self.static_.is_empty() && self.non_static.is_empty()
178 }
179
180 pub fn reborrow(&self) -> JsonLdTypes {
181 JsonLdTypes {
182 static_: self.static_,
183 non_static: Cow::Borrowed(&self.non_static),
184 }
185 }
186}
187
188impl From<&'static &'static str> for JsonLdTypes<'_> {
189 fn from(value: &'static &'static str) -> Self {
190 Self::new(std::slice::from_ref(value), Cow::Owned(vec![]))
191 }
192}
193
194impl<'a> From<&'a [String]> for JsonLdTypes<'a> {
195 fn from(value: &'a [String]) -> Self {
196 Self::new(&[], Cow::Borrowed(value))
197 }
198}
199
200impl From<Vec<String>> for JsonLdTypes<'_> {
201 fn from(value: Vec<String>) -> Self {
202 Self::new(&[], Cow::Owned(value))
203 }
204}
205
206impl<'a> From<Cow<'a, [String]>> for JsonLdTypes<'a> {
207 fn from(value: Cow<'a, [String]>) -> Self {
208 Self::new(&[], value)
209 }
210}
211
212impl serde::Serialize for JsonLdTypes<'_> {
213 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
214 where
215 S: serde::Serializer,
216 {
217 use serde::ser::SerializeSeq;
218 let mut seq = serializer.serialize_seq(Some(self.static_.len() + self.non_static.len()))?;
219 for ty in self.static_ {
220 seq.serialize_element(ty)?;
221 }
222 for ty in self.non_static.as_ref() {
223 seq.serialize_element(ty)?;
224 }
225 seq.end()
226 }
227}
228
229impl<'a> From<JsonLdTypes<'a>> for Vec<String> {
230 fn from(value: JsonLdTypes<'a>) -> Self {
231 let mut result = Vec::with_capacity(value.len());
232 result.extend(value.static_.iter().map(|s| s.to_string()));
233 result.extend(value.non_static.into_owned());
234 result
235 }
236}
237
238pub struct WithContext<T> {
239 pub context: Option<json_ld::syntax::Context>,
240 pub value: T,
241}
242
243impl<T> WithContext<T> {
244 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> WithContext<U> {
245 WithContext {
246 context: self.context,
247 value: f(self.value),
248 }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use crate::{CompactJsonLd, ContextLoader, Expandable};
255
256 #[tokio::test]
257 async fn accept_defined_type() {
258 let input = CompactJsonLd(json_syntax::json!({
259 "@context": { "Defined": "http://example.org/#Defined" },
260 "@type": ["Defined"]
261 }));
262
263 assert!(input.expand(&ContextLoader::default()).await.is_ok());
264 }
265
266 #[tokio::test]
267 async fn reject_undefined_type() {
268 let input = CompactJsonLd(json_syntax::json!({
269 "@type": ["Undefined"]
270 }));
271
272 assert!(input.expand(&ContextLoader::default()).await.is_err());
273 }
274
275 #[tokio::test]
276 async fn accept_defined_property() {
277 let input = CompactJsonLd(json_syntax::json!({
278 "@context": { "defined": "http://example.org/#defined" },
279 "defined": "foo"
280 }));
281
282 assert!(input.expand(&ContextLoader::default()).await.is_ok());
283 }
284
285 #[tokio::test]
286 async fn reject_undefined_property() {
287 let input = CompactJsonLd(json_syntax::json!({
288 "undefined": "foo"
289 }));
290
291 assert!(input.expand(&ContextLoader::default()).await.is_err());
292 }
293}