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 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}