referencing/
resolver.rs

1use core::fmt;
2use std::sync::Arc;
3
4use fluent_uri::Uri;
5use serde_json::Value;
6
7use crate::{list::List, resource::JsonSchemaResource, Draft, Error, Registry, ResourceRef};
8
9/// A reference resolver.
10///
11/// Resolves references against the base URI and looks up the result in the registry.
12#[derive(Clone)]
13pub struct Resolver<'r> {
14    pub(crate) registry: &'r Registry,
15    base_uri: Arc<Uri<String>>,
16    scopes: List<Uri<String>>,
17}
18
19impl PartialEq for Resolver<'_> {
20    fn eq(&self, other: &Self) -> bool {
21        self.base_uri == other.base_uri
22    }
23}
24impl Eq for Resolver<'_> {}
25
26impl fmt::Debug for Resolver<'_> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("Resolver")
29            .field("base_uri", &self.base_uri.as_str())
30            .field("scopes", &{
31                let mut buf = String::from("[");
32                let mut values = self.scopes.iter();
33                if let Some(value) = values.next() {
34                    buf.push_str(value.as_str());
35                }
36                for value in values {
37                    buf.push_str(", ");
38                    buf.push_str(value.as_str());
39                }
40                buf.push(']');
41                buf
42            })
43            .finish()
44    }
45}
46
47impl<'r> Resolver<'r> {
48    /// Create a new `Resolver` with the given registry and base URI.
49    pub(crate) fn new(registry: &'r Registry, base_uri: Arc<Uri<String>>) -> Self {
50        Self {
51            registry,
52            base_uri,
53            scopes: List::new(),
54        }
55    }
56    #[must_use]
57    pub fn base_uri(&self) -> Arc<Uri<String>> {
58        self.base_uri.clone()
59    }
60    /// Resolve a reference to the resource it points to.
61    ///
62    /// # Errors
63    ///
64    /// If the reference cannot be resolved or is invalid.
65    pub fn lookup(&self, reference: &str) -> Result<Resolved<'r>, Error> {
66        let (uri, fragment) = if let Some(reference) = reference.strip_prefix('#') {
67            (self.base_uri.clone(), reference)
68        } else {
69            let (uri, fragment) = if let Some((uri, fragment)) = reference.rsplit_once('#') {
70                (uri, fragment)
71            } else {
72                (reference, "")
73            };
74            let uri = self
75                .registry
76                .resolve_against(&self.base_uri.borrow(), uri)?;
77            (uri, fragment)
78        };
79
80        let Some(retrieved) = self.registry.resources.get(&*uri) else {
81            return Err(Error::unretrievable(
82                uri.as_str(),
83                "Retrieving external resources is not supported once the registry is populated"
84                    .into(),
85            ));
86        };
87
88        if fragment.starts_with('/') {
89            let resolver = self.evolve(uri);
90            return retrieved.pointer(fragment, resolver);
91        }
92
93        if !fragment.is_empty() {
94            let retrieved = self.registry.anchor(&uri, fragment)?;
95            let resolver = self.evolve(uri);
96            return retrieved.resolve(resolver);
97        }
98
99        let resolver = self.evolve(uri);
100        Ok(Resolved::new(
101            retrieved.contents(),
102            resolver,
103            retrieved.draft(),
104        ))
105    }
106    /// Resolve a recursive reference.
107    ///
108    /// This method implements the recursive reference resolution algorithm
109    /// as specified in JSON Schema Draft 2019-09.
110    ///
111    /// It starts by resolving "#" and then follows the dynamic scope,
112    /// looking for resources with `$recursiveAnchor: true`.
113    ///
114    /// # Errors
115    ///
116    /// This method can return any error that [`Resolver::lookup`] can return.
117    pub fn lookup_recursive_ref(&self) -> Result<Resolved<'r>, Error> {
118        let mut resolved = self.lookup("#")?;
119
120        if let Value::Object(obj) = resolved.contents {
121            if obj
122                .get("$recursiveAnchor")
123                .and_then(Value::as_bool)
124                .unwrap_or(false)
125            {
126                for uri in &self.dynamic_scope() {
127                    let next_resolved = self.lookup(uri.as_str())?;
128
129                    match next_resolved.contents {
130                        Value::Object(next_obj) => {
131                            if !next_obj
132                                .get("$recursiveAnchor")
133                                .and_then(Value::as_bool)
134                                .unwrap_or(false)
135                            {
136                                break;
137                            }
138                        }
139                        _ => break,
140                    }
141
142                    resolved = next_resolved;
143                }
144            }
145        }
146
147        Ok(resolved)
148    }
149    /// Create a resolver for a subresource.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the resource id cannot be resolved against the base URI of this resolver.
154    pub fn in_subresource(&self, subresource: ResourceRef<'_>) -> Result<Self, Error> {
155        self.in_subresource_inner(&subresource)
156    }
157
158    pub(crate) fn in_subresource_inner(
159        &self,
160        subresource: &impl JsonSchemaResource,
161    ) -> Result<Self, Error> {
162        if let Some(id) = subresource.id() {
163            let base_uri = self.registry.resolve_against(&self.base_uri.borrow(), id)?;
164            Ok(Resolver {
165                registry: self.registry,
166                base_uri,
167                scopes: self.scopes.clone(),
168            })
169        } else {
170            Ok(self.clone())
171        }
172    }
173    #[must_use]
174    pub fn dynamic_scope(&self) -> List<Uri<String>> {
175        self.scopes.clone()
176    }
177    fn evolve(&self, base_uri: Arc<Uri<String>>) -> Resolver<'r> {
178        if !self.base_uri.as_str().is_empty()
179            && (self.scopes.is_empty() || base_uri != self.base_uri)
180        {
181            Resolver {
182                registry: self.registry,
183                base_uri,
184                scopes: self.scopes.push_front(self.base_uri.clone()),
185            }
186        } else {
187            Resolver {
188                registry: self.registry,
189                base_uri,
190                scopes: self.scopes.clone(),
191            }
192        }
193    }
194    /// Resolve a reference against a base.
195    ///
196    /// # Errors
197    ///
198    /// If the reference is invalid.
199    pub fn resolve_against(&self, base: &Uri<&str>, uri: &str) -> Result<Arc<Uri<String>>, Error> {
200        self.registry.resolve_against(base, uri)
201    }
202}
203
204/// A reference resolved to its contents by a [`Resolver`].
205#[derive(Debug)]
206pub struct Resolved<'r> {
207    /// The contents of the resolved reference.
208    contents: &'r Value,
209    /// The resolver that resolved this reference, which can be used for further resolutions.
210    resolver: Resolver<'r>,
211    draft: Draft,
212}
213
214impl<'r> Resolved<'r> {
215    pub(crate) fn new(contents: &'r Value, resolver: Resolver<'r>, draft: Draft) -> Self {
216        Self {
217            contents,
218            resolver,
219            draft,
220        }
221    }
222    /// Resolved contents.
223    #[must_use]
224    pub fn contents(&self) -> &'r Value {
225        self.contents
226    }
227    /// Resolver used to resolve this contents.
228    #[must_use]
229    pub fn resolver(&self) -> &Resolver<'r> {
230        &self.resolver
231    }
232    #[must_use]
233    pub fn into_inner(self) -> (&'r Value, Resolver<'r>, Draft) {
234        (self.contents, self.resolver, self.draft)
235    }
236}