Skip to main content

jsonld_core/
indexed.rs

1use crate::object::{InvalidExpandedJson, TryFromJson, TryFromJsonObject};
2use jsonld_syntax::{IntoJson, IntoJsonWithContext, native_stack::on_sufficient_stack};
3use rdfx::vocabulary::VocabularyMut;
4use std::{
5    convert::{TryFrom, TryInto},
6    ops::{Deref, DerefMut},
7};
8
9/// Indexed objects.
10///
11/// Nodes and value objects may be indexed by a string in JSON-LD.
12/// This type is a wrapper around any kind of indexable data.
13///
14/// It is a pointer type that `Deref` into the underlying value.
15#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
16pub struct Indexed<T> {
17    /// Index.
18    index: Option<String>,
19
20    /// Value.
21    value: T,
22}
23
24impl<T> Indexed<T> {
25    /// Creates a non-indexed value, without metadata.
26    pub fn none(value: T) -> Self {
27        Self::new(value, None)
28    }
29
30    /// Create a new (maybe) indexed value.
31    #[inline(always)]
32    pub fn new(value: T, index: Option<String>) -> Self {
33        Indexed { index, value }
34    }
35
36    /// Get a reference to the inner value.
37    #[inline(always)]
38    pub fn inner(&self) -> &T {
39        &self.value
40    }
41
42    /// Mutably borrows the indexed value.
43    pub fn inner_mut(&mut self) -> &mut T {
44        &mut self.value
45    }
46
47    /// Drops the index and returns the underlying value.
48    #[inline(always)]
49    pub fn into_inner(self) -> T {
50        self.value
51    }
52
53    /// Returns the `@index` of the value, if it has one.
54    #[inline(always)]
55    pub fn index(&self) -> Option<&str> {
56        match &self.index {
57            Some(index) => Some(index.as_str()),
58            None => None,
59        }
60    }
61
62    /// Sets the `@index` of the value, or removes it when given `None`.
63    #[inline(always)]
64    pub fn set_index(&mut self, index: Option<String>) {
65        self.index = index;
66    }
67
68    /// Consumes the indexed value, returning the inner value and its
69    /// `@index`.
70    #[inline(always)]
71    pub fn into_parts(self) -> (T, Option<String>) {
72        (self.value, self.index)
73    }
74
75    /// Cast the inner value.
76    #[inline(always)]
77    pub fn map_inner<U, F>(self, f: F) -> Indexed<U>
78    where
79        F: FnOnce(T) -> U,
80    {
81        Indexed::new(f(self.value), self.index)
82    }
83
84    /// Cast the inner value.
85    #[inline(always)]
86    pub fn cast<U: From<T>>(self) -> Indexed<U> {
87        Indexed::new(self.value.into(), self.index)
88    }
89
90    /// Try to cast the inner value.
91    ///
92    /// # Errors
93    ///
94    /// Returns the index and the conversion error when the inner value does not convert.
95    #[inline(always)]
96    pub fn try_cast<U: TryFrom<T>>(self) -> Result<Indexed<U>, Indexed<U::Error>> {
97        match self.value.try_into() {
98            Ok(value) => Ok(Indexed::new(value, self.index)),
99            Err(e) => Err(Indexed::new(e, self.index)),
100        }
101    }
102}
103
104impl<T, B, O: TryFromJsonObject<T, B>> TryFromJson<T, B> for Indexed<O> {
105    fn try_from_json_in(vocabulary: &mut impl VocabularyMut<Iri = T, BlankId = B>, value: jstrict::Value) -> Result<Self, InvalidExpandedJson> {
106        match value {
107            jstrict::Value::Object(object) => Self::try_from_json_object_in(vocabulary, object),
108            _ => Err(InvalidExpandedJson::InvalidObject),
109        }
110    }
111}
112
113/// Every nesting level of an expanded document is read through this
114/// implementation (objects, nodes, lists, graphs, included and reverse
115/// nodes alike), so it is the one recursion point of [`TryFromJson`]: each
116/// level runs on sufficient native stack, and a document of any depth is read
117/// on any thread.
118impl<T, B, O: TryFromJsonObject<T, B>> TryFromJsonObject<T, B> for Indexed<O> {
119    fn try_from_json_object_in(vocabulary: &mut impl VocabularyMut<Iri = T, BlankId = B>, mut object: jstrict::Object) -> Result<Self, InvalidExpandedJson> {
120        on_sufficient_stack(move || {
121            let index = match object.remove_unique("@index").map_err(InvalidExpandedJson::duplicate_key)? {
122                Some(index_entry) => match index_entry.value {
123                    jstrict::Value::String(index) => Some(index.to_string()),
124                    _ => return Err(InvalidExpandedJson::InvalidIndex),
125                },
126                None => None,
127            };
128
129            let value = O::try_from_json_object_in(vocabulary, object)?;
130            Ok(Self::new(value, index))
131        })
132    }
133}
134
135impl<T> From<T> for Indexed<T> {
136    #[inline(always)]
137    fn from(value: T) -> Indexed<T> {
138        Indexed::new(value, None)
139    }
140}
141
142impl<T> Deref for Indexed<T> {
143    type Target = T;
144
145    #[inline(always)]
146    fn deref(&self) -> &T {
147        &self.value
148    }
149}
150
151impl<T> DerefMut for Indexed<T> {
152    #[inline(always)]
153    fn deref_mut(&mut self) -> &mut T {
154        &mut self.value
155    }
156}
157
158impl<T> AsRef<T> for Indexed<T> {
159    #[inline(always)]
160    fn as_ref(&self) -> &T {
161        &self.value
162    }
163}
164
165impl<T> AsMut<T> for Indexed<T> {
166    #[inline(always)]
167    fn as_mut(&mut self) -> &mut T {
168        &mut self.value
169    }
170}
171
172/// Every nesting level of an expanded document is written through this
173/// implementation, so each level runs on sufficient native stack.
174impl<T: IntoJsonWithContext<N>, N> IntoJsonWithContext<N> for Indexed<T> {
175    fn into_json_with(self, vocabulary: &N) -> jstrict::Value {
176        on_sufficient_stack(move || {
177            let mut result = self.value.into_json_with(vocabulary);
178
179            if let Some(obj) = result.as_object_mut()
180                && let Some(index) = self.index
181            {
182                obj.insert("@index".into(), index.into_json());
183            }
184
185            result
186        })
187    }
188}