Skip to content

Commit

Permalink
refactor(core): move validate into core
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Sep 23, 2024
1 parent 0b83d82 commit 71c6fbf
Show file tree
Hide file tree
Showing 52 changed files with 231 additions and 514 deletions.
4 changes: 0 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ updates:
directory: "/stac-server"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/stac-validate"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/scripts"
schedule:
Expand Down
40 changes: 0 additions & 40 deletions .github/workflows/validate.yml

This file was deleted.

13 changes: 2 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
[workspace]
resolver = "2"
members = [
"api",
"cli",
"core",
"duckdb",
"pgstac",
"python",
"server",
"validate",
]
default-members = ["api", "cli", "core", "server", "validate"]
members = ["api", "cli", "core", "duckdb", "pgstac", "python", "server"]
default-members = ["api", "cli", "core", "server"]
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ This monorepo contains several crates:
| [stac-api](./api/README.md) | Data structures for the [STAC API](https://github.com/radiantearth/stac-api-spec) specification | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/api.yml?style=flat-square&branch=main)](https://github.com/stac-utils/stac-rs/actions/workflows/api.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/stac-api?style=flat-square)](https://docs.rs/stac-api/latest/stac_api/) <br> [![Crates.io](https://img.shields.io/crates/v/stac-api?style=flat-square)](https://crates.io/crates/stac-api) |
| [stac-cli](./cli/README.md)| Command line interface | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/cli.yml?style=flat-square&branch=main)](https://github.com/stac-utils/stac-rs/actions/workflows/cli.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/stac-cli?style=flat-square)](https://docs.rs/stac-cli/latest/stac_cli/) <br> [![Crates.io](https://img.shields.io/crates/v/stac-cli?style=flat-square)](https://crates.io/crates/stac-cli) |
| [stac-server](./server/README.md)| STAC API server with multiple backends | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/server.yml?style=flat-square&branch=main)](https://github.com/stac-utils/stac-rs/actions/workflows/server.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/stac-server?style=flat-square)](https://docs.rs/stac-server/latest/stac_server/) <br> [![Crates.io](https://img.shields.io/crates/v/stac-server?style=flat-square)](https://crates.io/crates/stac-server) |
| [stac-validate](./validate/README.md) | Validate STAC data structures with [jsonschema](https://json-schema.org/) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/validate.yml?style=flat-square&branch-main)](https://github.com/stac-utils/stac-rs/actions/workflows/validate.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/stac-validate?style=flat-square)](https://docs.rs/stac-validate/latest/stac-validate/) <br> [![Crates.io](https://img.shields.io/crates/v/stac-validate?style=flat-square)](https://crates.io/crates/stac-validate) |
| [pgstac](./pgstac/README.md) | Bindings for [pgstac](https://github.com/stac-utils/pgstac) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/pgstac.yml?style=flat-square&branch=main)](https://github.com/stac-utils/stac-rs/actions/workflows/pgstac.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/pgstac?style=flat-square)](https://docs.rs/pgstac/latest/pgstac/) <br> [![Crates.io](https://img.shields.io/crates/v/pgstac?style=flat-square)](https://crates.io/crates/pgstac) |
| [stac-duckdb](./duckdb/README.md) | Experimental client for [duckdb](https://duckdb.org/) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/duckdb.yml?style=flat-square&branch=main)](https://github.com/stac-utils/stac-rs/actions/workflows/duckdb.yml) <br /> [![docs.rs](https://img.shields.io/docsrs/stac-duckdb?style=flat-square)](https://docs.rs/stac-duckdb/latest/stac_duckdb/) <br> [![Crates.io](https://img.shields.io/crates/v/stac-duckdb?style=flat-square)](https://crates.io/crates/stac-duckdb) |

Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ stac = { version = "0.10.0", path = "../core", features = [
"geoparquet-compression",
"object-store-all",
"reqwest",
"validate",
] }
stac-api = { version = "0.6.0", path = "../api", features = ["client"] }
stac-duckdb = { version = "0.0.2", path = "../duckdb", optional = true }
stac-server = { version = "0.3.1", path = "../server", features = ["axum"] }
stac-validate = { version = "0.3.0", path = "../validate" }
thiserror = "1"
tokio = { version = "1.23", features = [
"macros",
Expand Down
4 changes: 2 additions & 2 deletions cli/src/args/validate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{Input, Run};
use crate::{Error, Result, Value};
use stac_validate::Validate;
use stac::Validate;
use tokio::sync::mpsc::Sender;

/// Arguments for the `validate` subcommand.
Expand All @@ -17,7 +17,7 @@ impl Run for Args {
async fn run(self, input: Input, _: Option<Sender<Value>>) -> Result<Option<Value>> {
let value = input.get().await?;
let result = value.validate().await;
if let Err(stac_validate::Error::Validation(ref errors)) = result {
if let Err(stac::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 Down
4 changes: 0 additions & 4 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ pub enum Error {
#[error(transparent)]
StacServer(#[from] stac_server::Error),

/// [stac_validate::Error]
#[error(transparent)]
StacValidate(#[from] stac_validate::Error),

/// [tokio::sync::mpsc::error::SendError]
#[error(transparent)]
TokioSend(#[from] tokio::sync::mpsc::error::SendError<Value>),
Expand Down
4 changes: 4 additions & 0 deletions core/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]

### Added

- Validation, from the (now defunct) **stac-validate** ([#434](https://github.com/stac-utils/stac-rs/pull/434))

## [0.10.1] - 2024-09-20

### Fixed
Expand Down
7 changes: 6 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ object-store-all = [
"object-store-http",
]
reqwest = ["dep:reqwest"]
validate = ["dep:jsonschema", "dep:reqwest", "dep:tokio", "dep:tracing"]
validate-blocking = ["validate", "tokio/rt"]

[dependencies]
arrow-array = { version = "52", optional = true }
Expand All @@ -57,6 +59,7 @@ geo = { version = "0.28", optional = true }
geo-types = { version = "0.7", optional = true }
geoarrow = { version = "0.3", optional = true }
geojson = "0.24"
jsonschema = { version = "0.20", optional = true }
log = "0.4"
mime = "0.3"
object_store = { version = "0.11", optional = true }
Expand All @@ -65,14 +68,16 @@ reqwest = { version = "0.12", optional = true, features = ["json", "blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
thiserror = "1"
tracing = { version = "0.1", optional = true }
tokio = { version = "1", optional = true }
url = "2"

[dev-dependencies]
assert-json-diff = "2"
bytes = "1"
rstest = "0.22"
tempdir = "0.3"
tokio = "1"
tokio = { version = "1", features = ["macros"] }
tokio-test = "0.4"

[package.metadata.docs.rs]
Expand Down
76 changes: 75 additions & 1 deletion core/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{Value, Version};
#[cfg(feature = "validate")]
use jsonschema::ValidationError;
use serde_json::Value as JsonValue;
use thiserror::Error;

Expand All @@ -11,6 +13,11 @@ pub enum Error {
#[cfg(feature = "geoarrow")]
Arrow(#[from] arrow_schema::ArrowError),

/// Cannot validate a non-object, non-array
#[error("value is not an object or an array, cannot validate")]
#[cfg(feature = "validate")]
CannotValidate(serde_json::Value),

/// [chrono::ParseError]
#[error(transparent)]
ChronoParse(#[from] chrono::ParseError),
Expand Down Expand Up @@ -69,16 +76,23 @@ pub enum Error {

/// Returned when a geometry is missing but is required.
#[error("no geometry field")]
#[deprecated(since = "0.10.2", note = "renamed to NoGeometry")]
MissingGeometry,

/// Returned when there is not a `type` field on a STAC object
#[error("no \"type\" field in the JSON object")]
#[deprecated(since = "0.10.2", note = "renamed to NoType")]
MissingType,

/// Returned when an object is expected to have an href, but it doesn't.
#[error("object has no href")]
#[deprecated(since = "0.10.2", note = "use to NoHref")]
MissingHref,

/// There is no geometry.
#[error("no geometry")]
NoGeometry,

/// There are no items, when items are required.
#[error("no items")]
NoItems,
Expand All @@ -87,6 +101,14 @@ pub enum Error {
#[error("no href")]
NoHref,

/// There is no type.
#[error("no type")]
NoType,

/// No version field on an object.
#[error("no version field")]
NoVersion,

/// This value is not an item.
#[error("value is not an item")]
NotAnItem(Value),
Expand Down Expand Up @@ -119,14 +141,35 @@ pub enum Error {
Parquet(#[from] parquet::errors::ParquetError),

/// [reqwest::Error]
#[cfg(feature = "reqwest")]
#[cfg(any(feature = "reqwest", feature = "validate"))]
#[error(transparent)]
Reqwest(#[from] reqwest::Error),

/// [serde_json::Error]
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),

/// [tokio::task::JoinError]
#[error(transparent)]
#[cfg(feature = "validate")]
TokioJoin(#[from] tokio::task::JoinError),

/// [tokio::sync::mpsc::error::SendError]
#[error(transparent)]
#[cfg(feature = "validate")]
TokioSend(
#[from]
tokio::sync::mpsc::error::SendError<(
url::Url,
tokio::sync::oneshot::Sender<crate::Result<std::sync::Arc<serde_json::Value>>>,
)>,
),

/// [tokio::sync::oneshot::error::RecvError]
#[error(transparent)]
#[cfg(feature = "validate")]
TokioRecv(#[from] tokio::sync::oneshot::error::RecvError),

/// [std::num::TryFromIntError]
#[error(transparent)]
TryFromInt(#[from] std::num::TryFromIntError),
Expand Down Expand Up @@ -154,4 +197,35 @@ pub enum Error {
/// [url::ParseError]
#[error(transparent)]
Url(#[from] url::ParseError),

/// A list of validation errors.
///
/// Since we usually don't have the original [serde_json::Value] (because we
/// create them from the STAC objects), we need these errors to be `'static`
/// lifetime.
#[error("validation errors")]
#[cfg(feature = "validate")]
Validation(Vec<ValidationError<'static>>),
}

#[cfg(feature = "validate")]
impl Error {
pub(crate) fn from_validation_errors<'a, I>(errors: I) -> Error
where
I: Iterator<Item = ValidationError<'a>>,
{
use std::borrow::Cow;

let mut error_vec = Vec::new();
for error in errors {
// Cribbed from https://docs.rs/jsonschema/latest/src/jsonschema/error.rs.html#21-30
error_vec.push(ValidationError {
instance_path: error.instance_path.clone(),
instance: Cow::Owned(error.instance.into_owned()),
kind: error.kind,
schema_path: error.schema_path,
})
}
Error::Validation(error_vec)
}
}
6 changes: 6 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,15 @@ mod migrate;
pub mod mime;
mod ndjson;
mod statistics;
#[cfg(feature = "validate")]
mod validate;
mod value;
mod version;

#[cfg(feature = "validate-blocking")]
pub use validate::ValidateBlocking;
#[cfg(feature = "validate")]
pub use validate::{Validate, Validator};
pub use {
asset::{Asset, Assets},
band::Band,
Expand Down
17 changes: 9 additions & 8 deletions validate/src/blocking.rs → core/src/validate/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ pub trait ValidateBlocking: Validate {
/// # Examples
///
/// ```
/// use stac_validate::ValidateBlocking;
/// use stac::Item;
/// use stac::{ValidateBlocking, Item};
///
/// let mut item = Item::new("an-id");
/// item.validate_blocking().unwrap();
Expand All @@ -30,9 +29,8 @@ impl<T: Serialize> ValidateBlocking for T {}
#[cfg(test)]
mod tests {
use super::ValidateBlocking;
use geojson::{Geometry, Value};
use crate::{Catalog, Collection, Item};
use rstest as _;
use stac::{Catalog, Collection, Item};

#[test]
fn item() {
Expand All @@ -41,7 +39,10 @@ mod tests {
}

#[test]
#[cfg(feature = "geo")]
fn item_with_geometry() {
use geojson::{Geometry, Value};

let mut item = Item::new("an-id");
item.set_geometry(Geometry::new(Value::Point(vec![-105.1, 40.1])))
.unwrap();
Expand All @@ -51,7 +52,7 @@ mod tests {
#[test]
fn item_with_extensions() {
let item: Item =
stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
crate::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
item.validate_blocking().unwrap();
}

Expand All @@ -69,14 +70,14 @@ mod tests {

#[test]
fn value() {
let value: stac::Value = stac::read("examples/simple-item.json").unwrap();
let value: crate::Value = crate::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]);
let item = crate::read("examples/simple-item.json").unwrap();
let item_collection = crate::ItemCollection::from(vec![item]);
item_collection.validate_blocking().unwrap();
}
}
Loading

0 comments on commit 71c6fbf

Please sign in to comment.