1use crate::json_escape;
5use std::fmt::Write;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CellFailure {
11 pub from: usize,
12 pub to: usize,
13 pub actual: String,
14}
15
16impl CellFailure {
17 pub fn new(from: usize, to: usize, actual: impl Into<String>) -> CellFailure {
18 CellFailure {
19 from,
20 to,
21 actual: actual.into(),
22 }
23 }
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Status {
29 Passed,
30 Failed,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct AnchorRange {
39 pub from: usize,
40 pub to: usize,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct ExampleFailure {
50 pub line: i64,
51 pub message: String,
52 pub stack: String,
53 pub cells: Option<Vec<CellFailure>>,
54 pub anchor: Option<AnchorRange>,
55 pub doc_path: Option<String>,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct ReferencedDocument {
67 pub path: String,
68 pub source_hash: String,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct ExampleResult {
74 pub name: String,
75 pub status: Status,
76 pub lines: Vec<usize>,
77 pub failure: Option<ExampleFailure>,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct OathResults {
83 pub version: u32,
84 pub oath_path: String,
85 pub source_hash: String,
86 pub documents: Vec<ReferencedDocument>,
90 pub examples: Vec<ExampleResult>,
91}
92
93pub fn to_wire_json(results: &OathResults) -> String {
101 let mut out = String::new();
102 out.push_str("{\n");
103 field(&mut out, 1, "version", &results.version.to_string(), true);
104 string_field(&mut out, 1, "oathPath", &results.oath_path, true);
105 string_field(&mut out, 1, "sourceHash", &results.source_hash, true);
106 if !results.documents.is_empty() {
107 indent(&mut out, 1);
108 out.push_str("\"documents\": [\n");
109 for (i, doc) in results.documents.iter().enumerate() {
110 indent(&mut out, 2);
111 out.push_str("{\n");
112 string_field(&mut out, 3, "path", &doc.path, true);
113 string_field(&mut out, 3, "sourceHash", &doc.source_hash, false);
114 out.push('\n');
115 indent(&mut out, 2);
116 out.push('}');
117 if i + 1 < results.documents.len() {
118 out.push(',');
119 }
120 out.push('\n');
121 }
122 indent(&mut out, 1);
123 out.push_str("],\n");
124 }
125 indent(&mut out, 1);
126 out.push_str("\"examples\": ");
127 write_examples(&mut out, &results.examples, 1);
128 out.push('\n');
129 out.push('}');
130 out
131}
132
133fn write_examples(out: &mut String, examples: &[ExampleResult], depth: usize) {
134 if examples.is_empty() {
135 out.push_str("[]");
136 return;
137 }
138 out.push_str("[\n");
139 for (i, example) in examples.iter().enumerate() {
140 indent(out, depth + 1);
141 out.push_str("{\n");
142 string_field(out, depth + 2, "name", &example.name, true);
143 let status = match example.status {
144 Status::Passed => "passed",
145 Status::Failed => "failed",
146 };
147 string_field(out, depth + 2, "status", status, true);
148 indent(out, depth + 2);
149 out.push_str("\"lines\": ");
150 write_ints(out, &example.lines, depth + 2);
151 match &example.failure {
152 Some(failure) => {
153 out.push_str(",\n");
154 indent(out, depth + 2);
155 out.push_str("\"failure\": ");
156 write_failure(out, failure, depth + 2);
157 out.push('\n');
158 }
159 None => out.push('\n'),
160 }
161 indent(out, depth + 1);
162 out.push('}');
163 if i + 1 < examples.len() {
164 out.push(',');
165 }
166 out.push('\n');
167 }
168 indent(out, depth);
169 out.push(']');
170}
171
172fn write_failure(out: &mut String, failure: &ExampleFailure, depth: usize) {
173 out.push_str("{\n");
174 field(out, depth + 1, "line", &failure.line.to_string(), true);
175 string_field(out, depth + 1, "message", &failure.message, true);
176 let has_more =
177 failure.cells.is_some() || failure.anchor.is_some() || failure.doc_path.is_some();
178 string_field(out, depth + 1, "stack", &failure.stack, has_more);
179 if let Some(cells) = &failure.cells {
180 indent(out, depth + 1);
181 out.push_str("\"cells\": [\n");
182 for (i, cell) in cells.iter().enumerate() {
183 indent(out, depth + 2);
184 out.push_str("{\n");
185 field(out, depth + 3, "from", &cell.from.to_string(), true);
186 field(out, depth + 3, "to", &cell.to.to_string(), true);
187 string_field(out, depth + 3, "actual", &cell.actual, false);
188 indent(out, depth + 2);
189 out.push('}');
190 if i + 1 < cells.len() {
191 out.push(',');
192 }
193 out.push('\n');
194 }
195 indent(out, depth + 1);
196 out.push(']');
197 out.push_str(if failure.anchor.is_some() || failure.doc_path.is_some() {
198 ",\n"
199 } else {
200 "\n"
201 });
202 }
203 if let Some(anchor) = &failure.anchor {
204 indent(out, depth + 1);
205 out.push_str("\"anchor\": {\n");
206 field(out, depth + 2, "from", &anchor.from.to_string(), true);
207 field(out, depth + 2, "to", &anchor.to.to_string(), false);
208 indent(out, depth + 1);
209 out.push('}');
210 out.push_str(if failure.doc_path.is_some() {
211 ",\n"
212 } else {
213 "\n"
214 });
215 }
216 if let Some(doc_path) = &failure.doc_path {
217 string_field(out, depth + 1, "docPath", doc_path, false);
218 out.push('\n');
219 }
220 indent(out, depth);
221 out.push('}');
222}
223
224fn write_ints(out: &mut String, values: &[usize], depth: usize) {
225 if values.is_empty() {
226 out.push_str("[]");
227 return;
228 }
229 out.push_str("[\n");
230 for (i, value) in values.iter().enumerate() {
231 indent(out, depth + 1);
232 let _ = write!(out, "{value}");
233 if i + 1 < values.len() {
234 out.push(',');
235 }
236 out.push('\n');
237 }
238 indent(out, depth);
239 out.push(']');
240}
241
242fn field(out: &mut String, depth: usize, key: &str, raw_value: &str, comma: bool) {
243 indent(out, depth);
244 let _ = write!(out, "\"{key}\": {raw_value}");
245 out.push_str(if comma { ",\n" } else { "\n" });
246}
247
248fn string_field(out: &mut String, depth: usize, key: &str, value: &str, comma: bool) {
249 indent(out, depth);
250 let _ = write!(out, "\"{key}\": ");
251 json_escape::write_string(out, value);
252 out.push_str(if comma { ",\n" } else { "\n" });
253}
254
255fn indent(out: &mut String, depth: usize) {
256 for _ in 0..depth {
257 out.push_str(" ");
258 }
259}