ssi_json_ld/
lib.rs

1//! Linked-Data types.
2use 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
18/// Type that provides a JSON-LD document loader.
19pub 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
53/// JSON-LD-Expandable value.
54pub 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
130/// Any type representing a JSON-LD object.
131pub trait JsonLdObject {
132    /// Returns the JSON-LD context attached to `self`.
133    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}