jsona_util/schema/
mod.rs

1pub mod associations;
2pub mod fetcher;
3
4use anyhow::anyhow;
5use jsona::dom::{Keys, Node};
6use parking_lot::Mutex;
7use std::{str::FromStr, sync::Arc};
8use url::Url;
9
10use self::associations::SchemaAssociations;
11use self::fetcher::Fetcher;
12use crate::{environment::Environment, HashMap};
13
14pub use jsona_schema_validator::{JSONASchemaValidationError, JSONASchemaValidator, Schema};
15
16#[derive(Clone)]
17pub struct Schemas<E: Environment> {
18    associations: SchemaAssociations<E>,
19    fetcher: Fetcher<E>,
20    validators: Arc<Mutex<HashMap<Url, Arc<JSONASchemaValidator>>>>,
21}
22
23impl<E: Environment> Schemas<E> {
24    pub fn new(env: E) -> Self {
25        let fetcher = Fetcher::new(env.clone());
26        Self {
27            associations: SchemaAssociations::new(env, fetcher.clone()),
28            fetcher,
29            validators: Arc::new(Mutex::new(HashMap::default())),
30        }
31    }
32
33    /// Get a reference to the schemas's associations.
34    pub fn associations(&self) -> &SchemaAssociations<E> {
35        &self.associations
36    }
37
38    pub fn set_cache_path(&self, cache_path: Option<Url>) {
39        let cache_path = match cache_path {
40            Some(mut path) => {
41                path.set_fragment(None);
42                path.set_query(None);
43                if !path.path().ends_with('/') {
44                    path.set_path(&format!("{}/", path.path()));
45                }
46                Some(path)
47            }
48            None => None,
49        };
50        tracing::info!(
51            "set cache path {:?}",
52            cache_path.as_ref().map(|v| v.as_str())
53        );
54        self.fetcher.set_cache_path(cache_path);
55    }
56}
57
58impl<E: Environment> Schemas<E> {
59    #[tracing::instrument(skip_all, fields(%schema_uri))]
60    pub async fn validate(
61        &self,
62        schema_uri: &Url,
63        value: &Node,
64    ) -> Result<Vec<JSONASchemaValidationError>, anyhow::Error> {
65        let validator = self.load_validator(schema_uri).await?;
66        Ok(validator.validate(value))
67    }
68
69    pub async fn load_validator(
70        &self,
71        schema_uri: &Url,
72    ) -> Result<Arc<JSONASchemaValidator>, anyhow::Error> {
73        if let Some(s) = self.validators.lock().get(schema_uri).cloned() {
74            return Ok(s);
75        }
76
77        let schema: Arc<JSONASchemaValidator> =
78            match self.fetcher.fetch(schema_uri).await.and_then(|v| {
79                std::str::from_utf8(&v)
80                    .map_err(|v| anyhow!("{}", v))
81                    .and_then(|v| Node::from_str(v).map_err(|_| anyhow!("invalid jsona doc")))
82                    .and_then(|v| {
83                        JSONASchemaValidator::try_from(&v)
84                            .map_err(|_| anyhow!("invalid jsona schema"))
85                    })
86            }) {
87                Ok(s) => Arc::new(s),
88                Err(error) => {
89                    tracing::warn!(?error, "failed to use remote jsonaschema");
90                    return Err(error);
91                }
92            };
93
94        self.validators
95            .lock()
96            .insert(schema_uri.clone(), schema.clone());
97
98        Ok(schema)
99    }
100
101    #[tracing::instrument(skip_all, fields(%schema_uri))]
102    pub async fn query(&self, schema_uri: &Url, path: &Keys) -> Result<Vec<Schema>, anyhow::Error> {
103        let validator = self.load_validator(schema_uri).await?;
104        let schemas = validator.pointer(path).into_iter().cloned().collect();
105        Ok(schemas)
106    }
107}