1use crate::JsonType;
2use crate::expect_core::Context;
3use crate::expect_core::ExpectOpError;
4use crate::internals::objects::ArrayObject;
5use crate::internals::objects::ValueObject;
6use crate::internals::objects::ValueTypeObject;
7use crate::internals::utils::is_unquotable_js_identifier;
8use serde_json::Error as SerdeJsonError;
9use serde_json::Value;
10use std::fmt::Write;
11use thiserror::Error;
12
13pub type ExpectJsonResult<V> = Result<V, ExpectJsonError>;
14
15#[derive(Debug, Error)]
16pub enum ExpectJsonError {
17 #[error("Failed to serialise expected value to Json")]
18 FailedToSerialiseExpected(#[source] SerdeJsonError),
19
20 #[error("Failed to serialise other value to Json")]
21 FailedToSerialiseReceived(#[source] SerdeJsonError),
22
23 #[error(
24 "Json {} at {context} are different types:
25 expected {expected}
26 received {received}",
27 value_or_number_type_name(received, expected)
28 )]
29 DifferentTypes {
30 context: Context<'static>,
31 received: ValueTypeObject,
32 expected: ValueTypeObject,
33 },
34
35 #[error(
36 "Json {json_type}s at {context} are not equal:
37 expected {expected}
38 received {received}"
39 )]
40 DifferentValues {
41 context: Context<'static>,
42 json_type: JsonType,
43 received: ValueObject,
44 expected: ValueObject,
45 },
46
47 #[error(
48 "Json is not null at {context}, expected null:
49 expected null
50 received {received}"
51 )]
52 ReceivedIsNotNull {
53 context: Context<'static>,
54 received: ValueTypeObject,
55 },
56
57 #[error(
58 "Json null received at {context}, expected not null:
59 expected {expected}
60 received null"
61 )]
62 ReceivedIsNull {
63 context: Context<'static>,
64 expected: ValueTypeObject,
65 },
66
67 #[error(
68 "Json objects at {context} are not equal:
69 expected field '{expected_key}',
70 but it was not found"
71 )]
72 ObjectKeyMissing {
73 context: Context<'static>,
74 expected_key: String,
75 },
76
77 #[error(
78 "Json arrays at {context} contain different types:
79 expected {expected}
80 full array {expected_full_array}
81 received {received}
82 full array {received_array}"
83 )]
84 ArrayContainsDifferentTypes {
85 context: Context<'static>,
86 received: Box<ValueTypeObject>,
87 received_array: Box<ArrayObject>,
88 expected: Box<ValueTypeObject>,
89 expected_full_array: Box<ArrayObject>,
90 },
91
92 #[error(
93 "Json {json_type}s at {context} are not equal:
94 expected {expected}
95 full array {expected_full_array}
96 received {received}
97 full array {received_array}"
98 )]
99 ArrayContainsDifferentValues {
100 context: Context<'static>,
101 json_type: JsonType,
102 received: Box<ValueObject>,
103 received_array: Box<ArrayObject>,
104 expected: Box<ValueObject>,
105 expected_full_array: Box<ArrayObject>,
106 },
107
108 #[error(
109 "Json arrays at {context} are not equal:
110 expected {expected_array}
111 received {received_array}"
112 )]
113 ArrayMissingInMiddle {
114 context: Context<'static>,
115 received_array: ArrayObject,
116 expected_array: ArrayObject,
117 },
122
123 #[error(
124 "Json arrays at {context} are not equal:
125 expected {expected_array}
126 received {received_array}"
127 )]
128 ArrayValuesAreDifferent {
129 context: Context<'static>,
130 received_array: ArrayObject,
131 expected_array: ArrayObject,
132 },
133
134 #[error(
135 "Json arrays at {context} are not equal:
136 expected array index at '{expected_index}',
137 but it was not found"
138 )]
139 ArrayIndexMissing {
140 context: Context<'static>,
141 expected_index: usize,
142 },
143
144 #[error(
145 "Json arrays at {context} are not equal, missing {} {} at the end:
146 expected {expected_array}
147 received {received_array}
148 missing {missing_in_received}"
149 , missing_in_received.len(), pluralise_item_word(missing_in_received.len())
150 )]
151 ArrayMissingAtEnd {
152 context: Context<'static>,
153 expected_array: ArrayObject,
154 received_array: ArrayObject,
155 missing_in_received: ArrayObject,
156 },
157
158 #[error(
159 "Json arrays at {context} are not equal, missing {} {} from the start:
160 expected {expected_array}
161 received {received_array}
162 missing {missing_in_received}"
163 , missing_in_received.len(), pluralise_item_word(missing_in_received.len())
164 )]
165 ArrayMissingAtStart {
166 context: Context<'static>,
167 expected_array: ArrayObject,
168 received_array: ArrayObject,
169 missing_in_received: ArrayObject,
170 },
171
172 #[error(
173 "Json arrays at {context} are not equal, received {} extra {} at the end:
174 expected {expected_array}
175 received {received_array}
176 extra {extra_in_received}"
177 , extra_in_received.len(), pluralise_item_word(extra_in_received.len())
178 )]
179 ArrayExtraAtEnd {
180 context: Context<'static>,
181 expected_array: ArrayObject,
182 received_array: ArrayObject,
183 extra_in_received: ArrayObject,
184 },
185
186 #[error(
187 "Json arrays at {context} are not equal, received {} extra {} at the start:
188 expected {expected_array}
189 received {received_array}
190 extra {extra_in_received}"
191 , extra_in_received.len(), pluralise_item_word(extra_in_received.len())
192 )]
193 ArrayExtraAtStart {
194 context: Context<'static>,
195 expected_array: ArrayObject,
196 received_array: ArrayObject,
197 extra_in_received: ArrayObject,
198 },
199
200 #[error(
201 r#"Json object at {context} has extra field "{received_extra_field}":
202 expected {expected_obj}
203 received {received_obj}"#
204 )]
205 ObjectReceivedHasExtraKey {
206 context: Context<'static>,
207 received_extra_field: String,
208 received_obj: ValueObject,
209 expected_obj: ValueObject,
210 },
211
212 #[error(
213 r#"Json object at {context} has many extra fields over expected:
214 expected {expected_obj}
215 received {received_obj}
216
217 extra fields in received:
218{}"#,
219 format_extra_fields(received_extra_fields)
220 )]
221 ObjectReceivedHasExtraKeys {
222 context: Context<'static>,
223 received_extra_fields: Vec<String>,
224 received_obj: ValueObject,
225 expected_obj: ValueObject,
226 },
227
228 #[error("{source}")]
229 ExpectOpError {
230 #[from]
231 source: ExpectOpError,
232 },
233}
234
235impl ExpectJsonError {
236 pub(crate) fn array_index_missing<'a>(
237 context: &mut Context<'a>,
238 source_error: Self,
239 received_array: &'a [Value],
240 expected_array: &'a [Value],
241 ) -> Self {
242 match source_error {
243 Self::DifferentValues {
244 context: source_error_context,
245 json_type,
246 received,
247 expected,
248 } => {
249 if source_error_context != *context {
251 return Self::DifferentValues {
252 context: source_error_context,
253 json_type,
254 received,
255 expected,
256 };
257 }
258
259 Self::ArrayContainsDifferentValues {
260 context: source_error_context,
261 json_type,
262 received: Box::new(received),
263 received_array: Box::new(ArrayObject::from(received_array.to_owned())),
264 expected: Box::new(expected),
265 expected_full_array: Box::new(ArrayObject::from(expected_array.to_owned())),
266 }
267 }
268 Self::DifferentTypes {
269 context: source_error_context,
270 received,
271 expected,
272 } => {
273 if source_error_context != *context {
275 return Self::DifferentTypes {
276 context: source_error_context,
277 received,
278 expected,
279 };
280 }
281
282 Self::ArrayContainsDifferentTypes {
283 context: source_error_context,
284 received: Box::new(received),
285 received_array: Box::new(ArrayObject::from(received_array.to_owned())),
286 expected: Box::new(expected),
287 expected_full_array: Box::new(ArrayObject::from(expected_array.to_owned())),
288 }
289 }
290
291 _ => source_error,
293 }
294 }
295}
296
297fn pluralise_item_word(len: usize) -> &'static str {
298 if len == 1 { "item" } else { "items" }
299}
300
301fn value_or_number_type_name(left: &ValueTypeObject, right: &ValueTypeObject) -> &'static str {
302 if left.is_number() && right.is_number() {
303 "numbers"
304 } else {
305 "values"
306 }
307}
308
309fn format_extra_fields(received_extra_fields: &[String]) -> String {
310 let mut output = String::new();
311
312 for field in received_extra_fields {
313 if is_unquotable_js_identifier(field) {
314 let _ = writeln!(output, " {field},");
315 } else {
316 let _ = writeln!(output, r#" "{field}","#);
317 }
318 }
319
320 output
321}