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
14pub 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 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(®istry, retriever.clone()).await;
45 Ok(Validator {
46 validators,
47 registry,
48 retriever,
49 })
50 }
51
52 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 #[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 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 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 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}