1mod column_style;
35mod css;
36mod error;
37mod json;
38mod notice;
39mod plugin;
40mod plugins;
41mod step;
42mod version;
43mod view;
44mod viewer;
45mod workspace;
46
47pub use error::MigrateError;
48pub use notice::Notice;
49pub use plugin::PluginMigration;
50use serde::{Deserialize, Serialize};
51use serde_json::Value;
52pub use step::{Apply, Step, StepCx};
53use step::{Entry, Layer, Run, panels};
54pub use version::Version;
55
56#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
58#[serde(rename_all = "lowercase")]
59pub enum Kind {
60 View,
62
63 Viewer,
65
66 Workspace,
68}
69
70#[derive(Clone, Debug, PartialEq)]
72pub struct Migrated {
73 pub value: Value,
75
76 pub notices: Vec<Notice>,
78
79 pub steps: Vec<&'static str>,
81}
82
83pub struct Migrator {
85 plugins: Vec<Box<dyn PluginMigration>>,
86}
87
88impl Default for Migrator {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Migrator {
95 pub fn new() -> Self {
97 Self {
98 plugins: plugins::first_party(),
99 }
100 }
101
102 pub fn with_plugin(mut self, plugin: impl PluginMigration + 'static) -> Self {
108 let owned = self
109 .plugins
110 .iter()
111 .flat_map(|x| x.plugins().iter().copied())
112 .collect::<Vec<_>>();
113
114 if let Some(name) = plugin.plugins().iter().find(|x| owned.contains(x)) {
115 panic!("plugin `{name}` already has a migration");
116 }
117
118 self.plugins.push(Box::new(plugin));
119 self
120 }
121
122 pub fn migrate(
135 &self,
136 kind: Kind,
137 from: Option<&str>,
138 value: Value,
139 ) -> Result<Migrated, MigrateError> {
140 let mut value = value;
141 if !value.is_object() {
142 return Err(MigrateError::NotAnObject);
143 }
144
145 let schema = self.schema_version();
146 let from = match value.get("version").and_then(Value::as_str).or(from) {
147 Some(version) => check_version(version, schema)?,
148 None => Version::new(2, 0, 0),
149 };
150
151 let entries = self.entries();
152 let known_plugins = self.plugin_names();
153 let mut run = Run {
154 kind,
155 from,
156 schema,
157 known_plugins: &known_plugins,
158 notices: vec![],
159 steps: vec![],
160 };
161
162 run.apply(&entries, &mut value)?;
163 stamp(kind, &schema.to_string(), &mut value);
164 Ok(Migrated {
165 value,
166 notices: run.notices,
167 steps: run.steps,
168 })
169 }
170
171 pub fn step_ids(&self) -> Vec<&'static str> {
173 self.entries().iter().map(|x| x.step.id).collect()
174 }
175
176 pub fn newest_step_version(&self) -> Version {
178 self.entries()
179 .iter()
180 .map(|x| x.step.at)
181 .max()
182 .unwrap_or_else(Version::crate_version)
183 }
184
185 pub fn schema_version(&self) -> Version {
187 self.newest_step_version().max(Version::crate_version())
188 }
189
190 fn plugin_names(&self) -> Vec<&'static str> {
191 let mut names = plugins::REGISTRATION_ORDER.to_vec();
192 for name in self.plugins.iter().flat_map(|x| x.plugins().iter()) {
193 if !names.contains(name) {
194 names.push(name);
195 }
196 }
197
198 names
199 }
200
201 fn entries(&self) -> Vec<Entry<'_>> {
202 let fixed = [
203 (Layer::View, view::STEPS),
204 (Layer::Viewer, viewer::STEPS),
205 (Layer::ColumnStyle, column_style::STEPS),
206 (Layer::Workspace, workspace::STEPS),
207 ];
208
209 let mut entries = fixed
210 .into_iter()
211 .flat_map(|(layer, steps)| {
212 steps.iter().map(move |step| Entry {
213 layer,
214 plugins: &[],
215 step: *step,
216 })
217 })
218 .collect::<Vec<_>>();
219
220 for plugin in &self.plugins {
221 entries.extend(plugin.steps().into_iter().map(|step| Entry {
222 layer: Layer::Plugin,
223 plugins: plugin.plugins(),
224 step,
225 }));
226 }
227
228 entries.sort_by_key(|x| (x.step.at, x.layer));
229 entries
230 }
231}
232
233pub(crate) fn check_version(version: &str, schema: Version) -> Result<Version, MigrateError> {
234 let parsed = Version::parse(version).ok_or_else(|| MigrateError::InvalidVersion {
235 version: version.to_owned(),
236 })?;
237
238 if parsed < Version::new(2, 0, 0) {
239 Err(MigrateError::Unsupported {
240 version: version.to_owned(),
241 })
242 } else if parsed > schema {
243 Err(MigrateError::FromFuture {
244 version: version.to_owned(),
245 })
246 } else {
247 Ok(parsed)
248 }
249}
250
251fn stamp(kind: Kind, version: &str, value: &mut Value) {
252 let version = Value::from(version);
253 match kind {
254 Kind::View => {},
255 Kind::Viewer => {
256 value["version"] = version;
257 },
258 Kind::Workspace => {
259 for pointer in panels(value) {
260 if let Some(Value::Object(panel)) = value.pointer_mut(&pointer)
261 && panel.contains_key("version")
262 {
263 panel.insert("version".to_owned(), version.clone());
264 }
265 }
266
267 value["version"] = version;
268 },
269 }
270}
271
272use wasm_bindgen::prelude::*;
273
274#[wasm_bindgen]
277pub fn migrate(kind: &str, from: Option<String>, value: JsValue) -> Result<JsValue, JsError> {
278 let kind = match kind {
279 "view" => Kind::View,
280 "viewer" => Kind::Viewer,
281 "workspace" => Kind::Workspace,
282 _ => {
283 return Err(JsError::new(&format!(
284 "Unknown config kind `{kind}`; expected `view`, `viewer` or `workspace`"
285 )));
286 },
287 };
288
289 let value: serde_json::Value = serde_wasm_bindgen::from_value(value)?;
290 let migrated = Migrator::new().migrate(kind, from.as_deref(), value)?;
291 let result = serde_json::json!({
292 "value": migrated.value,
293 "notices": migrated.notices,
294 "steps": migrated.steps,
295 });
296
297 Ok(result.serialize(&serde_wasm_bindgen::Serializer::json_compatible())?)
298}