Skip to main content

stac_validate/
validator.rs

1use crate::{Error, Result};
2use async_recursion::async_recursion;
3use async_trait::async_trait;
4use fluent_uri::Uri;
5use jsonschema::{AsyncRetrieve, Registry, Resource, Validator as JsonschemaValidator};
6use reqwest::Client;
7use serde::Serialize;
8use serde_json::{Map, Value};
9use stac::{Type, Version};
10use std::collections::HashMap;
11
12const SCHEMA_BASE: &str = "https://schemas.stacspec.org";
13
14/// A structure for validating STAC.
15pub struct Validator {
16    validators: HashMap<Uri<String>, JsonschemaValidator>,
17    registry: Registry<'static>,
18    retriever: Retriever,
19}
20
21#[derive(Debug, Clone)]
22struct Retriever(Client);
23
24impl Validator {
25    /// Creates a new validator.
26    ///
27    /// # Examples
28    ///
29    /// ```
30    /// use stac_validate::Validator;
31    ///
32    /// #[tokio::main]
33    /// async fn main() {
34    ///     let validator = Validator::new().await.unwrap();
35    /// }
36    /// ```
37    pub async fn new() -> Result<Validator> {
38        let retriever = Retriever(Client::builder().user_agent(crate::user_agent()).build()?);
39        let registry = Registry::new()
40            .extend(prebuild_resources())
41            .expect("prebuild resource URIs should be valid")
42            .prepare()
43            .expect("prebuild registry should build");
44        let validators = prebuild_validators(&registry, retriever.clone()).await;
45        Ok(Validator {
46            validators,
47            registry,
48            retriever,
49        })
50    }
51
52    /// Validates a single value.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use stac::Item;
58    /// use stac_validate::Validate;
59    ///
60    /// #[tokio::main]
61    /// async fn main() {
62    ///     let mut item = Item::new("an-id");
63    ///     item.validate().await.unwrap();
64    /// }
65    /// ```
66    pub async fn validate<T>(&mut self, value: &T) -> Result<()>
67    where
68        T: Serialize,
69    {
70        let value = serde_json::to_value(value)?;
71        let _ = self.validate_value(value).await?;
72        Ok(())
73    }
74
75    /// If you have a [serde_json::Value], you can skip a deserialization step by using this method.
76    #[async_recursion]
77    pub async fn validate_value(&mut self, value: Value) -> Result<Value> {
78        if let Value::Object(object) = value {
79            self.validate_object(object).await.map(Value::Object)
80        } else if let Value::Array(array) = value {
81            self.validate_array(array).await.map(Value::Array)
82        } else {
83            Err(Error::ScalarJson(value))
84        }
85    }
86
87    #[async_recursion]
88    async fn validate_array(&mut self, array: Vec<Value>) -> Result<Vec<Value>> {
89        let mut errors = Vec::new();
90        let mut new_array = Vec::with_capacity(array.len());
91        for value in array {
92            match self.validate_value(value).await {
93                Ok(value) => new_array.push(value),
94                Err(error) => {
95                    if let Error::Validation(e) = error {
96                        errors.extend(e);
97                    } else {
98                        return Err(error);
99                    }
100                }
101            }
102        }
103        if errors.is_empty() {
104            Ok(new_array)
105        } else {
106            Err(Error::Validation(errors))
107        }
108    }
109
110    #[async_recursion]
111    async fn validate_object(
112        &mut self,
113        mut object: Map<String, Value>,
114    ) -> Result<Map<String, Value>> {
115        let r#type = if let Some(r#type) = object.get("type").and_then(|v| v.as_str()) {
116            let r#type: Type = r#type.parse()?;
117            if r#type == Type::ItemCollection {
118                if let Some(features) = object.remove("features") {
119                    let features = self.validate_value(features).await?;
120                    let _ = object.insert("features".to_string(), features);
121                }
122                return Ok(object);
123            }
124            r#type
125        } else {
126            match object.remove("collections") {
127                Some(collections) => {
128                    let collections = self.validate_value(collections).await?;
129                    let _ = object.insert("collections".to_string(), collections);
130                    return Ok(object);
131                }
132                _ => {
133                    return Err(stac::Error::MissingField("type").into());
134                }
135            }
136        };
137
138        let version: Version = object
139            .get("stac_version")
140            .and_then(|v| v.as_str())
141            .map(|v| v.parse::<Version>())
142            .transpose()
143            .unwrap()
144            .ok_or(stac::Error::MissingField("stac_version"))?;
145
146        let uri = build_uri(r#type, &version);
147        let validator = self.validator(uri).await?;
148        let value = Value::Object(object);
149        let errors: Vec<_> = validator.iter_errors(&value).collect();
150        let object = if errors.is_empty() {
151            if let Value::Object(object) = value {
152                object
153            } else {
154                unreachable!()
155            }
156        } else {
157            return Err(Error::from_validation_errors(
158                errors.into_iter(),
159                Some(&value),
160            ));
161        };
162
163        self.validate_extensions(object).await
164    }
165
166    async fn validate_extensions(
167        &mut self,
168        object: Map<String, Value>,
169    ) -> Result<Map<String, Value>> {
170        match object
171            .get("stac_extensions")
172            .and_then(|value| value.as_array())
173            .cloned()
174        {
175            Some(stac_extensions) => {
176                let uris = stac_extensions
177                    .into_iter()
178                    .filter_map(|value| {
179                        if let Value::String(s) = value {
180                            Some(Uri::parse(s).map_err(|(err, _)| err))
181                        } else {
182                            None
183                        }
184                    })
185                    .collect::<std::result::Result<Vec<_>, _>>()?;
186                self.ensure_validators(&uris).await?;
187
188                let mut errors = Vec::new();
189                let value = Value::Object(object);
190                for uri in uris {
191                    let validator = self
192                        .validator_opt(&uri)
193                        .expect("We already ensured they're present");
194                    errors.extend(validator.iter_errors(&value));
195                }
196                if errors.is_empty() {
197                    if let Value::Object(object) = value {
198                        Ok(object)
199                    } else {
200                        unreachable!()
201                    }
202                } else {
203                    Err(Error::from_validation_errors(
204                        errors.into_iter(),
205                        Some(&value),
206                    ))
207                }
208            }
209            _ => Ok(object),
210        }
211    }
212
213    async fn validator(&mut self, uri: Uri<String>) -> Result<&JsonschemaValidator> {
214        self.ensure_validator(&uri).await?;
215        Ok(self.validator_opt(&uri).unwrap())
216    }
217
218    async fn ensure_validators(&mut self, uris: &[Uri<String>]) -> Result<()> {
219        for uri in uris {
220            self.ensure_validator(uri).await?;
221        }
222        Ok(())
223    }
224
225    async fn ensure_validator(&mut self, uri: &Uri<String>) -> Result<()> {
226        if !self.validators.contains_key(uri) {
227            let client = reqwest::Client::new();
228            let response = client.get(uri.as_str()).send().await?.error_for_status()?;
229            let json_data = response.json().await?;
230            let validator = jsonschema::async_options()
231                .with_registry(&self.registry)
232                .with_retriever(self.retriever.clone())
233                .build(&json_data)
234                .await
235                .map_err(Box::new)?;
236            let _ = self.validators.insert(uri.clone(), validator);
237        }
238        Ok(())
239    }
240
241    fn validator_opt(&self, uri: &Uri<String>) -> Option<&JsonschemaValidator> {
242        self.validators.get(uri)
243    }
244}
245
246#[async_trait]
247impl AsyncRetrieve for Retriever {
248    async fn retrieve(
249        &self,
250        uri: &Uri<String>,
251    ) -> std::result::Result<Value, Box<dyn std::error::Error + Send + Sync>> {
252        let response = self.0.get(uri.as_str()).send().await?.error_for_status()?;
253        let value = response.json().await?;
254        Ok(value)
255    }
256}
257
258fn build_uri(r#type: Type, version: &Version) -> Uri<String> {
259    Uri::parse(format!(
260        "{}{}",
261        SCHEMA_BASE,
262        r#type
263            .spec_path(version)
264            .expect("we shouldn't get here with an item collection")
265    ))
266    .unwrap()
267}
268
269async fn prebuild_validators(
270    registry: &Registry<'static>,
271    retriever: Retriever,
272) -> HashMap<Uri<String>, JsonschemaValidator> {
273    use Type::*;
274    use Version::*;
275
276    let mut schemas = HashMap::new();
277
278    macro_rules! schema {
279        ($t:expr_2021, $v:expr_2021, $path:expr_2021, $schemas:expr_2021) => {
280            let url = build_uri($t, &$v);
281            let value = serde_json::from_str(include_str!($path)).unwrap();
282            let validator = jsonschema::async_options()
283                .with_registry(registry)
284                .with_retriever(retriever.clone())
285                .build(&value)
286                .await
287                .unwrap();
288            let _ = schemas.insert(url, validator);
289        };
290    }
291
292    schema!(Item, v1_0_0, "schemas/v1.0.0/item.json", schemas);
293    schema!(Catalog, v1_0_0, "schemas/v1.0.0/catalog.json", schemas);
294    schema!(
295        Collection,
296        v1_0_0,
297        "schemas/v1.0.0/collection.json",
298        schemas
299    );
300    schema!(Item, v1_1_0, "schemas/v1.1.0/item.json", schemas);
301    schema!(Catalog, v1_1_0, "schemas/v1.1.0/catalog.json", schemas);
302    schema!(
303        Collection,
304        v1_1_0,
305        "schemas/v1.1.0/collection.json",
306        schemas
307    );
308
309    schemas
310}
311
312fn prebuild_resources() -> Vec<(String, Resource)> {
313    let mut resources = Vec::new();
314
315    macro_rules! resolve {
316        ($url:expr_2021, $path:expr_2021) => {
317            let _ = resources.push((
318                $url.to_string(),
319                Resource::from_contents(serde_json::from_str(include_str!($path)).unwrap()),
320            ));
321        };
322    }
323
324    // General
325    resolve!(
326        "https://geojson.org/schema/Feature.json",
327        "schemas/geojson/Feature.json"
328    );
329    resolve!(
330        "https://geojson.org/schema/Geometry.json",
331        "schemas/geojson/Geometry.json"
332    );
333    resolve!(
334        "http://json-schema.org/draft-07/schema",
335        "schemas/json-schema/draft-07.json"
336    );
337
338    // STAC v1.0.0
339    resolve!(
340        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/basics.json",
341        "schemas/v1.0.0/basics.json"
342    );
343    resolve!(
344        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json",
345        "schemas/v1.0.0/datetime.json"
346    );
347    resolve!(
348        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json",
349        "schemas/v1.0.0/instrument.json"
350    );
351    resolve!(
352        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json",
353        "schemas/v1.0.0/item.json"
354    );
355    resolve!(
356        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/licensing.json",
357        "schemas/v1.0.0/licensing.json"
358    );
359    resolve!(
360        "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/provider.json",
361        "schemas/v1.0.0/provider.json"
362    );
363
364    // STAC v1.1.0
365    resolve!(
366        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/bands.json",
367        "schemas/v1.1.0/bands.json"
368    );
369    resolve!(
370        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/basics.json",
371        "schemas/v1.1.0/basics.json"
372    );
373    resolve!(
374        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/common.json",
375        "schemas/v1.1.0/common.json"
376    );
377    resolve!(
378        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/data-values.json",
379        "schemas/v1.1.0/data-values.json"
380    );
381    resolve!(
382        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json",
383        "schemas/v1.1.0/datetime.json"
384    );
385    resolve!(
386        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/instrument.json",
387        "schemas/v1.1.0/instrument.json"
388    );
389    resolve!(
390        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/item.json",
391        "schemas/v1.1.0/item.json"
392    );
393    resolve!(
394        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/licensing.json",
395        "schemas/v1.1.0/licensing.json"
396    );
397    resolve!(
398        "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/provider.json",
399        "schemas/v1.1.0/provider.json"
400    );
401
402    resources
403}
404
405#[cfg(test)]
406mod tests {
407    use super::Validator;
408    use crate::Validate;
409    use serde_json::json;
410    use stac::{Collection, Item};
411
412    #[tokio::test]
413    async fn validate_simple_item() {
414        let item: Item = stac_io::read("examples/simple-item.json").unwrap();
415        item.validate().await.unwrap();
416    }
417
418    #[tokio::test]
419    async fn validate_inside_tokio_runtime() {
420        let item: Item = stac_io::read("examples/extended-item.json").unwrap();
421        item.validate().await.unwrap();
422    }
423
424    #[tokio::test]
425    async fn validate_array() {
426        let items: Vec<_> = (0..100)
427            .map(|i| Item::new(format!("item-{i}")))
428            .map(|i| serde_json::to_value(i).unwrap())
429            .collect();
430        let mut validator = Validator::new().await.unwrap();
431        validator.validate(&items).await.unwrap();
432    }
433
434    #[tokio::test]
435    async fn validate_collections() {
436        let collection: Collection = stac_io::read("examples/collection.json").unwrap();
437        let collections = json!({
438            "collections": [collection]
439        });
440        collections.validate().await.unwrap();
441    }
442}