Skip to content

Commit 6050dec

Browse files
committed
feat(core): use internal tagging for Value
That worked better than expected.
1 parent 7f5385b commit 6050dec

File tree

9 files changed

+17
-260
lines changed

9 files changed

+17
-260
lines changed

crates/api/src/item_collection.rs

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,15 @@ use serde::{Deserialize, Serialize};
33
use serde_json::{Map, Value};
44
use stac::{Href, Link, Links};
55

6-
const ITEM_COLLECTION_TYPE: &str = "FeatureCollection";
7-
86
/// The return value of the `/items` and `/search` endpoints.
97
///
108
/// This might be a [stac::ItemCollection], but if the [fields
119
/// extension](https://github.com/stac-api-extensions/fields) is used, it might
1210
/// not be. Defined by the [itemcollection
1311
/// fragment](https://github.com/radiantearth/stac-api-spec/blob/main/fragments/itemcollection/README.md).
14-
#[derive(Debug, Serialize, Deserialize)]
12+
#[derive(Debug, Serialize, Deserialize, Default)]
13+
#[serde(tag = "type", rename = "FeatureCollection")]
1514
pub struct ItemCollection {
16-
#[serde(
17-
deserialize_with = "deserialize_type",
18-
serialize_with = "serialize_type"
19-
)]
20-
r#type: String,
21-
2215
/// A possibly-empty array of Item objects.
2316
#[serde(rename = "features")]
2417
pub items: Vec<Item>,
@@ -110,7 +103,6 @@ impl ItemCollection {
110103
pub fn new(items: Vec<Item>) -> Result<ItemCollection> {
111104
let number_returned = items.len();
112105
Ok(ItemCollection {
113-
r#type: ITEM_COLLECTION_TYPE.to_string(),
114106
items,
115107
links: Vec::new(),
116108
number_matched: None,
@@ -147,25 +139,6 @@ impl Links for ItemCollection {
147139
}
148140
}
149141

150-
impl Default for ItemCollection {
151-
fn default() -> Self {
152-
ItemCollection {
153-
r#type: "FeatureCollection".to_string(),
154-
items: Vec::new(),
155-
links: Vec::new(),
156-
number_matched: None,
157-
number_returned: None,
158-
context: None,
159-
additional_fields: Map::default(),
160-
next: None,
161-
prev: None,
162-
first: None,
163-
last: None,
164-
href: None,
165-
}
166-
}
167-
}
168-
169142
impl From<Vec<Item>> for ItemCollection {
170143
fn from(items: Vec<Item>) -> Self {
171144
ItemCollection {
@@ -174,17 +147,3 @@ impl From<Vec<Item>> for ItemCollection {
174147
}
175148
}
176149
}
177-
178-
fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
179-
where
180-
D: serde::de::Deserializer<'de>,
181-
{
182-
stac::deserialize_type(deserializer, ITEM_COLLECTION_TYPE)
183-
}
184-
185-
fn serialize_type<S>(r#type: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
186-
where
187-
S: serde::ser::Serializer,
188-
{
189-
stac::serialize_type(r#type, serializer, ITEM_COLLECTION_TYPE)
190-
}

crates/core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1313
### Removed
1414

1515
- Extensions moved to their own crate ([#473](https://github.com/stac-utils/stac-rs/pull/473))
16+
- `*_TYPE` constants, `deserialize_type` and `serialize_type` top-level functions ([#498](https://github.com/stac-utils/stac-rs/pull/498))
1617

1718
## [0.10.2] - 2024-10-18
1819

crates/core/src/catalog.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ use crate::{Error, Fields, Href, Link, Links, Migrate, Result, Version, STAC_VER
22
use serde::{Deserialize, Serialize};
33
use serde_json::{Map, Value};
44

5-
/// The type field for [Catalogs](Catalog).
6-
pub const CATALOG_TYPE: &str = "Catalog";
7-
85
/// A STAC Catalog object represents a logical group of other `Catalog`,
96
/// [Collection](crate::Collection), and [Item](crate::Item) objects.
107
///
@@ -18,14 +15,8 @@ pub const CATALOG_TYPE: &str = "Catalog";
1815
/// Their purpose is discovery: to be browsed by people or be crawled by clients
1916
/// to build a searchable index.
2017
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
18+
#[serde(tag = "type")]
2119
pub struct Catalog {
22-
/// Set to `"Catalog"` if this Catalog only implements the `Catalog` spec.
23-
#[serde(
24-
deserialize_with = "deserialize_type",
25-
serialize_with = "serialize_type"
26-
)]
27-
r#type: String,
28-
2920
/// The STAC version the `Catalog` implements.
3021
#[serde(rename = "stac_version")]
3122
pub version: Version,
@@ -72,7 +63,6 @@ impl Catalog {
7263
/// ```
7364
pub fn new(id: impl ToString, description: impl ToString) -> Catalog {
7465
Catalog {
75-
r#type: CATALOG_TYPE.to_string(),
7666
version: STAC_VERSION,
7767
extensions: Vec::new(),
7868
id: id.to_string(),
@@ -135,20 +125,6 @@ impl Fields for Catalog {
135125
}
136126
}
137127

138-
fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
139-
where
140-
D: serde::de::Deserializer<'de>,
141-
{
142-
crate::deserialize_type(deserializer, CATALOG_TYPE)
143-
}
144-
145-
fn serialize_type<S>(r#type: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
146-
where
147-
S: serde::ser::Serializer,
148-
{
149-
crate::serialize_type(r#type, serializer, CATALOG_TYPE)
150-
}
151-
152128
impl Migrate for Catalog {}
153129

154130
#[cfg(test)]
@@ -161,7 +137,6 @@ mod tests {
161137
let catalog = Catalog::new("an-id", "a description");
162138
assert!(catalog.title.is_none());
163139
assert_eq!(catalog.description, "a description");
164-
assert_eq!(catalog.r#type, "Catalog");
165140
assert_eq!(catalog.version, STAC_VERSION);
166141
assert!(catalog.extensions.is_empty());
167142
assert_eq!(catalog.id, "an-id");

crates/core/src/collection.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ use serde::{Deserialize, Serialize};
77
use serde_json::{Map, Value};
88
use std::collections::HashMap;
99

10-
/// The type field for [Collections](Collection).
11-
pub const COLLECTION_TYPE: &str = "Collection";
12-
1310
const DEFAULT_LICENSE: &str = "proprietary";
1411

1512
/// The STAC `Collection` Specification defines a set of common fields to describe
@@ -25,14 +22,8 @@ const DEFAULT_LICENSE: &str = "proprietary";
2522
/// contains all the required fields is a valid STAC `Collection` and also a valid
2623
/// STAC `Catalog`.
2724
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
25+
#[serde(tag = "type")]
2826
pub struct Collection {
29-
/// Must be set to `"Collection"` to be a valid `Collection`.
30-
#[serde(
31-
deserialize_with = "deserialize_type",
32-
serialize_with = "serialize_type"
33-
)]
34-
r#type: String,
35-
3627
/// The STAC version the `Collection` implements.
3728
#[serde(rename = "stac_version")]
3829
pub version: Version,
@@ -173,7 +164,6 @@ impl Collection {
173164
/// ```
174165
pub fn new(id: impl ToString, description: impl ToString) -> Collection {
175166
Collection {
176-
r#type: COLLECTION_TYPE.to_string(),
177167
version: STAC_VERSION,
178168
extensions: Vec::new(),
179169
id: id.to_string(),
@@ -418,20 +408,6 @@ impl TryFrom<Map<String, Value>> for Collection {
418408
}
419409
}
420410

421-
fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
422-
where
423-
D: serde::de::Deserializer<'de>,
424-
{
425-
crate::deserialize_type(deserializer, COLLECTION_TYPE)
426-
}
427-
428-
fn serialize_type<S>(r#type: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
429-
where
430-
S: serde::ser::Serializer,
431-
{
432-
crate::serialize_type(r#type, serializer, COLLECTION_TYPE)
433-
}
434-
435411
impl Migrate for Collection {}
436412

437413
#[cfg(test)]
@@ -453,7 +429,6 @@ mod tests {
453429
assert_eq!(collection.extent, Extent::default());
454430
assert!(collection.summaries.is_none());
455431
assert!(collection.assets.is_empty());
456-
assert_eq!(collection.r#type, "Collection");
457432
assert_eq!(collection.version, STAC_VERSION);
458433
assert!(collection.extensions.is_empty());
459434
assert_eq!(collection.id, "an-id");

crates/core/src/item.rs

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ use serde_json::{Map, Value};
1010
use std::{collections::HashMap, path::Path};
1111
use url::Url;
1212

13-
/// The type field for [Items](Item).
14-
pub const ITEM_TYPE: &str = "Feature";
15-
1613
const TOP_LEVEL_ATTRIBUTES: [&str; 8] = [
1714
"type",
1815
"stac_extensions",
@@ -32,14 +29,8 @@ const TOP_LEVEL_ATTRIBUTES: [&str; 8] = [
3229
/// enables any client to search or crawl online catalogs of spatial 'assets'
3330
/// (e.g., satellite imagery, derived data, DEMs).
3431
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
32+
#[serde(tag = "type", rename = "Feature")]
3533
pub struct Item {
36-
/// Type of the GeoJSON Object. MUST be set to `"Feature"`.
37-
#[serde(
38-
deserialize_with = "deserialize_type",
39-
serialize_with = "serialize_type"
40-
)]
41-
r#type: String,
42-
4334
/// The STAC version the `Item` implements.
4435
#[serde(rename = "stac_version")]
4536
pub version: Version,
@@ -107,10 +98,8 @@ pub struct Item {
10798
/// [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet/blob/main/spec/stac-geoparquet-spec.md),
10899
/// use this "flat" representation.
109100
#[derive(Debug, Serialize, Deserialize)]
101+
#[serde(tag = "type", rename = "Feature")]
110102
pub struct FlatItem {
111-
#[serde(default = "default_type")]
112-
r#type: String,
113-
114103
#[serde(rename = "stac_version", default = "default_stac_version")]
115104
version: Version,
116105

@@ -334,7 +323,6 @@ impl Item {
334323
/// ```
335324
pub fn new(id: impl ToString) -> Item {
336325
Item {
337-
r#type: ITEM_TYPE.to_string(),
338326
version: STAC_VERSION,
339327
extensions: Vec::new(),
340328
id: id.to_string(),
@@ -560,7 +548,6 @@ impl Item {
560548
}
561549
}
562550
Ok(FlatItem {
563-
r#type: ITEM_TYPE.to_string(),
564551
version: STAC_VERSION,
565552
extensions: self.extensions,
566553
id: self.id,
@@ -677,36 +664,17 @@ impl TryFrom<Item> for Feature {
677664
}
678665
}
679666

680-
fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
681-
where
682-
D: serde::de::Deserializer<'de>,
683-
{
684-
crate::deserialize_type(deserializer, ITEM_TYPE)
685-
}
686-
687-
fn serialize_type<S>(r#type: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
688-
where
689-
S: serde::ser::Serializer,
690-
{
691-
crate::serialize_type(r#type, serializer, ITEM_TYPE)
692-
}
693-
694667
fn default_stac_version() -> Version {
695668
STAC_VERSION
696669
}
697670

698-
fn default_type() -> String {
699-
ITEM_TYPE.to_string()
700-
}
701-
702671
impl Migrate for Item {}
703672

704673
#[cfg(test)]
705674
mod tests {
706675
use super::{Builder, FlatItem, Item};
707676
use crate::{Asset, STAC_VERSION};
708677
use geojson::{feature::Id, Feature};
709-
use serde_json::Value;
710678

711679
#[test]
712680
fn new() {
@@ -715,7 +683,6 @@ mod tests {
715683
assert!(item.properties.datetime.is_some());
716684
assert!(item.assets.is_empty());
717685
assert!(item.collection.is_none());
718-
assert_eq!(item.r#type, "Feature");
719686
assert_eq!(item.version, STAC_VERSION);
720687
assert!(item.extensions.is_empty());
721688
assert_eq!(item.id, "an-id");
@@ -731,22 +698,6 @@ mod tests {
731698
assert!(value.get("collection").is_none());
732699
}
733700

734-
#[test]
735-
fn deserialize_invalid_type_field() {
736-
let mut item: Value =
737-
serde_json::to_value(crate::read::<Item>("examples/simple-item.json").unwrap())
738-
.unwrap();
739-
item["type"] = "Item".into(); // must be "Feature"
740-
assert!(serde_json::from_value::<Item>(item).is_err());
741-
}
742-
743-
#[test]
744-
fn serialize_invalid_type_field() {
745-
let mut item = Item::new("an-id");
746-
item.r#type = "Item".to_string(); // must be "Feature"
747-
assert!(serde_json::to_value(item).is_err());
748-
}
749-
750701
#[test]
751702
#[cfg(feature = "geo")]
752703
fn set_geometry_sets_bbox() {

crates/core/src/item_collection.rs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ use serde::{Deserialize, Serialize};
33
use serde_json::{Map, Value};
44
use std::{ops::Deref, vec::IntoIter};
55

6-
/// The type field for [ItemCollections](ItemCollection).
7-
pub const ITEM_COLLECTION_TYPE: &str = "FeatureCollection";
8-
96
/// A [GeoJSON FeatureCollection](https://www.rfc-editor.org/rfc/rfc7946#page-12) of items.
107
///
118
/// While not part of the STAC specification, ItemCollections are often used to store many items in a single file.
129
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
10+
#[serde(tag = "type", rename = "FeatureCollection")]
1311
pub struct ItemCollection {
1412
/// The list of [Items](Item).
1513
///
@@ -25,23 +23,13 @@ pub struct ItemCollection {
2523
#[serde(flatten)]
2624
pub additional_fields: Map<String, Value>,
2725

28-
/// The type field.
29-
///
30-
/// Must be set to "FeatureCollection".
31-
#[serde(
32-
deserialize_with = "deserialize_type",
33-
serialize_with = "serialize_type"
34-
)]
35-
r#type: String,
36-
3726
#[serde(skip)]
3827
href: Option<String>,
3928
}
4029

4130
impl From<Vec<Item>> for ItemCollection {
4231
fn from(items: Vec<Item>) -> Self {
4332
ItemCollection {
44-
r#type: ITEM_COLLECTION_TYPE.to_string(),
4533
items,
4634
links: Vec::new(),
4735
additional_fields: Map::new(),
@@ -94,20 +82,6 @@ impl Links for ItemCollection {
9482
}
9583
}
9684

97-
fn deserialize_type<'de, D>(deserializer: D) -> Result<String, D::Error>
98-
where
99-
D: serde::de::Deserializer<'de>,
100-
{
101-
crate::deserialize_type(deserializer, ITEM_COLLECTION_TYPE)
102-
}
103-
104-
fn serialize_type<S>(r#type: &String, serializer: S) -> Result<S::Ok, S::Error>
105-
where
106-
S: serde::ser::Serializer,
107-
{
108-
crate::serialize_type(r#type, serializer, ITEM_COLLECTION_TYPE)
109-
}
110-
11185
impl Migrate for ItemCollection {
11286
fn migrate(mut self, version: &crate::Version) -> crate::Result<Self> {
11387
let mut items = Vec::with_capacity(self.items.len());

0 commit comments

Comments
 (0)