Skip to main content

svugy_parse/
json.rs

1//! Load a model from id-keyed JSON, rejecting duplicate keys and admitting it through every check, and print a model back as JSON.
2
3use std::collections::BTreeMap;
4use std::fmt;
5
6use serde::de::{self, SeqAccess, Visitor};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use svugy_model::builder::ModelBuilder;
9use svugy_model::legend::Legend;
10use svugy_model::model::{ByteRange, Edge, EdgeId, Element, Model, Traveler};
11use svugy_model::path::ElementPath;
12use svugy_model::unique_key_map::UniqueKeyMapVisitor;
13use svugy_model::validate::UncheckedModel;
14
15use crate::diagnostic::{Import, NoWarning, SourceError, SourceLanguage};
16
17/// Mark JSON as a source language: its one error is the text not parsing as a model document, and it raises no warnings.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct JsonLanguage;
20
21/// Report JSON that does not parse as a model document, before any model check runs.
22pub type JsonError = SourceError<JsonLanguage>;
23
24/// Collect the import of a JSON model: the model's admission and the malformed-JSON error, if any.
25pub type JsonImport = Import<JsonLanguage>;
26
27/// Print a model as pretty-printed, deterministic JSON, which says every model.
28pub struct JsonPrinter;
29
30/// Shape a model as its JSON file: the diagram's title, subtitle, description and legend, elements keyed by path, edges listed with their endpoints and ordinal.
31#[derive(Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33struct ModelDocument {
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    title: Option<String>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    subtitle: Option<String>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    description: Option<String>,
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    legend: Option<Legend>,
42    #[serde(default, deserialize_with = "UniqueKeyMapVisitor::deserialize")]
43    elements: BTreeMap<ElementPath, Element>,
44    #[serde(
45        default,
46        serialize_with = "EdgeRecord::serialize_all",
47        deserialize_with = "UniqueEdgeListVisitor::deserialize"
48    )]
49    edges: BTreeMap<EdgeId, Edge>,
50}
51
52/// Record one edge as written in JSON.
53#[derive(Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55struct EdgeRecord {
56    from: ElementPath,
57    to: ElementPath,
58    #[serde(default)]
59    ordinal: u32,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    label: Option<String>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    description: Option<String>,
64    #[serde(
65        default,
66        skip_serializing_if = "BTreeMap::is_empty",
67        deserialize_with = "UniqueKeyMapVisitor::deserialize"
68    )]
69    style: BTreeMap<String, String>,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    traveler: Option<Traveler>,
72    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
73    badge: bool,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    span: Option<ByteRange>,
76}
77
78/// Collect edge records, rejecting an id already seen.
79struct UniqueEdgeListVisitor;
80
81impl SourceLanguage for JsonLanguage {
82    const ERROR_CODE: &'static str = "svugy::malformed_json";
83    type Warning = NoWarning;
84
85    fn import(source_text: &str) -> JsonImport {
86        let read_document = ModelDocument::read(source_text);
87        let document = match read_document {
88            Ok(document) => document,
89            Err(serde_error) => return Self::reject_malformed(source_text, &serde_error),
90        };
91        let unchecked_model = document.into_unchecked_model();
92        let admission = unchecked_model.admit();
93        JsonImport {
94            admission,
95            errors: Vec::new(),
96            warnings: Vec::new(),
97        }
98    }
99}
100
101impl JsonLanguage {
102    /// Withhold the model behind one error at where serde stopped reading `json`, its message serde's own.
103    fn reject_malformed(json: &str, serde_error: &serde_json::Error) -> JsonImport {
104        let empty_builder = ModelBuilder::new();
105        let empty_admission = empty_builder.build();
106        let line = serde_error.line();
107        let column = serde_error.column();
108        let offset = Self::byte_offset_at(json, line, column);
109        let message = serde_error.to_string();
110        let malformed_json_error = JsonError::new(message, offset..offset);
111        let errors = vec![malformed_json_error];
112        JsonImport {
113            admission: empty_admission,
114            errors,
115            warnings: Vec::new(),
116        }
117    }
118
119    /// Turn serde's 1-based `line` and byte `column` into a byte offset into `json`, clamped to its end; line 0 means no position.
120    fn byte_offset_at(json: &str, line: usize, column: usize) -> usize {
121        let preceding_line_count = line.saturating_sub(1);
122        let preceding_lines = json.split_inclusive('\n').take(preceding_line_count);
123        let line_start: usize = preceding_lines.map(str::len).sum();
124        let column_offset = column.saturating_sub(1);
125        let offset = line_start + column_offset;
126        offset.min(json.len())
127    }
128}
129
130impl JsonPrinter {
131    /// Print `model` as pretty-printed JSON, elements keyed by path and edges listed in edge-id order.
132    pub fn print(model: &Model) -> String {
133        let document = ModelDocument::of(model);
134        serde_json::to_string_pretty(&document)
135            .expect("a model serializes: every map key is a string")
136    }
137}
138
139impl ModelDocument {
140    /// Copy `model` into the document its JSON file holds.
141    fn of(model: &Model) -> Self {
142        let title = model.title().map(str::to_owned);
143        let subtitle = model.subtitle().map(str::to_owned);
144        let description = model.description().map(str::to_owned);
145        let legend = model.legend().cloned();
146        let elements = model.elements().clone();
147        let edges = model.edges().clone();
148        Self {
149            title,
150            subtitle,
151            description,
152            legend,
153            elements,
154            edges,
155        }
156    }
157
158    /// Read `json` as one model document and nothing after it.
159    fn read(json: &str) -> Result<Self, serde_json::Error> {
160        let mut deserializer = serde_json::Deserializer::from_str(json);
161        let document = Self::deserialize(&mut deserializer)?;
162        deserializer.end()?;
163        Ok(document)
164    }
165
166    /// Hold the document's model unchecked, as written, ordinals included.
167    fn into_unchecked_model(self) -> UncheckedModel {
168        let Self {
169            title,
170            subtitle,
171            description,
172            legend,
173            elements,
174            edges,
175        } = self;
176        let unchecked_model = UncheckedModel::from_parts(title, subtitle, legend, elements, edges);
177        match description {
178            Some(description) => unchecked_model.with_description(description),
179            None => unchecked_model,
180        }
181    }
182}
183
184impl EdgeRecord {
185    /// Write the edge map as a list of records, in edge-id order.
186    fn serialize_all<S: Serializer>(
187        edges: &BTreeMap<EdgeId, Edge>,
188        serializer: S,
189    ) -> Result<S::Ok, S::Error> {
190        let records = edges
191            .iter()
192            .map(|(id, edge)| EdgeRecord::from_parts(id, edge));
193        serializer.collect_seq(records)
194    }
195
196    /// Record the edge `id` names, as JSON writes it.
197    fn from_parts(id: &EdgeId, edge: &Edge) -> Self {
198        let from = id.from().clone();
199        let to = id.to().clone();
200        let ordinal = id.ordinal();
201        let label = edge.label().map(str::to_owned);
202        let description = edge.description().map(str::to_owned);
203        let style = edge.style().clone();
204        let traveler = edge.traveler();
205        let badge = edge.is_badge();
206        let span = edge.span();
207        Self {
208            from,
209            to,
210            ordinal,
211            label,
212            description,
213            style,
214            traveler,
215            badge,
216            span,
217        }
218    }
219
220    /// Split the record into the id and the edge it describes.
221    fn into_parts(self) -> (EdgeId, Edge) {
222        let id = EdgeId::new(self.from, self.to, self.ordinal);
223        let mut edge = Edge::new();
224        if let Some(label) = self.label {
225            edge = edge.with_label(label);
226        }
227        if let Some(description) = self.description {
228            edge = edge.with_description(description);
229        }
230        for (key, value) in self.style {
231            edge = edge.with_style(key, value);
232        }
233        if let Some(traveler) = self.traveler {
234            edge = edge.with_traveler(traveler);
235        }
236        if self.badge {
237            edge = edge.with_badge();
238        }
239        if let Some(span) = self.span {
240            edge = edge.with_span(span);
241        }
242        (id, edge)
243    }
244}
245
246impl UniqueEdgeListVisitor {
247    /// Read the edge list into a map keyed by edge id, failing on a repeated id.
248    fn deserialize<'de, D: Deserializer<'de>>(
249        deserializer: D,
250    ) -> Result<BTreeMap<EdgeId, Edge>, D::Error> {
251        deserializer.deserialize_seq(Self)
252    }
253}
254
255impl<'de> Visitor<'de> for UniqueEdgeListVisitor {
256    type Value = BTreeMap<EdgeId, Edge>;
257
258    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259        formatter.write_str("a list of edges with unique (from, to, ordinal)")
260    }
261
262    fn visit_seq<A: SeqAccess<'de>>(self, mut records: A) -> Result<Self::Value, A::Error> {
263        let mut edges = BTreeMap::new();
264        while let Some(record) = records.next_element::<EdgeRecord>()? {
265            let (id, edge) = record.into_parts();
266            let is_duplicate = edges.contains_key(&id);
267            if is_duplicate {
268                let duplicate_edge_message = format!("duplicate edge `{id}`");
269                let duplicate_edge_error = de::Error::custom(duplicate_edge_message);
270                return Err(duplicate_edge_error);
271            }
272            edges.insert(id, edge);
273        }
274        Ok(edges)
275    }
276}