From d1c2d5791fc9cccd37219b3d19a34c60bc1e838b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 17 Oct 2023 16:56:19 -0600 Subject: [PATCH 1/9] feat: add Item.intersects --- stac/CHANGELOG.md | 8 ++++++++ stac/src/item.rs | 26 ++++++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index f0864107..095bbce1 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) + +### Removed + +- `Item.intersects_bbox` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) + ## [0.5.1] - 2023-09-14 ### Added diff --git a/stac/src/item.rs b/stac/src/item.rs index d86a60d1..a3ff5f21 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -209,29 +209,31 @@ impl Item { Ok(()) } - /// Returns true if this item's geometry intersects the provided bounding box. - /// - /// TODO support three dimensional bounding boxes. + /// Returns true if this item's geometry intersects the provided geojson geometry. /// /// # Examples /// /// ``` /// use stac::Item; /// use geojson::{Geometry, Value}; + /// use geo::{Rect, coord}; /// /// let mut item = Item::new("an-id"); /// item.set_geometry(Some(Geometry::new(Value::Point(vec![-105.1, 41.1])))); - /// let bbox = stac::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap(); - /// assert!(item.intersects_bbox(bbox).unwrap()); + /// let intersects = Rect::new( + /// coord! { x: -106.0, y: 40.0 }, + /// coord! { x: -105.0, y: 42.0 }, + /// ); + /// assert!(item.intersects(&intersects).unwrap()); /// ``` #[cfg(feature = "geo")] - pub fn intersects_bbox(&self, bbox: geo::Rect) -> Result { - // TODO support three dimensional - use geo::Intersects; - + pub fn intersects(&self, intersects: &T) -> Result + where + T: geo::Intersects, + { if let Some(geometry) = self.geometry.clone() { let geometry: geo::Geometry = geometry.try_into()?; - Ok(geometry.intersects(&bbox)) + Ok(intersects.intersects(&geometry)) } else { Ok(false) } @@ -441,7 +443,7 @@ mod tests { #[test] #[cfg(feature = "geo")] - fn insersects_bbox() { + fn insersects() { use geojson::Geometry; let mut item = Item::new("an-id"); item.set_geometry(Some(Geometry::new(geojson::Value::Point(vec![ @@ -449,7 +451,7 @@ mod tests { ])))) .unwrap(); assert!(item - .intersects_bbox(crate::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap()) + .intersects(&crate::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap()) .unwrap()); } From bfa9c2c78e252a30cb3f79302e1c9ad79e24a683 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:40:06 -0600 Subject: [PATCH 2/9] feat: add common metadata fields --- stac/CHANGELOG.md | 1 + stac/src/asset.rs | 22 ++++++++++++++++ stac/src/item.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 095bbce1..6b3db09e 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) +- Common metadata fields ([#203](https://github.com/stac-utils/stac-rs/pull/203)) ### Removed diff --git a/stac/src/asset.rs b/stac/src/asset.rs index 0b96e8b5..23ba5cfb 100644 --- a/stac/src/asset.rs +++ b/stac/src/asset.rs @@ -31,6 +31,26 @@ pub struct Asset { #[serde(skip_serializing_if = "Option::is_none")] pub roles: Option>, + /// Creation date and time of the corresponding data, in UTC. + /// + /// This identifies the creation time of the data. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + + /// Date and time the metadata was updated last, in UTC. + /// + /// This identifies the updated time of the data. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub updated: Option, + /// Additional fields on the asset. #[serde(flatten)] pub additional_fields: Map, @@ -84,6 +104,8 @@ impl Asset { description: None, r#type: None, roles: None, + created: None, + updated: None, additional_fields: Map::new(), } } diff --git a/stac/src/item.rs b/stac/src/item.rs index a3ff5f21..5f0dd80a 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -105,6 +105,64 @@ pub struct Properties { /// requires `start_datetime` and `end_datetime` from common metadata to be set. pub datetime: Option, + /// The first or start date and time for the Item, in UTC. + /// + /// It is formatted as date-time according to RFC 3339, section 5.6. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_datetime: Option, + + /// The last or end date and time for the Item, in UTC. + /// + /// It is formatted as date-time according to RFC 3339, section 5.6. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub end_datetime: Option, + + /// A human readable title describing the Item. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + /// Detailed multi-line description to fully explain the Item. + /// + /// CommonMark 0.29 syntax MAY be used for rich text representation. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// Creation date and time of the corresponding data, in UTC. + /// + /// This identifies the creation time of the metadata. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + + /// Date and time the metadata was updated last, in UTC. + /// + /// This identifies the updated time of the metadata. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub updated: Option, + /// Additional fields on the properties. #[serde(flatten)] pub additional_fields: Map, @@ -114,6 +172,12 @@ impl Default for Properties { fn default() -> Properties { Properties { datetime: Some(Utc::now().to_rfc3339()), + start_datetime: None, + end_datetime: None, + title: None, + description: None, + created: None, + updated: None, additional_fields: Map::new(), } } From 81e27cf0a74d90c2a91817fb7861b36cd018732c Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:43:49 -0600 Subject: [PATCH 3/9] fix: deprecate, don't remove, intersects_bbox --- RELEASING.md | 7 ++++--- stac/CHANGELOG.md | 4 ++-- stac/src/item.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index c76756f3..0427f330 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,9 +8,10 @@ 4. Update the package's `Cargo.toml` file accordingly, and update the other packages' `Cargo.toml` if they depend on this package. 5. Scan the package's README for references to version numbers, and update any that are needed. 6. Update the package's CHANGELOG with a new section for the new version. Don't forget to update the links at the bottom, too. -7. Test the release with `cargo release -p {package name}`. By default, this does a dry-run, so it won't actually do anything. -8. Use the normal pull request workflow to merge your branch. -9. Once merged, run `cargo release --execute` to do the release. Use the same `-p` flags as you did during the dry run. +7. If it's a breaking release, search for any deprecated functions that should be removed. +8. Test the release with `cargo release -p {package name}`. By default, this does a dry-run, so it won't actually do anything. +9. Use the normal pull request workflow to merge your branch. +10. Once merged, run `cargo release --execute` to do the release. Use the same `-p` flags as you did during the dry run. ## After-the-fact releases diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 6b3db09e..f0faf490 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -11,9 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) - Common metadata fields ([#203](https://github.com/stac-utils/stac-rs/pull/203)) -### Removed +### Deprecated -- `Item.intersects_bbox` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) +- `Item.intersects_bbox` ([#204](https://github.com/stac-utils/stac-rs/pull/204)) ## [0.5.1] - 2023-09-14 diff --git a/stac/src/item.rs b/stac/src/item.rs index 5f0dd80a..15371fe6 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -303,6 +303,34 @@ impl Item { } } + /// Returns true if this item's geometry intersects the provided bounding box. + /// + /// DEPRECATED Use `intersects` instead. + /// + /// # Examples + /// + /// ``` + /// use stac::Item; + /// use geojson::{Geometry, Value}; + /// + /// let mut item = Item::new("an-id"); + /// item.set_geometry(Some(Geometry::new(Value::Point(vec![-105.1, 41.1])))); + /// let bbox = stac::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap(); + /// assert!(item.intersects_bbox(bbox).unwrap()); + /// ``` + #[cfg(feature = "geo")] + #[deprecated(since = "0.5.2", note = "Use intersects instead")] + pub fn intersects_bbox(&self, bbox: geo::Rect) -> Result { + use geo::Intersects; + + if let Some(geometry) = self.geometry.clone() { + let geometry: geo::Geometry = geometry.try_into()?; + Ok(geometry.intersects(&bbox)) + } else { + Ok(false) + } + } + /// Returns true if this item's datetime (or start and end datetimes) /// intersects the provided datetime. /// From 8123f1fd7d27266434c9ab9fcdc9690463484a78 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:59:10 -0600 Subject: [PATCH 4/9] release: stac v0.5.2 --- stac/CHANGELOG.md | 5 ++++- stac/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index f0faf490..849d8586 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.5.2] - 2023-10-18 + ### Added - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) @@ -248,7 +250,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Initial release. -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.1...main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.2...main +[0.5.2]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.1...stac-v0.5.2 [0.5.1]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.0...stac-v0.5.1 [0.5.0]: https://github.com/stac-utils/stac-rs/compare/stac-v0.4.0...stac-v0.5.0 [0.4.0]: https://github.com/stac-utils/stac-rs/compare/stac-v0.3.2...stac-v0.4.0 diff --git a/stac/Cargo.toml b/stac/Cargo.toml index 339d0336..e2007a50 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac" -version = "0.5.1" +version = "0.5.2" authors = ["Pete Gadomski "] edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) specification" From 95442a472badab6636aa70aab337824a21fb0f1b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:39:43 -0600 Subject: [PATCH 5/9] feat: add Geometry::point --- stac/src/geometry.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ stac/src/item.rs | 27 +----------------- stac/src/lib.rs | 4 ++- 3 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 stac/src/geometry.rs diff --git a/stac/src/geometry.rs b/stac/src/geometry.rs new file mode 100644 index 00000000..d597e8b8 --- /dev/null +++ b/stac/src/geometry.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[cfg(feature = "geo")] +use crate::{Error, Result}; + +/// Additional metadata fields can be added to the GeoJSON Object Properties. +/// +/// We can't just use the [geojson] crate because it doesn't implement [schemars]. +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct Geometry { + /// The geometry type. + pub r#type: String, + + /// The other geometry attributes. + /// + /// `GeometryCollection` doesn't have a `coordinates` member, so we must + /// capture everything in a flat, generic array. + #[serde(flatten)] + pub attributes: Map, +} + +impl Geometry { + /// Creates a point geometry. + /// + /// # Examples + /// + /// ``` + /// use stac::Geometry; + /// let geometry = Geometry::point(-108.0, 42.0); + /// ``` + pub fn point(x: f64, y: f64) -> Geometry { + use serde_json::json; + + let mut attributes = Map::new(); + let _ = attributes.insert("coordinates".to_string(), json!([x, y])); + Geometry { + r#type: "Point".to_string(), + attributes, + } + } +} + +#[cfg(feature = "geo")] +impl TryFrom for geo::Geometry { + type Error = Error; + fn try_from(geometry: Geometry) -> Result { + serde_json::from_value::(serde_json::to_value(geometry)?)? + .try_into() + .map_err(Error::from) + } +} + +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "geo")] + fn point() { + let point = super::Geometry::point(-108.0, 42.0); + let geometry: geo::Geometry = point.try_into().unwrap(); + assert_eq!( + geometry, + geo::Geometry::Point(geo::Point::new(-108.0, 42.0)) + ); + } +} diff --git a/stac/src/item.rs b/stac/src/item.rs index 15371fe6..9027a182 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -1,4 +1,4 @@ -use crate::{Asset, Assets, Error, Extensions, Href, Link, Links, Result, STAC_VERSION}; +use crate::{Asset, Assets, Error, Extensions, Geometry, Href, Link, Links, Result, STAC_VERSION}; use chrono::{DateTime, FixedOffset, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -81,21 +81,6 @@ pub struct Item { href: Option, } -/// Additional metadata fields can be added to the GeoJSON Object Properties. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct Geometry { - /// The geometry type. - pub r#type: String, - - /// The other geometry attributes. - /// - /// `GeometryCollection` doesn't have a `coordinates` member, so we must - /// capture everything in a flat, generic array. - #[serde(flatten)] - pub attributes: Map, -} - /// Additional metadata fields can be added to the GeoJSON Object Properties. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Properties { @@ -441,16 +426,6 @@ impl TryFrom> for Item { } } -#[cfg(feature = "geo")] -impl TryFrom for geo::Geometry { - type Error = Error; - fn try_from(geometry: Geometry) -> Result { - serde_json::from_value::(serde_json::to_value(geometry)?)? - .try_into() - .map_err(Error::from) - } -} - fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result where D: serde::de::Deserializer<'de>, diff --git a/stac/src/lib.rs b/stac/src/lib.rs index 1e6703bf..7b001892 100644 --- a/stac/src/lib.rs +++ b/stac/src/lib.rs @@ -118,6 +118,7 @@ mod error; mod extensions; #[cfg(feature = "geo")] pub mod geo; +mod geometry; mod href; mod io; mod item; @@ -132,9 +133,10 @@ pub use { collection::{Collection, Extent, Provider, SpatialExtent, TemporalExtent, COLLECTION_TYPE}, error::Error, extensions::Extensions, + geometry::Geometry, href::{href_to_url, Href}, io::{read, read_json}, - item::{Geometry, Item, Properties, ITEM_TYPE}, + item::{Item, Properties, ITEM_TYPE}, item_collection::{ItemCollection, ITEM_COLLECTION_TYPE}, link::{Link, Links}, value::Value, From 2767c636337287241ad5205d247b42747a9ace80 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:41:56 -0600 Subject: [PATCH 6/9] feat: add Search.validate --- stac-api/src/error.rs | 5 +++++ stac-api/src/search.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/stac-api/src/error.rs b/stac-api/src/error.rs index 41ba43df..a47a6619 100644 --- a/stac-api/src/error.rs +++ b/stac-api/src/error.rs @@ -1,3 +1,4 @@ +use crate::Search; use serde_json::{Map, Value}; use thiserror::Error; @@ -23,6 +24,10 @@ pub enum Error { #[error(transparent)] ParseFloatError(#[from] std::num::ParseFloatError), + /// A search has both bbox and intersects. + #[error("search has bbox and intersects")] + SearchHasBboxAndIntersects(Search), + /// [serde_json::Error] #[error(transparent)] SerdeJson(#[from] serde_json::Error), diff --git a/stac-api/src/search.rs b/stac-api/src/search.rs index aadbd585..63d1d703 100644 --- a/stac-api/src/search.rs +++ b/stac-api/src/search.rs @@ -123,6 +123,29 @@ pub struct GetSearch { pub additional_fields: HashMap, } +impl Search { + /// Validates this search. + /// + /// E.g. the search is invalid if both bbox and intersects are specified. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// let mut search = Search { bbox: Some(vec![-180.0, -90.0, 180.0, 80.0]), ..Default::default() }; + /// search.validate().unwrap(); + /// search.intersects = Some(stac::Geometry::point(0., 0.)); + /// let _ = search.validate().unwrap_err(); + /// ``` + pub fn validate(&self) -> Result<()> { + if self.bbox.is_some() & self.intersects.is_some() { + Err(Error::SearchHasBboxAndIntersects(self.clone())) + } else { + Ok(()) + } + } +} + impl TryFrom for GetSearch { type Error = Error; From a0536660bd220ab3b678f4259c5919570a69ed84 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:45:52 -0600 Subject: [PATCH 7/9] chore: update changelog --- stac-api/CHANGELOG.md | 4 ++++ stac/CHANGELOG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/stac-api/CHANGELOG.md b/stac-api/CHANGELOG.md index 2203d253..c663b6df 100644 --- a/stac-api/CHANGELOG.md +++ b/stac-api/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Search.validate` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) + ## [0.3.2] - 2023-10-11 ### Added diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 849d8586..6d0ab4a2 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Geometry::point` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) + ## [0.5.2] - 2023-10-18 ### Added From c5cc7b47710f44e6c0b0ab5f2f56fcfcd6e8be32 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 08:22:50 -0600 Subject: [PATCH 8/9] feat: validate collections endpoint --- stac-cli/CHANGELOG.md | 4 +++ stac-cli/src/command.rs | 10 ++++-- stac-cli/src/commands/validate.rs | 59 ++++++++++++++++++++++++++----- stac-cli/src/error.rs | 3 ++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/stac-cli/CHANGELOG.md b/stac-cli/CHANGELOG.md index e2fe80f8..74ac8103 100644 --- a/stac-cli/CHANGELOG.md +++ b/stac-cli/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Validation for the collections endpoint ([#208](https://github.com/stac-utils/stac-rs/pull/208)) + ## [0.0.5] - 2023-10-11 ### Added diff --git a/stac-cli/src/command.rs b/stac-cli/src/command.rs index eb5eb79a..56680a0d 100644 --- a/stac-cli/src/command.rs +++ b/stac-cli/src/command.rs @@ -83,9 +83,15 @@ pub enum Command { compact: bool, }, - /// Validates a STAC object using json-schema validation. + /// Validates a STAC object or API endpoint using json-schema validation. Validate { - /// The href of the STAC object. + /// The href of the STAC object or endpoint. + /// + /// The validator will make some decisions depending on what type of + /// data is returned from the href. If it's a STAC Catalog, Collection, + /// or Item, that object will be validated. If its a collections + /// endpoint from a STAC API, all collections will be validated. + /// Additional behavior TBD. href: String, }, } diff --git a/stac-cli/src/commands/validate.rs b/stac-cli/src/commands/validate.rs index 2ed8b65e..c25d1b3b 100644 --- a/stac-cli/src/commands/validate.rs +++ b/stac-cli/src/commands/validate.rs @@ -1,12 +1,55 @@ -use crate::Result; -use stac_validate::Validate; +use crate::{Error, Result}; +use stac_validate::{Validate, Validator}; pub async fn validate(href: &str) -> Result<()> { let value: serde_json::Value = stac_async::read_json(href).await?; - let result = { - let value = value.clone(); - tokio::task::spawn_blocking(move || value.validate()).await? - }; + if let Some(map) = value.as_object() { + if map.contains_key("type") { + let value = value.clone(); + let result = tokio::task::spawn_blocking(move || value.validate()).await?; + print_result(result).map_err(Error::from) + } else if let Some(collections) = map + .get("collections") + .and_then(|collections| collections.as_array()) + { + let collections = collections.clone(); + let result = tokio::task::spawn_blocking(move || { + let mut errors = Vec::new(); + let mut validator = Validator::new(); + let num_collections = collections.len(); + let mut valid_collections = 0; + for collection in collections { + if let Some(id) = collection.get("id").and_then(|id| id.as_str()) { + println!("== Validating {}", id); + } + let result = validator.validate(collection); + match print_result(result) { + Ok(()) => valid_collections += 1, + Err(err) => errors.push(err), + } + println!("") + } + println!( + "{}/{} collections are valid", + valid_collections, num_collections + ); + if errors.is_empty() { + Ok(()) + } else { + Err(Error::ValidationGroup(errors)) + } + }) + .await?; + result + } else { + todo!() + } + } else { + todo!() + } +} + +pub fn print_result(result: stac_validate::Result<()>) -> stac_validate::Result<()> { match result { Ok(()) => { println!("OK!"); @@ -16,11 +59,11 @@ pub async fn validate(href: &str) -> Result<()> { for err in &errors { println!("Validation error at {}: {}", err.instance_path, err) } - Err(stac_validate::Error::Validation(errors).into()) + Err(stac_validate::Error::Validation(errors)) } Err(err) => { println!("Error while validating: {}", err); - Err(err.into()) + Err(err) } } } diff --git a/stac-cli/src/error.rs b/stac-cli/src/error.rs index a9c5518d..6ccb2022 100644 --- a/stac-cli/src/error.rs +++ b/stac-cli/src/error.rs @@ -24,6 +24,9 @@ pub enum Error { #[error(transparent)] TokioJoinError(#[from] tokio::task::JoinError), + + #[error("many validation errors")] + ValidationGroup(Vec), } impl Error { From a1299604811dd3fc1b6fda9dc78608a43a76274d Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 08:34:51 -0600 Subject: [PATCH 9/9] release: stac-cli v0.0.6 --- stac-cli/CHANGELOG.md | 5 ++++- stac-cli/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stac-cli/CHANGELOG.md b/stac-cli/CHANGELOG.md index 74ac8103..f0732cf2 100644 --- a/stac-cli/CHANGELOG.md +++ b/stac-cli/CHANGELOG.md @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.0.6] - 2023-10-18 + ### Added - Validation for the collections endpoint ([#208](https://github.com/stac-utils/stac-rs/pull/208)) @@ -37,7 +39,8 @@ Moved over from [stac-incubator-rs](https://github.com/gadomski/stac-incubator-r - Downloading ([#142](https://github.com/stac-utils/stac-rs/pull/142), [#152](https://github.com/stac-utils/stac-rs/pull/152)) - Validation ([#155](https://github.com/stac-utils/stac-rs/pull/155)) -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.5..main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.6..main +[0.0.6]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.5..stac-cli-v0.0.6 [0.0.5]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.4..stac-cli-v0.0.5 [0.0.4]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.3..stac-cli-v0.0.4 [0.0.3]: https://github.com/stac-utils/stac-rs/tree/stac-cli-v0.0.3 diff --git a/stac-cli/Cargo.toml b/stac-cli/Cargo.toml index c56f7116..29fbd557 100644 --- a/stac-cli/Cargo.toml +++ b/stac-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac-cli" -version = "0.0.5" +version = "0.0.6" edition = "2021" description = "Command line interface for stac-rs" documentation = "https://docs.rs/stac-cli"