1use crate::{Error, Result, Version};
2use serde::{de::DeserializeOwned, Serialize};
3use serde_json::{Map, Value};
4use std::collections::HashMap;
5
6pub trait Migrate: Sized + Serialize + DeserializeOwned + std::fmt::Debug {
8 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(); 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 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}