1use serde_json::{Map, Value};
14
15use crate::error::MigrateError;
16use crate::json::escape;
17use crate::notice::Notice;
18use crate::plugins::resolve_plugin;
19use crate::version::Version;
20use crate::{Kind, check_version};
21
22pub type Apply = fn(&mut Map<String, Value>, &mut StepCx) -> Result<(), MigrateError>;
25
26#[derive(Clone, Copy)]
29pub struct Step {
30 pub at: Version,
32
33 pub id: &'static str,
35
36 pub apply: Apply,
38}
39
40pub struct StepCx<'a> {
42 kind: Kind,
43 from: Version,
44 step: &'static str,
45 pointer: &'a str,
46 known_plugins: &'a [&'static str],
47 notices: &'a mut Vec<Notice>,
48}
49
50impl StepCx<'_> {
51 pub fn kind(&self) -> Kind {
53 self.kind
54 }
55
56 pub fn from(&self) -> Version {
58 self.from
59 }
60
61 pub fn dropped(&mut self, path: &[&str], value: Value) {
63 let pointer = self.pointer_to(path);
64 self.notices.push(Notice::Dropped {
65 step: self.step,
66 pointer,
67 value,
68 });
69 }
70
71 pub fn approximated(&mut self, path: &[&str], reason: &'static str) {
73 let pointer = self.pointer_to(path);
74 self.notices.push(Notice::Approximated {
75 step: self.step,
76 pointer,
77 reason,
78 });
79 }
80
81 pub fn malformed(&self, path: &[&str], expected: &'static str) -> MigrateError {
83 MigrateError::Malformed {
84 step: self.step,
85 pointer: self.pointer_to(path),
86 expected,
87 }
88 }
89
90 pub fn conflict(&self, keys: &[&str]) -> MigrateError {
92 MigrateError::Conflict {
93 step: self.step,
94 pointer: self.pointer.to_owned(),
95 keys: keys.iter().map(|x| (*x).to_owned()).collect(),
96 }
97 }
98
99 pub fn missing_table(&self) -> MigrateError {
101 MigrateError::MissingTable {
102 step: self.step,
103 pointer: self.pointer.to_owned(),
104 }
105 }
106
107 pub(crate) fn resolve_plugin(&self, name: &str) -> Option<&'static str> {
108 resolve_plugin(name, self.known_plugins)
109 }
110
111 fn pointer_to(&self, path: &[&str]) -> String {
112 path.iter().fold(self.pointer.to_owned(), |acc, seg| {
113 format!("{acc}/{}", escape(seg))
114 })
115 }
116}
117
118#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
119pub(crate) enum Layer {
120 View,
121 Viewer,
122 ColumnStyle,
123 Plugin,
124 Workspace,
125}
126
127pub(crate) struct Entry<'a> {
128 pub layer: Layer,
129 pub plugins: &'a [&'static str],
130 pub step: Step,
131}
132
133pub(crate) struct Run<'a> {
134 pub kind: Kind,
135 pub from: Version,
136 pub schema: Version,
137 pub known_plugins: &'a [&'static str],
138 pub notices: Vec<Notice>,
139 pub steps: Vec<&'static str>,
140}
141
142impl Run<'_> {
143 pub fn apply(&mut self, entries: &[Entry], root: &mut Value) -> Result<(), MigrateError> {
144 for entry in entries {
145 let notices = self.notices.len();
146 let mut changed = false;
147 for pointer in targets(self.kind, entry.layer, root) {
148 let Some(Value::Object(target)) = root.pointer_mut(&pointer) else {
149 continue;
150 };
151
152 let from = match target.get("version").and_then(Value::as_str) {
153 Some(version) => check_version(version, self.schema)?,
154 None => self.from,
155 };
156
157 if from >= entry.step.at
158 || (entry.layer == Layer::Plugin && !self.owns(entry.plugins, target))
159 {
160 continue;
161 }
162
163 let before = target.clone();
164 let mut cx = StepCx {
165 kind: self.kind_at(&pointer),
166 from,
167 step: entry.step.id,
168 pointer: &pointer,
169 known_plugins: self.known_plugins,
170 notices: &mut self.notices,
171 };
172
173 (entry.step.apply)(target, &mut cx)?;
174 changed |= *target != before;
175 }
176
177 if changed || self.notices.len() > notices {
178 self.steps.push(entry.step.id);
179 }
180 }
181
182 Ok(())
183 }
184
185 fn kind_at(&self, pointer: &str) -> Kind {
186 if self.kind == Kind::Workspace
187 && (pointer.starts_with("/panels/") || pointer.starts_with("/viewers/"))
188 {
189 Kind::Panel
190 } else {
191 self.kind
192 }
193 }
194
195 fn owns(&self, plugins: &[&'static str], target: &Map<String, Value>) -> bool {
196 target
197 .get("plugin")
198 .and_then(Value::as_str)
199 .and_then(|name| resolve_plugin(name, self.known_plugins))
200 .is_some_and(|name| plugins.contains(&name))
201 }
202}
203
204fn targets(kind: Kind, layer: Layer, root: &Value) -> Vec<String> {
205 match (kind, layer) {
206 (Kind::View, Layer::View) => vec![String::new()],
207 (Kind::View, _) => vec![],
208 (Kind::Viewer, Layer::View) => {
209 let mut out = vec![String::new()];
210 let mut pointer = String::from("/table");
211 while root.pointer(&pointer).is_some_and(Value::is_object) {
212 out.push(pointer.clone());
213 pointer.push_str("/table");
214 }
215
216 out
217 },
218 (Kind::Viewer, Layer::Workspace) => vec![],
219 (Kind::Viewer, _) => vec![String::new()],
220 (Kind::Panel, Layer::Workspace) => vec![],
221 (Kind::Panel, _) => vec![String::new()],
222 (Kind::Workspace, Layer::Workspace) => vec![String::new()],
223 (Kind::Workspace, Layer::View) => {
224 let mut out = panels(root);
225 out.extend(children(root, "derived_tables"));
226 out
227 },
228 (Kind::Workspace, _) => panels(root),
229 }
230}
231
232pub(crate) fn panels(root: &Value) -> Vec<String> {
233 let mut out = children(root, "panels");
234 out.extend(children(root, "viewers"));
235 out
236}
237
238fn children(root: &Value, key: &str) -> Vec<String> {
239 root.get(key)
240 .and_then(Value::as_object)
241 .map(|map| {
242 map.keys()
243 .map(|name| format!("/{key}/{}", escape(name)))
244 .collect()
245 })
246 .unwrap_or_default()
247}