stac_types/
migrate.rs

1use crate::{Error, Result, Version};
2use serde::{de::DeserializeOwned, Serialize};
3use serde_json::{Map, Value};
4use std::collections::HashMap;
5
6/// Migrates a STAC object from one version to another.
7pub trait Migrate: Sized + Serialize + DeserializeOwned + std::fmt::Debug {
8    /// Migrates this object to another version.
9    ///
10    /// # Examples
11    ///
12    /// ```
13    /// use stac::{Item, Migrate, Version};
14    ///
15    /// let mut item: Item = stac::read("../../spec-examples/v1.0.0/simple-item.json").unwrap();
16    /// let item = item.migrate(&Version::v1_1_0).unwrap();
17    /// assert_eq!(item.version, Version::v1_1_0);
18    /// ```
19    fn migrate(self, to: &Version) -> Result<Self> {
20        let mut value = serde_json::to_value(self)?;
21        if let Some(version) = value
22            .as_object()
23            .and_then(|object| object.get("stac_version"))
24            .and_then(|version| version.as_str())
25        {
26            let from: Version = version.parse().unwrap(); // infallible
27            let steps = from.steps(to)?;
28            for step in steps {
29                value = step.migrate(value)?;
30            }
31            let _ = value
32                .as_object_mut()
33                .unwrap()
34                .insert("stac_version".into(), to.to_string().into());
35        } else {
36            tracing::warn!("no stac_version attribute found, skipping any migrations");
37        }
38        serde_json::from_value(value).map_err(Error::from)
39    }
40}
41
42#[allow(non_camel_case_types)]
43enum Step {
44    v1_0_0_to_v1_1_0_beta_1,
45    v1_0_0_to_v1_1_0,
46}
47
48impl Version {
49    fn steps(self, to: &Version) -> Result<Vec<Step>> {
50        match self {
51            Version::v1_0_0 => match to {
52                Version::v1_0_0 => Ok(Vec::new()),
53                Version::v1_1_0_beta_1 => Ok(vec![Step::v1_0_0_to_v1_1_0_beta_1]),
54                Version::v1_1_0 => Ok(vec![Step::v1_0_0_to_v1_1_0]),
55                _ => Err(Error::UnsupportedMigration(self, to.clone())),
56            },
57            Version::v1_1_0_beta_1 => match to {
58                Version::v1_1_0_beta_1 => Ok(Vec::new()),
59                _ => Err(Error::UnsupportedMigration(self, to.clone())),
60            },
61            Version::v1_1_0 => match to {
62                Version::v1_1_0 => Ok(Vec::new()),
63                _ => Err(Error::UnsupportedMigration(self, to.clone())),
64            },
65            Version::Unknown(ref from) => match to {
66                Version::Unknown(to_str) => {
67                    if from == to_str {
68                        Ok(Vec::new())
69                    } else {
70                        Err(Error::UnsupportedMigration(self, to.clone()))
71                    }
72                }
73                _ => Err(Error::UnsupportedMigration(self, to.clone())),
74            },
75        }
76    }
77}
78
79impl Step {
80    fn migrate(&self, mut value: Value) -> Result<Value> {
81        if let Some(mut object) = value.as_object_mut() {
82            match self {
83                Step::v1_0_0_to_v1_1_0_beta_1 | Step::v1_0_0_to_v1_1_0 => {
84                    tracing::debug!("migrating from v1.0.0 to v1.1.0");
85                    if let Some(assets) = object.get_mut("assets").and_then(|v| v.as_object_mut()) {
86                        for asset in assets.values_mut().filter_map(|v| v.as_object_mut()) {
87                            migrate_bands(asset)?;
88                        }
89                    }
90                    migrate_links(object);
91                    if object
92                        .get("type")
93                        .and_then(|t| t.as_str())
94                        .map(|t| t == "Feature")
95                        .unwrap_or_default()
96                    {
97                        if object
98                            .get("properties")
99                            .and_then(|p| p.as_object())
100                            .is_none()
101                        {
102                            let _ = object.insert(
103                                "properties".to_string(),
104                                Value::Object(Default::default()),
105                            );
106                        }
107                        object = object
108                            .get_mut("properties")
109                            .and_then(|v| v.as_object_mut())
110                            .unwrap();
111                    }
112                    migrate_license(object);
113                }
114            }
115        }
116        Ok(value)
117    }
118}
119
120fn migrate_bands(asset: &mut Map<String, Value>) -> Result<()> {
121    let mut bands: Vec<Map<String, Value>> = Vec::new();
122    if let Some(Value::Array(eo)) = asset.remove("eo:bands") {
123        bands.resize_with(eo.len(), Default::default);
124        for (eo_band, band) in eo.into_iter().zip(bands.iter_mut()) {
125            if let Value::Object(eo_band) = eo_band {
126                for (key, value) in eo_band.into_iter() {
127                    if key == "name" {
128                        let _ = band.insert(key, value);
129                    } else {
130                        let _ = band.insert(format!("eo:{}", key), value);
131                    }
132                }
133            }
134        }
135    }
136    if let Some(Value::Array(raster)) = asset.remove("raster:bands") {
137        if raster.len() > bands.len() {
138            bands.resize_with(raster.len(), Default::default);
139        }
140        for (raster_band, band) in raster.into_iter().zip(bands.iter_mut()) {
141            if let Value::Object(raster_band) = raster_band {
142                for (key, value) in raster_band.into_iter() {
143                    if key == "nodata" || key == "data_type" || key == "statistics" || key == "unit"
144                    {
145                        let _ = band.insert(key, value);
146                    } else {
147                        let _ = band.insert(format!("raster:{}", key), value);
148                    }
149                }
150            }
151        }
152    }
153    let mut counts: HashMap<String, HashMap<String, u64>> = HashMap::new();
154    let mut values: HashMap<String, Value> = HashMap::new();
155    for band in &bands {
156        for (key, value) in band {
157            let value_as_string = serde_json::to_string(value)?;
158            if !values.contains_key(&value_as_string) {
159                let _ = values.insert(value_as_string.clone(), value.clone());
160            }
161            *counts
162                .entry(key.to_string())
163                .or_default()
164                .entry(value_as_string)
165                .or_default() += 1;
166        }
167    }
168    for (key, count) in counts {
169        if let Some((value_as_string, &count)) = count.iter().max_by_key(|(_, &count)| count) {
170            if count > 1 {
171                let value = values
172                    .get(value_as_string)
173                    .expect("every value should be in the lookup hash")
174                    .clone();
175                for band in &mut bands {
176                    if band.get(&key).map(|v| v == &value).unwrap_or_default() {
177                        let value = band.remove(&key).unwrap();
178                        let _ = asset.insert(key.clone(), value);
179                    }
180                }
181            }
182        }
183    }
184    if bands.iter().any(|band| !band.is_empty()) {
185        let _ = asset.insert(
186            "bands".into(),
187            Value::Array(bands.into_iter().map(Value::Object).collect()),
188        );
189    }
190    Ok(())
191}
192
193fn migrate_links(object: &mut Map<String, Value>) {
194    if let Some(links) = object.get_mut("links").and_then(|v| v.as_array_mut()) {
195        for link in links {
196            if let Some(link) = link.as_object_mut() {
197                if link
198                    .get("rel")
199                    .and_then(|v| v.as_str())
200                    .map(|s| s == "self")
201                    .unwrap_or_default()
202                {
203                    if let Some(href) = link.get("href").and_then(|v| v.as_str()) {
204                        if href.starts_with('/') {
205                            let _ =
206                                link.insert("href".to_string(), format!("file://{}", href).into());
207                        }
208                    }
209                }
210            }
211        }
212    }
213}
214
215fn migrate_license(object: &mut Map<String, Value>) {
216    if object
217        .get("license")
218        .and_then(|v| v.as_str())
219        .map(|l| l == "proprietary" || l == "various")
220        .unwrap_or_default()
221    {
222        let _ = object.insert("license".into(), "other".to_string().into());
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use assert_json_diff::assert_json_eq;
229    use serde_json::Value;
230    use stac::{Collection, DataType, Item, Link, Links, Migrate, Version};
231
232    #[test]
233    fn migrate_v1_0_0_to_v1_1_0() {
234        let item: Item = stac::read("data/bands-v1.0.0.json").unwrap();
235        let item = item.migrate(&Version::v1_1_0).unwrap();
236        let asset = &item.assets["example"];
237        assert_eq!(asset.data_type.as_ref().unwrap(), &DataType::UInt16);
238        assert_eq!(asset.bands[0].name.as_ref().unwrap(), "r");
239        assert_eq!(asset.bands[1].name.as_ref().unwrap(), "g");
240        assert_eq!(asset.bands[2].name.as_ref().unwrap(), "b");
241        assert_eq!(asset.bands[3].name.as_ref().unwrap(), "nir");
242
243        let expected: Value =
244            serde_json::to_value(stac::read::<Item>("data/bands-v1.1.0.json").unwrap()).unwrap();
245        assert_json_eq!(expected, serde_json::to_value(item).unwrap());
246
247        let mut collection = Collection::new("an-id", "a description");
248        collection.version = Version::v1_0_0;
249        let collection = collection.migrate(&Version::v1_1_0).unwrap();
250        assert_eq!(collection.license, "other");
251
252        let mut item = Item::new("an-id");
253        item.version = Version::v1_0_0;
254        item.set_link(Link::self_("/an/absolute/href"));
255        let item = item.migrate(&Version::v1_1_0).unwrap();
256        assert_eq!(item.link("self").unwrap().href, "file:///an/absolute/href");
257    }
258
259    #[test]
260    fn remove_empty_bands() {
261        // https://github.com/stac-utils/stac-rs/issues/350
262        let item: Item = stac::read("data/20201211_223832_CS2.json").unwrap();
263        let item = item.migrate(&Version::v1_1_0).unwrap();
264        let asset = &item.assets["data"];
265        assert!(asset.bands.is_empty());
266    }
267
268    #[test]
269    fn migrate_v1_1_0_to_v1_1_0() {
270        let item: Item = stac::read("../../spec-examples/v1.1.0/simple-item.json").unwrap();
271        let _ = item.migrate(&Version::v1_1_0).unwrap();
272    }
273}