Skip to content

Commit

Permalink
refactor(validate): move to async
Browse files Browse the repository at this point in the history
Closes #207
  • Loading branch information
gadomski committed Sep 18, 2024
1 parent b354dd6 commit afab706
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 550 deletions.
13 changes: 3 additions & 10 deletions cli/src/args/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,8 @@ pub(crate) struct Args {
impl Run for Args {
async fn run(self, input: Input, _: Option<Sender<Value>>) -> Result<Option<Value>> {
let value = input.get().await?;
let result = tokio::task::spawn_blocking(move || {
if let Err(err) = value.validate() {
Err((err, value))
} else {
Ok(())
}
})
.await?;
if let Err((stac_validate::Error::Validation(ref errors), ref value)) = result {
let result = value.validate().await;
if let Err(stac_validate::Error::Validation(ref errors)) = result {
let message_base = match value {
stac::Value::Item(item) => format!("[item={}] ", item.id),
stac::Value::Catalog(catalog) => format!("[catalog={}] ", catalog.id),
Expand All @@ -38,7 +31,7 @@ impl Run for Args {
);
}
}
result.and(Ok(None)).map_err(|(err, _)| Error::from(err))
result.and(Ok(None)).map_err(Error::from)
}

fn take_infile(&mut self) -> Option<String> {
Expand Down
1 change: 1 addition & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Conversion traits for the three formats ([#396](https://github.com/stac-utils/stac-rs/pull/396))
- `object_store` ([#382](https://github.com/stac-utils/stac-rs/pull/382))
- `stac::geoparquet::Compression`, even if geoparquet is not enabled ([#396](https://github.com/stac-utils/stac-rs/pull/396))
- `Type` ([#397](https://github.com/stac-utils/stac-rs/pull/397))

### Changed

Expand Down
84 changes: 84 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,90 @@ pub const STAC_VERSION: Version = Version::v1_0_0;
/// Custom [Result](std::result::Result) type for this crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Enum for the four "types" of STAC values.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Type {
/// An item.
Item,

/// A collection.
Collection,

/// A catalog.
Catalog,

/// An item collection.
///
/// While not technically part of the STAC specification, it's used all over the place.
ItemCollection,
}

impl Type {
/// Returns this type as a str.
///
/// # Examples
///
/// ```
/// use stac::Type;
///
/// assert_eq!(Type::Item.as_str(), "Feature");
/// ```
pub fn as_str(&self) -> &'static str {
match self {
Type::Item => "Feature",
Type::Catalog => "Catalog",
Type::Collection => "Collection",
Type::ItemCollection => "FeatureCollection",
}
}

/// Returns the schema path for this type.
///
/// # Examples
///
/// ```
/// use stac::{Type, Version};
///
/// assert_eq!(Type::Item.spec_path(&Version::v1_0_0).unwrap(), "/v1.0.0/item-spec/json-schema/item.json");
/// ```
pub fn spec_path(&self, version: &Version) -> Option<String> {
match self {
Type::Item => Some(format!("/v{}/item-spec/json-schema/item.json", version)),
Type::Catalog => Some(format!(
"/v{}/catalog-spec/json-schema/catalog.json",
version
)),
Type::Collection => Some(format!(
"/v{}/collection-spec/json-schema/collection.json",
version
)),
Type::ItemCollection => None,
}
}
}

impl std::str::FromStr for Type {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"Feature" => Ok(Type::Item),
"Catalog" => Ok(Type::Catalog),
"Collection" => Ok(Type::Collection),
"FeatureCollection" => Ok(Type::ItemCollection),
_ => Err(Error::UnknownType(s.to_string())),
}
}
}

impl<T> PartialEq<T> for Type
where
T: AsRef<str>,
{
fn eq(&self, other: &T) -> bool {
self.as_str() == other.as_ref()
}
}

/// Utility function to deserialize the type field on an object.
///
/// Use this, via a wrapper function, for `#[serde(deserialize_with)]`.
Expand Down
2 changes: 1 addition & 1 deletion python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ serde = "1"
serde_json = "1"
stac = { path = "../core", features = ["reqwest", "object-store-all"] }
stac-api = { path = "../api", features = ["client"] }
stac-validate = { path = "../validate" }
stac-validate = { path = "../validate", features = ["blocking"] }
tokio = { version = "1", features = ["rt"] }
4 changes: 2 additions & 2 deletions python/src/validate.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{Error, Result};
use pyo3::{prelude::*, types::PyDict};
use stac::Value;
use stac_validate::Validate;
use stac_validate::ValidateBlocking;

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

fn validate_value(value: Value) -> Result<()> {
if let Err(error) = value.validate() {
if let Err(error) = value.validate_blocking() {
match error {
stac_validate::Error::Validation(errors) => {
let mut message = "Validation errors: ".to_string();
Expand Down
6 changes: 3 additions & 3 deletions server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ mod tests {
let root = api.root().await.unwrap();
assert!(!root.conformance.conforms_to.is_empty());
let catalog: Catalog = serde_json::from_value(serde_json::to_value(root).unwrap()).unwrap();
catalog.validate().unwrap();
catalog.validate().await.unwrap();
assert_eq!(catalog.id, "an-id");
assert_eq!(catalog.description, "a description");
assert_link!(
Expand Down Expand Up @@ -511,7 +511,7 @@ mod tests {
);
assert_eq!(collections.collections.len(), 1);
let collection = &collections.collections[0];
collection.validate().unwrap();
collection.validate().await.unwrap();
assert_link!(
collection.link("root"),
"http://stac.test/",
Expand Down Expand Up @@ -543,7 +543,7 @@ mod tests {
.unwrap();
let api = test_api(backend);
let collection = api.collection("a-collection").await.unwrap().unwrap();
collection.validate().unwrap();
collection.validate().await.unwrap();
assert_link!(
collection.link("root"),
"http://stac.test/",
Expand Down
4 changes: 4 additions & 0 deletions validate/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

## Changed

- Moved to async-first, with a blocking interface ([#397](https://github.com/stac-utils/stac-rs/pull/397))

## [0.2.2] - 2024-09-06

### Added
Expand Down
16 changes: 15 additions & 1 deletion validate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,31 @@ license = "MIT OR Apache-2.0"
keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
categories = ["science", "data-structures"]

[features]
blocking = ["tokio/rt"]

[dependencies]
jsonschema = "0.19"
log = "0.4"
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde = "1"
serde_json = "1"
stac = { version = "0.9.0", path = "../core" }
thiserror = "1"
tokio = "1"
tracing = "0.1"
url = "2"

[dev-dependencies]
geojson = "0.24"
stac = { version = "0.9.0", path = "../core", features = ["geo"] }
rstest = "0.22"
tokio = { version = "1", features = ["macros"] }
tokio-test = "0.4"

[[test]]
name = "examples"
required-features = ["blocking"]

[[test]]
name = "migrate"
required-features = ["blocking"]
6 changes: 4 additions & 2 deletions validate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
![Crates.io](https://img.shields.io/crates/l/stac-validate?style=for-the-badge)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=for-the-badge)](./CODE_OF_CONDUCT)

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

## Usage

Expand All @@ -22,7 +22,9 @@ stac-validate = "0.2"
```rust
use stac_validate::Validate;
let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
item.validate().unwrap();
tokio_test::block_on(async {
item.validate().await.unwrap();
});
```

Please see the [documentation](https://docs.rs/stac-validate) for more usage examples.
Expand Down
82 changes: 82 additions & 0 deletions validate/src/blocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::{Result, Validate};
use serde::Serialize;
use tokio::runtime::Builder;

/// Validate any serializable object with [json-schema](https://json-schema.org/)
///
/// This is a blocking alternative to [Validate]
pub trait ValidateBlocking: Validate {
/// Validates this object.
///
/// # Examples
///
/// ```
/// use stac_validate::ValidateBlocking;
/// use stac::Item;
///
/// let mut item = Item::new("an-id");
/// item.validate_blocking().unwrap();
/// ```
fn validate_blocking(&self) -> Result<()> {
Builder::new_current_thread()
.enable_io()
.build()?
.block_on(self.validate())
}
}

impl<T: Serialize> ValidateBlocking for T {}

#[cfg(test)]
mod tests {
use super::ValidateBlocking;
use geojson::{Geometry, Value};
use rstest as _;
use stac::{Catalog, Collection, Item};

#[test]
fn item() {
let item = Item::new("an-id");
item.validate_blocking().unwrap();
}

#[test]
fn item_with_geometry() {
let mut item = Item::new("an-id");
item.set_geometry(Geometry::new(Value::Point(vec![-105.1, 40.1])))
.unwrap();
item.validate_blocking().unwrap();
}

#[test]
fn item_with_extensions() {
let item: Item =
stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
item.validate_blocking().unwrap();
}

#[test]
fn catalog() {
let catalog = Catalog::new("an-id", "a description");
catalog.validate_blocking().unwrap();
}

#[test]
fn collection() {
let collection = Collection::new("an-id", "a description");
collection.validate_blocking().unwrap();
}

#[test]
fn value() {
let value: stac::Value = stac::read("examples/simple-item.json").unwrap();
value.validate_blocking().unwrap();
}

#[test]
fn item_collection() {
let item = stac::read("examples/simple-item.json").unwrap();
let item_collection = stac::ItemCollection::from(vec![item]);
item_collection.validate_blocking().unwrap();
}
}
Loading

0 comments on commit afab706

Please sign in to comment.