Skip to content

Commit b1ae6c6

Browse files
authored
feat: implement async jsonschema validation (#798)
## Closes Add async JSON Schema validation: [#627](https://github.com/stac-utils/rustac/issues/627?utm_source=chatgpt.com) ## Description Adds asynchronous JSON Schema validation via [async retrieve](Stranger6667/jsonschema#703) ## Checklist Delete any checklist items that do not apply (e.g. if your change is minor, it may not require documentation updates). - [x] Unit tests - [x] Documentation, including doctests - [x] Git history is linear - [x] Commit messages are descriptive - [ ] (optional) Git commit messages follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) - [x] Code is formatted (`cargo fmt`) - [x] `cargo test` <!-- markdownlint-disable-file MD041 -->
1 parent 6245cbb commit b1ae6c6

File tree

9 files changed

+118
-78
lines changed

9 files changed

+118
-78
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ arrow-json = "55.0.0"
4040
arrow-schema = "55.0.0"
4141
assert-json-diff = "2.0"
4242
assert_cmd = "2.0"
43+
async-recursion = "1.1.1"
4344
async-stream = "0.3.6"
45+
async-trait = "0.1.89"
4446
axum = "0.8.1"
4547
bb8 = "0.9.0"
4648
bb8-postgres = "0.9.0"
@@ -63,7 +65,7 @@ geojson = "0.24.1"
6365
getrandom = { version = "0.3.3", features = ["wasm_js"] }
6466
http = "1.1"
6567
indexmap = { version = "2.10.0", features = ["serde"] }
66-
jsonschema = { version = "0.33.0", default-features = false }
68+
jsonschema = { version = "0.33.0", default-features = false, features = ["resolve-async"] }
6769
libduckdb-sys = "1.3.0"
6870
log = "0.4.25"
6971
mime = "0.3.17"
@@ -75,6 +77,7 @@ quote = "1.0"
7577
reqwest = { version = "0.12.8", default-features = false, features = [
7678
"rustls-tls",
7779
] }
80+
referencing = "0.33.0"
7881
rstest = "0.26.1"
7982
rustls = { version = "0.23.22", default-features = false }
8083
serde = "1.0"

crates/cli/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::{
2020
io::Write,
2121
str::FromStr,
2222
};
23-
use tokio::{io::AsyncReadExt, net::TcpListener, runtime::Handle, task::JoinSet};
23+
use tokio::{io::AsyncReadExt, net::TcpListener, task::JoinSet};
2424
use tracing::metadata::Level;
2525
use tracing_indicatif::IndicatifLayer;
2626
use tracing_subscriber::{
@@ -492,9 +492,7 @@ impl Rustac {
492492
}
493493
Command::Validate { ref infile } => {
494494
let value = self.get(infile.as_deref()).await?;
495-
let result = Handle::current()
496-
.spawn_blocking(move || value.validate())
497-
.await?;
495+
let result = value.validate().await;
498496
if let Err(error) = result {
499497
if let stac_validate::Error::Validation(errors) = error {
500498
if let Some(format) = self.output_format {

crates/core/tests/examples.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use stac_validate::Validate;
44
use std::path::PathBuf;
55

66
#[rstest]
7-
fn v1_0_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
7+
#[tokio::test]
8+
async fn v1_0_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
89
let value: Value = stac::read(path.to_str().unwrap()).unwrap();
9-
value.validate().unwrap();
10+
value.validate().await.unwrap();
1011
}
1112

1213
#[rstest]
13-
fn v1_1_0(#[files("../../spec-examples/v1.1.0/**/*.json")] path: PathBuf) {
14+
#[tokio::test]
15+
async fn v1_1_0(#[files("../../spec-examples/v1.1.0/**/*.json")] path: PathBuf) {
1416
let value: Value = stac::read(path.to_str().unwrap()).unwrap();
15-
value.validate().unwrap();
17+
value.validate().await.unwrap();
1618
}

crates/core/tests/migrate.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use stac_validate::Validate;
44
use std::path::PathBuf;
55

66
#[rstest]
7-
fn v1_0_0_to_v1_1_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
7+
#[tokio::test]
8+
async fn v1_0_0_to_v1_1_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
89
let value: Value = stac::read(path.to_str().unwrap()).unwrap();
910
let value = value.migrate(&Version::v1_1_0).unwrap();
10-
value.validate().unwrap();
11+
value.validate().await.unwrap();
1112
}

crates/duckdb/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ thiserror.workspace = true
3434
geo.workspace = true
3535
rstest.workspace = true
3636
stac-validate = { path = "../validate" }
37+
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }

crates/duckdb/src/client.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,13 @@ mod tests {
464464
}
465465

466466
#[rstest]
467-
fn search(client: Client) {
467+
#[tokio::test]
468+
async fn search(client: Client) {
468469
let item_collection = client
469470
.search("data/100-sentinel-2-items.parquet", Search::default())
470471
.unwrap();
471472
assert_eq!(item_collection.items.len(), 100);
472-
item_collection.items[0].validate().unwrap();
473+
item_collection.items[0].validate().await.unwrap();
473474
}
474475

475476
#[rstest]

crates/validate/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ serde.workspace = true
1919
serde_json.workspace = true
2020
stac.workspace = true
2121
thiserror.workspace = true
22+
async-trait.workspace = true
23+
referencing.workspace = true
24+
async-recursion.workspace = true
2225

2326
[dev-dependencies]
2427
stac-io.workspace = true
25-
tokio = { workspace = true, features = ["macros"] }
28+
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }

crates/validate/src/lib.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,26 @@
88
//! use stac::Item;
99
//! use stac_validate::Validate;
1010
//!
11-
//! Item::new("an-id").validate().unwrap();
11+
//! #[tokio::main]
12+
//! async fn main() {
13+
//! Item::new("an-id").validate().await.unwrap();
14+
//! }
1215
//! ```
1316
//!
1417
//! All fetched schemas are cached, so if you're you're doing multiple
1518
//! validations, you should re-use the same [Validator]:
1619
//!
1720
//! ```
18-
//! # use stac::Item;
21+
//! use stac::Item;
1922
//! use stac_validate::Validator;
20-
//! let mut items: Vec<_> = (0..10).map(|n| Item::new(format!("item-{}", n))).collect();
21-
//! let mut validator = Validator::new().unwrap();
22-
//! for item in items {
23-
//! validator.validate(&item).unwrap();
23+
//!
24+
//! #[tokio::main]
25+
//! async fn main() {
26+
//! let mut items: Vec<_> = (0..10).map(|n| Item::new(format!("item-{}", n))).collect();
27+
//! let mut validator = Validator::new().await.unwrap();
28+
//! for item in items {
29+
//! validator.validate(&item).await.unwrap();
30+
//! }
2431
//! }
2532
//! ```
2633
//!
@@ -31,13 +38,15 @@ use serde::Serialize;
3138

3239
mod error;
3340
mod validator;
41+
use async_trait::async_trait;
3442

3543
pub use {error::Error, validator::Validator};
3644

3745
/// Public result type.
3846
pub type Result<T> = std::result::Result<T, Error>;
3947

4048
/// Validate any serializable object with [json-schema](https://json-schema.org/)
49+
#[async_trait]
4150
pub trait Validate: Serialize + Sized {
4251
/// Validates this object.
4352
///
@@ -53,16 +62,19 @@ pub trait Validate: Serialize + Sized {
5362
/// use stac::Item;
5463
/// use stac_validate::Validate;
5564
///
56-
/// let mut item = Item::new("an-id");
57-
/// item.validate().unwrap();
65+
/// #[tokio::main]
66+
/// async fn main() {
67+
/// let mut item = Item::new("an-id");
68+
/// item.validate().await.unwrap();
69+
/// }
5870
/// ```
59-
fn validate(&self) -> Result<()> {
60-
let mut validator = Validator::new()?;
61-
validator.validate(self)
71+
async fn validate(&self) -> Result<()> {
72+
let mut validator = Validator::new().await?;
73+
validator.validate(self).await
6274
}
6375
}
6476

65-
impl<T: Serialize> Validate for T {}
77+
impl<T: Serialize + Send + Sync> Validate for T {}
6678

6779
/// Returns a string suitable for use as a HTTP user agent.
6880
pub fn user_agent() -> &'static str {

0 commit comments

Comments
 (0)