use std::collections::BTreeMap;
use std::fmt;
use serde::de::{self, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use svugy_model::builder::ModelBuilder;
use svugy_model::legend::Legend;
use svugy_model::model::{ByteRange, Edge, EdgeId, Element, Model, Traveler};
use svugy_model::path::ElementPath;
use svugy_model::unique_key_map::UniqueKeyMapVisitor;
use svugy_model::validate::UncheckedModel;
use crate::diagnostic::{Import, NoWarning, SourceError, SourceLanguage};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JsonLanguage;
pub type JsonError = SourceError<JsonLanguage>;
pub type JsonImport = Import<JsonLanguage>;
pub struct JsonPrinter;
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct ModelDocument {
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
subtitle: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
legend: Option<Legend>,
#[serde(default, deserialize_with = "UniqueKeyMapVisitor::deserialize")]
elements: BTreeMap<ElementPath, Element>,
#[serde(
default,
serialize_with = "EdgeRecord::serialize_all",
deserialize_with = "UniqueEdgeListVisitor::deserialize"
)]
edges: BTreeMap<EdgeId, Edge>,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct EdgeRecord {
from: ElementPath,
to: ElementPath,
#[serde(default)]
ordinal: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "UniqueKeyMapVisitor::deserialize"
)]
style: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
traveler: Option<Traveler>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
badge: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
span: Option<ByteRange>,
}
struct UniqueEdgeListVisitor;
impl SourceLanguage for JsonLanguage {
const ERROR_CODE: &'static str = "svugy::malformed_json";
type Warning = NoWarning;
fn import(source_text: &str) -> JsonImport {
let read_document = ModelDocument::read(source_text);
let document = match read_document {
Ok(document) => document,
Err(serde_error) => return Self::reject_malformed(source_text, &serde_error),
};
let unchecked_model = document.into_unchecked_model();
let admission = unchecked_model.admit();
JsonImport {
admission,
errors: Vec::new(),
warnings: Vec::new(),
}
}
}
impl JsonLanguage {
fn reject_malformed(json: &str, serde_error: &serde_json::Error) -> JsonImport {
let empty_builder = ModelBuilder::new();
let empty_admission = empty_builder.build();
let line = serde_error.line();
let column = serde_error.column();
let offset = Self::byte_offset_at(json, line, column);
let message = serde_error.to_string();
let malformed_json_error = JsonError::new(message, offset..offset);
let errors = vec![malformed_json_error];
JsonImport {
admission: empty_admission,
errors,
warnings: Vec::new(),
}
}
fn byte_offset_at(json: &str, line: usize, column: usize) -> usize {
let preceding_line_count = line.saturating_sub(1);
let preceding_lines = json.split_inclusive('\n').take(preceding_line_count);
let line_start: usize = preceding_lines.map(str::len).sum();
let column_offset = column.saturating_sub(1);
let offset = line_start + column_offset;
offset.min(json.len())
}
}
impl JsonPrinter {
pub fn print(model: &Model) -> String {
let document = ModelDocument::of(model);
serde_json::to_string_pretty(&document)
.expect("a model serializes: every map key is a string")
}
}
impl ModelDocument {
fn of(model: &Model) -> Self {
let title = model.title().map(str::to_owned);
let subtitle = model.subtitle().map(str::to_owned);
let description = model.description().map(str::to_owned);
let legend = model.legend().cloned();
let elements = model.elements().clone();
let edges = model.edges().clone();
Self {
title,
subtitle,
description,
legend,
elements,
edges,
}
}
fn read(json: &str) -> Result<Self, serde_json::Error> {
let mut deserializer = serde_json::Deserializer::from_str(json);
let document = Self::deserialize(&mut deserializer)?;
deserializer.end()?;
Ok(document)
}
fn into_unchecked_model(self) -> UncheckedModel {
let Self {
title,
subtitle,
description,
legend,
elements,
edges,
} = self;
let unchecked_model = UncheckedModel::from_parts(title, subtitle, legend, elements, edges);
match description {
Some(description) => unchecked_model.with_description(description),
None => unchecked_model,
}
}
}
impl EdgeRecord {
fn serialize_all<S: Serializer>(
edges: &BTreeMap<EdgeId, Edge>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let records = edges
.iter()
.map(|(id, edge)| EdgeRecord::from_parts(id, edge));
serializer.collect_seq(records)
}
fn from_parts(id: &EdgeId, edge: &Edge) -> Self {
let from = id.from().clone();
let to = id.to().clone();
let ordinal = id.ordinal();
let label = edge.label().map(str::to_owned);
let description = edge.description().map(str::to_owned);
let style = edge.style().clone();
let traveler = edge.traveler();
let badge = edge.is_badge();
let span = edge.span();
Self {
from,
to,
ordinal,
label,
description,
style,
traveler,
badge,
span,
}
}
fn into_parts(self) -> (EdgeId, Edge) {
let id = EdgeId::new(self.from, self.to, self.ordinal);
let mut edge = Edge::new();
if let Some(label) = self.label {
edge = edge.with_label(label);
}
if let Some(description) = self.description {
edge = edge.with_description(description);
}
for (key, value) in self.style {
edge = edge.with_style(key, value);
}
if let Some(traveler) = self.traveler {
edge = edge.with_traveler(traveler);
}
if self.badge {
edge = edge.with_badge();
}
if let Some(span) = self.span {
edge = edge.with_span(span);
}
(id, edge)
}
}
impl UniqueEdgeListVisitor {
fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<EdgeId, Edge>, D::Error> {
deserializer.deserialize_seq(Self)
}
}
impl<'de> Visitor<'de> for UniqueEdgeListVisitor {
type Value = BTreeMap<EdgeId, Edge>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a list of edges with unique (from, to, ordinal)")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut records: A) -> Result<Self::Value, A::Error> {
let mut edges = BTreeMap::new();
while let Some(record) = records.next_element::<EdgeRecord>()? {
let (id, edge) = record.into_parts();
let is_duplicate = edges.contains_key(&id);
if is_duplicate {
let duplicate_edge_message = format!("duplicate edge `{id}`");
let duplicate_edge_error = de::Error::custom(duplicate_edge_message);
return Err(duplicate_edge_error);
}
edges.insert(id, edge);
}
Ok(edges)
}
}