a3s_box_core/compose/
diagnostic.rs1use std::error::Error;
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
10pub enum ComposeDiagnosticCode {
11 #[serde(rename = "compose.syntax")]
13 Syntax,
14 #[serde(rename = "compose.interpolation")]
16 Interpolation,
17 #[serde(rename = "compose.unsupported_field")]
19 UnsupportedField,
20 #[serde(rename = "compose.unsupported_value")]
22 UnsupportedValue,
23 #[serde(rename = "compose.invalid_value")]
25 InvalidValue,
26}
27
28impl ComposeDiagnosticCode {
29 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::Syntax => "compose.syntax",
33 Self::Interpolation => "compose.interpolation",
34 Self::UnsupportedField => "compose.unsupported_field",
35 Self::UnsupportedValue => "compose.unsupported_value",
36 Self::InvalidValue => "compose.invalid_value",
37 }
38 }
39}
40
41impl fmt::Display for ComposeDiagnosticCode {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 formatter.write_str(self.as_str())
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct ComposeDiagnostic {
54 pub code: ComposeDiagnosticCode,
56 pub path: String,
58 pub message: String,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub line: Option<usize>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub column: Option<usize>,
66}
67
68impl ComposeDiagnostic {
69 pub(super) fn new(
70 code: ComposeDiagnosticCode,
71 path: impl Into<String>,
72 message: impl Into<String>,
73 ) -> Self {
74 Self {
75 code,
76 path: path.into(),
77 message: message.into(),
78 line: None,
79 column: None,
80 }
81 }
82
83 pub(super) fn with_location(mut self, line: usize, column: usize) -> Self {
84 self.line = Some(line);
85 self.column = Some(column);
86 self
87 }
88
89 pub(super) fn unsupported_field(path: impl Into<String>, field: &str) -> Self {
90 Self::new(
91 ComposeDiagnosticCode::UnsupportedField,
92 path,
93 format!("unsupported Compose field {field:?}"),
94 )
95 }
96}
97
98impl fmt::Display for ComposeDiagnostic {
99 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(
101 formatter,
102 "{} at {}: {}",
103 self.code, self.path, self.message
104 )?;
105 if let (Some(line), Some(column)) = (self.line, self.column) {
106 write!(formatter, " (line {line}, column {column})")?;
107 }
108 Ok(())
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ComposeNormalizationError {
115 diagnostics: Vec<ComposeDiagnostic>,
116}
117
118impl ComposeNormalizationError {
119 pub(super) fn new(mut diagnostics: Vec<ComposeDiagnostic>) -> Self {
120 diagnostics.sort_by(|left, right| {
121 left.path
122 .cmp(&right.path)
123 .then_with(|| left.code.cmp(&right.code))
124 .then_with(|| left.message.cmp(&right.message))
125 });
126 diagnostics.dedup();
127 Self { diagnostics }
128 }
129
130 pub(super) fn one(diagnostic: ComposeDiagnostic) -> Self {
131 Self::new(vec![diagnostic])
132 }
133
134 pub fn diagnostics(&self) -> &[ComposeDiagnostic] {
136 &self.diagnostics
137 }
138
139 pub fn into_diagnostics(self) -> Vec<ComposeDiagnostic> {
141 self.diagnostics
142 }
143}
144
145impl fmt::Display for ComposeNormalizationError {
146 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self.diagnostics.as_slice() {
148 [] => formatter.write_str("Compose normalization failed"),
149 [diagnostic] => diagnostic.fmt(formatter),
150 diagnostics => {
151 write!(
152 formatter,
153 "Compose normalization failed with {} diagnostics: ",
154 diagnostics.len()
155 )?;
156 for (index, diagnostic) in diagnostics.iter().enumerate() {
157 if index > 0 {
158 formatter.write_str("; ")?;
159 }
160 diagnostic.fmt(formatter)?;
161 }
162 Ok(())
163 }
164 }
165 }
166}
167
168impl Error for ComposeNormalizationError {}
169
170pub(super) fn pointer_segment(value: &str) -> String {
171 value.replace('~', "~0").replace('/', "~1")
172}
173
174pub(super) fn child_path(parent: &str, child: &str) -> String {
175 let child = pointer_segment(child);
176 if parent == "/" {
177 format!("/{child}")
178 } else {
179 format!("{parent}/{child}")
180 }
181}