Skip to content

Commit 74643f3

Browse files
committed
refactor(validate): move to async
Closes #207
1 parent b354dd6 commit 74643f3

File tree

16 files changed

+678
-550
lines changed

16 files changed

+678
-550
lines changed

cli/src/args/validate.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,8 @@ pub(crate) struct Args {
1616
impl Run for Args {
1717
async fn run(self, input: Input, _: Option<Sender<Value>>) -> Result<Option<Value>> {
1818
let value = input.get().await?;
19-
let result = tokio::task::spawn_blocking(move || {
20-
if let Err(err) = value.validate() {
21-
Err((err, value))
22-
} else {
23-
Ok(())
24-
}
25-
})
26-
.await?;
27-
if let Err((stac_validate::Error::Validation(ref errors), ref value)) = result {
19+
let result = value.validate().await;
20+
if let Err(stac_validate::Error::Validation(ref errors)) = result {
2821
let message_base = match value {
2922
stac::Value::Item(item) => format!("[item={}] ", item.id),
3023
stac::Value::Catalog(catalog) => format!("[catalog={}] ", catalog.id),
@@ -38,7 +31,7 @@ impl Run for Args {
3831
);
3932
}
4033
}
41-
result.and(Ok(None)).map_err(|(err, _)| Error::from(err))
34+
result.and(Ok(None)).map_err(Error::from)
4235
}
4336

4437
fn take_infile(&mut self) -> Option<String> {

core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1515
- Conversion traits for the three formats ([#396](https://github.com/stac-utils/stac-rs/pull/396))
1616
- `object_store` ([#382](https://github.com/stac-utils/stac-rs/pull/382))
1717
- `stac::geoparquet::Compression`, even if geoparquet is not enabled ([#396](https://github.com/stac-utils/stac-rs/pull/396))
18+
- `Type` ([#397](https://github.com/stac-utils/stac-rs/pull/397))
1819

1920
### Changed
2021

core/src/lib.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,90 @@ pub const STAC_VERSION: Version = Version::v1_0_0;
209209
/// Custom [Result](std::result::Result) type for this crate.
210210
pub type Result<T> = std::result::Result<T, Error>;
211211

212+
/// Enum for the four "types" of STAC values.
213+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
214+
pub enum Type {
215+
/// An item.
216+
Item,
217+
218+
/// A collection.
219+
Collection,
220+
221+
/// A catalog.
222+
Catalog,
223+
224+
/// An item collection.
225+
///
226+
/// While not technically part of the STAC specification, it's used all over the place.
227+
ItemCollection,
228+
}
229+
230+
impl Type {
231+
/// Returns this type as a str.
232+
///
233+
/// # Examples
234+
///
235+
/// ```
236+
/// use stac::Type;
237+
///
238+
/// assert_eq!(Type::Item.as_str(), "Feature");
239+
/// ```
240+
pub fn as_str(&self) -> &'static str {
241+
match self {
242+
Type::Item => "Feature",
243+
Type::Catalog => "Catalog",
244+
Type::Collection => "Collection",
245+
Type::ItemCollection => "FeatureCollection",
246+
}
247+
}
248+
249+
/// Returns the schema path for this type.
250+
///
251+
/// # Examples
252+
///
253+
/// ```
254+
/// use stac::{Type, Version};
255+
///
256+
/// assert_eq!(Type::Item.spec_path(&Version::v1_0_0).unwrap(), "/v1.0.0/item-spec/json-schema/item.json");
257+
/// ```
258+
pub fn spec_path(&self, version: &Version) -> Option<String> {
259+
match self {
260+
Type::Item => Some(format!("/v{}/item-spec/json-schema/item.json", version)),
261+
Type::Catalog => Some(format!(
262+
"/v{}/catalog-spec/json-schema/catalog.json",
263+
version
264+
)),
265+
Type::Collection => Some(format!(
266+
"/v{}/collection-spec/json-schema/collection.json",
267+
version
268+
)),
269+
Type::ItemCollection => None,
270+
}
271+
}
272+
}
273+
274+
impl std::str::FromStr for Type {
275+
type Err = Error;
276+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
277+
match s {
278+
"Feature" => Ok(Type::Item),
279+
"Catalog" => Ok(Type::Catalog),
280+
"Collection" => Ok(Type::Collection),
281+
"FeatureCollection" => Ok(Type::ItemCollection),
282+
_ => Err(Error::UnknownType(s.to_string())),
283+
}
284+
}
285+
}
286+
287+
impl<T> PartialEq<T> for Type
288+
where
289+
T: AsRef<str>,
290+
{
291+
fn eq(&self, other: &T) -> bool {
292+
self.as_str() == other.as_ref()
293+
}
294+
}
295+
212296
/// Utility function to deserialize the type field on an object.
213297
///
214298
/// Use this, via a wrapper function, for `#[serde(deserialize_with)]`.

python/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ serde = "1"
2323
serde_json = "1"
2424
stac = { path = "../core", features = ["reqwest", "object-store-all"] }
2525
stac-api = { path = "../api", features = ["client"] }
26-
stac-validate = { path = "../validate" }
26+
stac-validate = { path = "../validate", features = ["blocking"] }
2727
tokio = { version = "1", features = ["rt"] }

python/src/validate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{Error, Result};
22
use pyo3::{prelude::*, types::PyDict};
33
use stac::Value;
4-
use stac_validate::Validate;
4+
use stac_validate::ValidateBlocking;
55

66
/// Validates a single href with json-schema.
77
///
@@ -42,7 +42,7 @@ pub fn validate(value: &Bound<'_, PyDict>) -> PyResult<()> {
4242
}
4343

4444
fn validate_value(value: Value) -> Result<()> {
45-
if let Err(error) = value.validate() {
45+
if let Err(error) = value.validate_blocking() {
4646
match error {
4747
stac_validate::Error::Validation(errors) => {
4848
let mut message = "Validation errors: ".to_string();

server/src/api.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ mod tests {
418418
let root = api.root().await.unwrap();
419419
assert!(!root.conformance.conforms_to.is_empty());
420420
let catalog: Catalog = serde_json::from_value(serde_json::to_value(root).unwrap()).unwrap();
421-
catalog.validate().unwrap();
421+
catalog.validate().await.unwrap();
422422
assert_eq!(catalog.id, "an-id");
423423
assert_eq!(catalog.description, "a description");
424424
assert_link!(
@@ -511,7 +511,7 @@ mod tests {
511511
);
512512
assert_eq!(collections.collections.len(), 1);
513513
let collection = &collections.collections[0];
514-
collection.validate().unwrap();
514+
collection.validate().await.unwrap();
515515
assert_link!(
516516
collection.link("root"),
517517
"http://stac.test/",
@@ -543,7 +543,7 @@ mod tests {
543543
.unwrap();
544544
let api = test_api(backend);
545545
let collection = api.collection("a-collection").await.unwrap().unwrap();
546-
collection.validate().unwrap();
546+
collection.validate().await.unwrap();
547547
assert_link!(
548548
collection.link("root"),
549549
"http://stac.test/",

validate/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
## Changed
10+
11+
- Moved to async-first, with a blocking interface ([#397](https://github.com/stac-utils/stac-rs/pull/397))
12+
913
## [0.2.2] - 2024-09-06
1014

1115
### Added

validate/Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,31 @@ license = "MIT OR Apache-2.0"
1010
keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
1111
categories = ["science", "data-structures"]
1212

13+
[features]
14+
blocking = ["tokio/rt"]
15+
1316
[dependencies]
1417
jsonschema = "0.19"
15-
log = "0.4"
1618
reqwest = { version = "0.12", features = ["blocking", "json"] }
1719
serde = "1"
1820
serde_json = "1"
1921
stac = { version = "0.9.0", path = "../core" }
2022
thiserror = "1"
23+
tokio = "1"
24+
tracing = "0.1"
2125
url = "2"
2226

2327
[dev-dependencies]
2428
geojson = "0.24"
2529
stac = { version = "0.9.0", path = "../core", features = ["geo"] }
2630
rstest = "0.22"
31+
tokio = { version = "1", features = ["macros"] }
32+
tokio-test = "0.4"
33+
34+
[[test]]
35+
name = "examples"
36+
required-features = ["blocking"]
37+
38+
[[test]]
39+
name = "migrate"
40+
required-features = ["blocking"]

validate/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
![Crates.io](https://img.shields.io/crates/l/stac-validate?style=for-the-badge)
77
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=for-the-badge)](./CODE_OF_CONDUCT)
88

9-
Validate [STAC](https://stacspec.org/) with [jsonschema](https://json-schema.org/).
9+
Validate [STAC](https://stacspec.org/) with [json-schema](https://json-schema.org/).
1010

1111
## Usage
1212

@@ -22,7 +22,9 @@ stac-validate = "0.2"
2222
```rust
2323
use stac_validate::Validate;
2424
let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
25-
item.validate().unwrap();
25+
tokio_test::block_on(async {
26+
item.validate().await.unwrap();
27+
});
2628
```
2729

2830
Please see the [documentation](https://docs.rs/stac-validate) for more usage examples.

validate/src/blocking.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::{Result, Validate};
2+
use serde::Serialize;
3+
use tokio::runtime::Builder;
4+
5+
/// Validate any serializable object with [json-schema](https://json-schema.org/)
6+
///
7+
/// This is a blocking alternative to [Validate]
8+
pub trait ValidateBlocking: Validate {
9+
/// Validates this object.
10+
///
11+
/// # Examples
12+
///
13+
/// ```
14+
/// use stac_validate::ValidateBlocking;
15+
/// use stac::Item;
16+
///
17+
/// let mut item = Item::new("an-id");
18+
/// item.validate_blocking().unwrap();
19+
/// ```
20+
fn validate_blocking(&self) -> Result<()> {
21+
Builder::new_current_thread()
22+
.enable_io()
23+
.build()?
24+
.block_on(self.validate())
25+
}
26+
}
27+
28+
impl<T: Serialize> ValidateBlocking for T {}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use super::ValidateBlocking;
33+
use geojson::{Geometry, Value};
34+
use rstest as _;
35+
use stac::{Catalog, Collection, Item};
36+
37+
#[test]
38+
fn item() {
39+
let item = Item::new("an-id");
40+
item.validate_blocking().unwrap();
41+
}
42+
43+
#[test]
44+
fn item_with_geometry() {
45+
let mut item = Item::new("an-id");
46+
item.set_geometry(Geometry::new(Value::Point(vec![-105.1, 40.1])))
47+
.unwrap();
48+
item.validate_blocking().unwrap();
49+
}
50+
51+
#[test]
52+
fn item_with_extensions() {
53+
let item: Item =
54+
stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
55+
item.validate_blocking().unwrap();
56+
}
57+
58+
#[test]
59+
fn catalog() {
60+
let catalog = Catalog::new("an-id", "a description");
61+
catalog.validate_blocking().unwrap();
62+
}
63+
64+
#[test]
65+
fn collection() {
66+
let collection = Collection::new("an-id", "a description");
67+
collection.validate_blocking().unwrap();
68+
}
69+
70+
#[test]
71+
fn value() {
72+
let value: stac::Value = stac::read("examples/simple-item.json").unwrap();
73+
value.validate_blocking().unwrap();
74+
}
75+
76+
#[test]
77+
fn item_collection() {
78+
let item = stac::read("examples/simple-item.json").unwrap();
79+
let item_collection = stac::ItemCollection::from(vec![item]);
80+
item_collection.validate_blocking().unwrap();
81+
}
82+
}

0 commit comments

Comments
 (0)