1use std::collections::BTreeMap;
4use std::fmt;
5
6use serde::de::{self, SeqAccess, Visitor};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use svugy_model::model::{ByteRange, Edge, EdgeId, Element, Model, Traveler, ViewAngle};
9use svugy_model::path::ElementPath;
10use svugy_model::unique_key_map::UniqueKeyMapVisitor;
11use svugy_model::validate::{ModelAdmission, ModelReport, UncheckedModel};
12use svugy_model::workflow::{ElementOverride, Step, StepId, StepItem, Workflow, WorkflowId};
13
14use crate::diagnostic::{Import, NoWarning, SourceError, SourceLanguage};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct JsonLanguage;
19
20pub type JsonError = SourceError<JsonLanguage>;
22
23pub type JsonImport = Import<JsonLanguage>;
25
26pub struct JsonPrinter;
28
29#[derive(Default, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32struct ModelDocument {
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 view: Option<ViewAngle>,
35 #[serde(deserialize_with = "UniqueKeyMapVisitor::deserialize")]
36 elements: BTreeMap<ElementPath, Element>,
37 workflows: Vec<WorkflowRecord>,
38 #[serde(
39 default,
40 rename = "title",
41 skip_serializing,
42 deserialize_with = "V3RootKey::reject_title"
43 )]
44 _v3_title: (),
45 #[serde(
46 default,
47 rename = "subtitle",
48 skip_serializing,
49 deserialize_with = "V3RootKey::reject_subtitle"
50 )]
51 _v3_subtitle: (),
52 #[serde(
53 default,
54 rename = "description",
55 skip_serializing,
56 deserialize_with = "V3RootKey::reject_description"
57 )]
58 _v3_description: (),
59 #[serde(
60 default,
61 rename = "legend",
62 skip_serializing,
63 deserialize_with = "V3RootKey::reject_legend"
64 )]
65 _v3_legend: (),
66 #[serde(
67 default,
68 rename = "edges",
69 skip_serializing,
70 deserialize_with = "V3RootKey::reject_edges"
71 )]
72 _v3_edges: (),
73}
74
75#[derive(Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78struct WorkflowRecord {
79 id: WorkflowId,
80 title: String,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 subtitle: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 description: Option<String>,
85 #[serde(
86 default,
87 skip_serializing_if = "BTreeMap::is_empty",
88 deserialize_with = "UniqueKeyMapVisitor::deserialize"
89 )]
90 overrides: BTreeMap<ElementPath, ElementOverride>,
91 #[serde(
92 default,
93 skip_serializing_if = "BTreeMap::is_empty",
94 serialize_with = "EdgeRecord::serialize_all",
95 deserialize_with = "UniqueEdgeListVisitor::deserialize"
96 )]
97 edges: BTreeMap<EdgeId, Edge>,
98 steps: Vec<StepRecord>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 span: Option<ByteRange>,
101}
102
103#[derive(Serialize, Deserialize)]
105#[serde(deny_unknown_fields)]
106struct StepRecord {
107 id: StepId,
108 title: String,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 description: Option<String>,
111 items: Vec<StepItemRecord>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
113 span: Option<ByteRange>,
114}
115
116#[derive(Serialize, Deserialize)]
118#[serde(rename_all = "lowercase")]
119enum StepItemRecord {
120 Edge(EdgeReference),
121 Element(ElementPath),
122}
123
124#[derive(Serialize, Deserialize)]
126#[serde(deny_unknown_fields)]
127struct EdgeReference {
128 from: ElementPath,
129 to: ElementPath,
130 #[serde(default)]
131 ordinal: u32,
132}
133
134#[derive(Serialize, Deserialize)]
136#[serde(deny_unknown_fields)]
137struct EdgeRecord {
138 from: ElementPath,
139 to: ElementPath,
140 #[serde(default)]
141 ordinal: u32,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 label: Option<String>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 description: Option<String>,
146 #[serde(
147 default,
148 skip_serializing_if = "BTreeMap::is_empty",
149 deserialize_with = "UniqueKeyMapVisitor::deserialize"
150 )]
151 style: BTreeMap<String, String>,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 traveler: Option<Traveler>,
154 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
155 badge: bool,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 span: Option<ByteRange>,
158}
159
160struct UniqueEdgeListVisitor;
162
163struct V3RootKey;
165
166impl SourceLanguage for JsonLanguage {
167 const ERROR_CODE: &'static str = "svugy::malformed_json";
168 type Warning = NoWarning;
169
170 fn import(source_text: &str) -> JsonImport {
171 let read_document = ModelDocument::read(source_text);
172 let document = match read_document {
173 Ok(document) => document,
174 Err(serde_error) => return Self::reject_malformed(source_text, &serde_error),
175 };
176 let unchecked_model = document.into_unchecked_model();
177 let admission = unchecked_model.admit();
178 JsonImport {
179 admission,
180 errors: Vec::new(),
181 warnings: Vec::new(),
182 }
183 }
184}
185
186impl JsonLanguage {
187 fn reject_malformed(json: &str, serde_error: &serde_json::Error) -> JsonImport {
189 let withheld_admission = ModelAdmission {
190 model: None,
191 report: ModelReport::default(),
192 };
193 let line = serde_error.line();
194 let column = serde_error.column();
195 let offset = Self::byte_offset_at(json, line, column);
196 let message = serde_error.to_string();
197 let malformed_json_error = JsonError::new(message, offset..offset);
198 let errors = vec![malformed_json_error];
199 JsonImport {
200 admission: withheld_admission,
201 errors,
202 warnings: Vec::new(),
203 }
204 }
205
206 fn byte_offset_at(json: &str, line: usize, column: usize) -> usize {
208 let preceding_line_count = line.saturating_sub(1);
209 let preceding_lines = json.split_inclusive('\n').take(preceding_line_count);
210 let line_start: usize = preceding_lines.map(str::len).sum();
211 let column_offset = column.saturating_sub(1);
212 let offset = line_start + column_offset;
213 offset.min(json.len())
214 }
215}
216
217impl JsonPrinter {
218 pub fn print(model: &Model) -> String {
220 let document = ModelDocument::of(model);
221 serde_json::to_string_pretty(&document)
222 .expect("a model serializes: every map key is a string")
223 }
224}
225
226impl ModelDocument {
227 fn of(model: &Model) -> Self {
229 let view = model.view();
230 let elements = model.elements().clone();
231 let model_workflows = model.workflows().iter();
232 let workflows = model_workflows.map(WorkflowRecord::of).collect();
233 Self {
234 view,
235 elements,
236 workflows,
237 ..Self::default()
238 }
239 }
240
241 fn read(json: &str) -> Result<Self, serde_json::Error> {
243 let mut deserializer = serde_json::Deserializer::from_str(json);
244 let document = Self::deserialize(&mut deserializer)?;
245 deserializer.end()?;
246 Ok(document)
247 }
248
249 fn into_unchecked_model(self) -> UncheckedModel {
251 let records = self.workflows.into_iter();
252 let workflows = records.map(WorkflowRecord::into_workflow).collect();
253 let unchecked_model = UncheckedModel::from_parts(self.elements, workflows);
254 let Some(view) = self.view else {
255 return unchecked_model;
256 };
257 unchecked_model.with_view(view)
258 }
259}
260
261impl WorkflowRecord {
262 fn of(workflow: &Workflow) -> Self {
264 let id = workflow.id().clone();
265 let title = workflow.title().to_owned();
266 let subtitle = workflow.subtitle().map(str::to_owned);
267 let description = workflow.description().map(str::to_owned);
268 let overrides = workflow.overrides().clone();
269 let edges = workflow.edges().clone();
270 let workflow_steps = workflow.steps().iter();
271 let steps = workflow_steps.map(StepRecord::of).collect();
272 let span = workflow.span();
273 Self {
274 id,
275 title,
276 subtitle,
277 description,
278 overrides,
279 edges,
280 steps,
281 span,
282 }
283 }
284
285 fn into_workflow(self) -> Workflow {
287 let mut workflow = Workflow::new(self.id, self.title)
288 .with_overrides(self.overrides)
289 .with_edges(self.edges);
290 if let Some(subtitle) = self.subtitle {
291 workflow = workflow.with_subtitle(subtitle);
292 }
293 if let Some(description) = self.description {
294 workflow = workflow.with_description(description);
295 }
296 for step_record in self.steps {
297 let step = step_record.into_step();
298 workflow = workflow.with_step(step);
299 }
300 if let Some(span) = self.span {
301 workflow = workflow.with_span(span);
302 }
303 workflow
304 }
305}
306
307impl StepRecord {
308 fn of(step: &Step) -> Self {
310 let id = step.id().clone();
311 let title = step.title().to_owned();
312 let description = step.description().map(str::to_owned);
313 let step_items = step.items();
314 let items = step_items.map(StepItemRecord::of).collect();
315 let span = step.span();
316 Self {
317 id,
318 title,
319 description,
320 items,
321 span,
322 }
323 }
324
325 fn into_step(self) -> Step {
327 let mut step = Step::new(self.id, self.title);
328 if let Some(description) = self.description {
329 step = step.with_description(description);
330 }
331 for item_record in self.items {
332 let item = item_record.into_item();
333 step = step.with_item(item);
334 }
335 if let Some(span) = self.span {
336 step = step.with_span(span);
337 }
338 step
339 }
340}
341
342impl StepItemRecord {
343 fn of(item: &StepItem) -> Self {
345 match item {
346 StepItem::Edge(id) => {
347 let from = id.from().clone();
348 let to = id.to().clone();
349 let ordinal = id.ordinal();
350 let reference = EdgeReference { from, to, ordinal };
351 Self::Edge(reference)
352 }
353 StepItem::Element(path) => Self::Element(path.clone()),
354 }
355 }
356
357 fn into_item(self) -> StepItem {
359 match self {
360 Self::Edge(reference) => {
361 let id = EdgeId::new(reference.from, reference.to, reference.ordinal);
362 StepItem::Edge(id)
363 }
364 Self::Element(path) => StepItem::Element(path),
365 }
366 }
367}
368
369impl EdgeRecord {
370 fn serialize_all<S: Serializer>(
372 edges: &BTreeMap<EdgeId, Edge>,
373 serializer: S,
374 ) -> Result<S::Ok, S::Error> {
375 let records = edges
376 .iter()
377 .map(|(id, edge)| EdgeRecord::from_parts(id, edge));
378 serializer.collect_seq(records)
379 }
380
381 fn from_parts(id: &EdgeId, edge: &Edge) -> Self {
383 let from = id.from().clone();
384 let to = id.to().clone();
385 let ordinal = id.ordinal();
386 let label = edge.label().map(str::to_owned);
387 let description = edge.description().map(str::to_owned);
388 let style = edge.style().clone();
389 let traveler = edge.traveler();
390 let badge = edge.is_badge();
391 let span = edge.span();
392 Self {
393 from,
394 to,
395 ordinal,
396 label,
397 description,
398 style,
399 traveler,
400 badge,
401 span,
402 }
403 }
404
405 fn into_parts(self) -> (EdgeId, Edge) {
407 let id = EdgeId::new(self.from, self.to, self.ordinal);
408 let mut edge = Edge::new();
409 if let Some(label) = self.label {
410 edge = edge.with_label(label);
411 }
412 if let Some(description) = self.description {
413 edge = edge.with_description(description);
414 }
415 for (key, value) in self.style {
416 edge = edge.with_style(key, value);
417 }
418 if let Some(traveler) = self.traveler {
419 edge = edge.with_traveler(traveler);
420 }
421 if self.badge {
422 edge = edge.with_badge();
423 }
424 if let Some(span) = self.span {
425 edge = edge.with_span(span);
426 }
427 (id, edge)
428 }
429}
430
431impl UniqueEdgeListVisitor {
432 fn deserialize<'de, D: Deserializer<'de>>(
434 deserializer: D,
435 ) -> Result<BTreeMap<EdgeId, Edge>, D::Error> {
436 deserializer.deserialize_seq(Self)
437 }
438}
439
440impl<'de> Visitor<'de> for UniqueEdgeListVisitor {
441 type Value = BTreeMap<EdgeId, Edge>;
442
443 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
444 formatter.write_str("a list of edges with unique (from, to, ordinal)")
445 }
446
447 fn visit_seq<A: SeqAccess<'de>>(self, mut records: A) -> Result<Self::Value, A::Error> {
448 let mut edges = BTreeMap::new();
449 while let Some(record) = records.next_element::<EdgeRecord>()? {
450 let (id, edge) = record.into_parts();
451 let is_duplicate = edges.contains_key(&id);
452 if is_duplicate {
453 let duplicate_edge_message = format!("duplicate edge `{id}`");
454 let duplicate_edge_error = de::Error::custom(duplicate_edge_message);
455 return Err(duplicate_edge_error);
456 }
457 edges.insert(id, edge);
458 }
459 Ok(edges)
460 }
461}
462
463impl V3RootKey {
464 fn reject_title<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
466 Self::reject(
467 "`title` moved from the root into workflows: set it as the first workflow's `title`",
468 )
469 }
470
471 fn reject_subtitle<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
473 Self::reject(
474 "`subtitle` moved from the root into workflows: set it as the first workflow's `subtitle`",
475 )
476 }
477
478 fn reject_description<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
480 Self::reject(
481 "`description` moved from the root into workflows: set it as the first workflow's `description`",
482 )
483 }
484
485 fn reject_legend<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
487 Self::reject(
488 "`legend` was removed: say what each entry meant in a step's or an edge's `description`",
489 )
490 }
491
492 fn reject_edges<'de, D: Deserializer<'de>>(_value: D) -> Result<(), D::Error> {
494 Self::reject(
495 "`edges` moved from the root into workflows: list each edge in a workflow's `edges` and name it in a step's `items`",
496 )
497 }
498
499 fn reject<E: de::Error>(message: &str) -> Result<(), E> {
501 let v3_key_error = E::custom(message);
502 Err(v3_key_error)
503 }
504}