Skip to main content

yaml_schema/schemas/
root_schema.rs

1//! RootSchema represents the root document in a schema document.
2
3use jsonptr::Pointer;
4use log::debug;
5use saphyr::MarkedYaml;
6use saphyr::Scalar;
7use saphyr::YamlData;
8use url::Url;
9
10use crate::Error;
11use crate::Result;
12use crate::YamlSchema;
13use crate::loader::marked_yaml_to_string;
14use crate::validation::Context;
15use crate::validation::Validator;
16
17/// A RootSchema represents the root document in a schema document, and includes additional
18/// fields such as `$schema` that are not allowed in subschemas. It also provides a way to
19/// resolve references to other schemas.
20#[derive(Debug, PartialEq)]
21pub struct RootSchema {
22    pub meta_schema: Option<String>,
23    pub schema: YamlSchema,
24    /// Base URI for resolving relative `$ref` values (from file path, URL, or `$id`).
25    pub base_uri: Option<Url>,
26}
27
28impl RootSchema {
29    /// Create an empty RootSchema
30    pub fn empty() -> Self {
31        Self {
32            meta_schema: None,
33            schema: YamlSchema::Empty,
34            base_uri: None,
35        }
36    }
37
38    /// Create a new RootSchema with a given schema
39    pub fn new(schema: YamlSchema) -> Self {
40        Self {
41            meta_schema: None,
42            schema,
43            base_uri: None,
44        }
45    }
46
47    /// Returns the `$id` of the schema's Subschema, if present.
48    pub fn id(&self) -> Option<String> {
49        match &self.schema {
50            YamlSchema::Subschema(subschema) => subschema.metadata_and_annotations.id.clone(),
51            _ => None,
52        }
53    }
54
55    /// Returns the preferred key for caching this schema: `$id` if it is a valid URI,
56    /// otherwise the given fallback (e.g. the file or fetch URI).
57    pub fn cache_key(&self, fallback: &str) -> String {
58        self.id()
59            .filter(|s| Url::parse(s).is_ok())
60            .unwrap_or_else(|| fallback.to_string())
61    }
62
63    /// Resolve a JSON Pointer to an element in the schema.
64    pub fn resolve(&self, pointer: &Pointer) -> Option<&YamlSchema> {
65        let components = pointer.components().collect::<Vec<_>>();
66        debug!("[RootSchema#resolve] components: {components:?}");
67        components.first().and_then(|component| {
68            debug!("[RootSchema#resolve] component: {component:?}");
69            match component {
70                jsonptr::Component::Root => {
71                    let components = &components[1..];
72                    components.first().and_then(|component| {
73                        debug!("[RootSchema#resolve] component: {component:?}");
74                        match component {
75                            jsonptr::Component::Root => unimplemented!(),
76                            jsonptr::Component::Token(token) => {
77                                self.schema.resolve(Some(token), &components[1..])
78                            }
79                        }
80                    })
81                }
82                jsonptr::Component::Token(token) => {
83                    self.schema.resolve(Some(token), &components[1..])
84                }
85            }
86        })
87    }
88}
89
90impl<'r> TryFrom<&MarkedYaml<'r>> for RootSchema {
91    type Error = Error;
92
93    fn try_from(marked_yaml: &MarkedYaml<'r>) -> Result<Self> {
94        match &marked_yaml.data {
95            YamlData::Value(scalar) => match scalar {
96                Scalar::Boolean(r#bool) => Ok(Self {
97                    meta_schema: None,
98                    schema: YamlSchema::BooleanLiteral(*r#bool),
99                    base_uri: None,
100                }),
101                Scalar::Null => Ok(RootSchema {
102                    meta_schema: None,
103                    schema: YamlSchema::Null,
104                    base_uri: None,
105                }),
106                _ => Err(generic_error!(
107                    "[loader#load_from_doc] Don't know how to a handle scalar: {:?}",
108                    scalar
109                )),
110            },
111            YamlData::Mapping(mapping) => {
112                debug!(
113                    "[loader#load_from_doc] Found mapping, trying to load as RootSchema: {mapping:?}"
114                );
115                let meta_schema = mapping
116                    .get(&MarkedYaml::value_from_str("$schema"))
117                    .map(|my| marked_yaml_to_string(my, "$schema must be a string"))
118                    .transpose()?;
119
120                let schema = YamlSchema::try_from(marked_yaml)?;
121                Ok(RootSchema {
122                    meta_schema,
123                    schema,
124                    base_uri: None,
125                })
126            }
127            _ => Err(generic_error!(
128                "[loader#load_from_doc] Don't know how to load: {:?}",
129                marked_yaml
130            )),
131        }
132    }
133}
134
135impl Validator for RootSchema {
136    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
137        self.schema.validate(context, value)
138    }
139}