json_syntax/
try_from.rs

1use core::fmt;
2use std::{collections::BTreeMap, marker::PhantomData, str::FromStr};
3
4use crate::{array::JsonArray, code_map::Mapped, CodeMap, Kind, KindSet, Object, Value};
5
6/// Conversion from JSON syntax, with code mapping info.
7///
8/// This trait is very similar to [`TryFrom<Value>`] but also passes code
9/// code mapping info to the conversion function.
10pub trait TryFromJson: Sized {
11	/// Error that may be returned by the conversion function.
12	type Error;
13
14	/// Tries to convert the given JSON value into `Self`, using the given
15	/// `code_map`.
16	///
17	/// It is assumed that the offset of `value` in the code map is `0`, for
18	/// instance if it is the output of a [`Parse`](crate::Parse) trait
19	/// function.
20	fn try_from_json(value: &Value, code_map: &CodeMap) -> Result<Self, Self::Error> {
21		Self::try_from_json_at(value, code_map, 0)
22	}
23
24	/// Tries to convert the given JSON value into `Self`, using the given
25	/// `code_map` and the offset of `value` in the code map.
26	///
27	/// Note to implementors: use the [`JsonArray::iter_mapped`] and
28	/// [`Object::iter_mapped`] methods to visit arrays and objects while
29	/// keeping track of the code map offset of each visited item.
30	fn try_from_json_at(
31		value: &Value,
32		code_map: &CodeMap,
33		offset: usize,
34	) -> Result<Self, Self::Error>;
35}
36
37impl<T: TryFromJson> TryFromJson for Box<T> {
38	type Error = T::Error;
39
40	fn try_from_json_at(
41		json: &Value,
42		code_map: &CodeMap,
43		offset: usize,
44	) -> Result<Self, Self::Error> {
45		T::try_from_json_at(json, code_map, offset).map(Box::new)
46	}
47}
48
49impl<T: TryFromJson> TryFromJson for Option<T> {
50	type Error = T::Error;
51
52	fn try_from_json_at(
53		json: &Value,
54		code_map: &CodeMap,
55		offset: usize,
56	) -> Result<Self, Self::Error> {
57		match json {
58			Value::Null => Ok(None),
59			other => T::try_from_json_at(other, code_map, offset).map(Some),
60		}
61	}
62}
63
64/// Conversion from JSON syntax object, with code mapping info.
65///
66/// This trait is very similar to [`TryFrom<Object>`] but also passes code
67/// code mapping info to the conversion function.
68pub trait TryFromJsonObject: Sized {
69	type Error;
70
71	/// Tries to convert the given JSON object into `Self`, using the given
72	/// `code_map`.
73	///
74	/// It is assumed that the offset of `object` in the code map is `0`, for
75	/// instance if it is the output of a [`Parse`](crate::Parse) trait
76	/// function.
77	fn try_from_json_object(object: &Object, code_map: &CodeMap) -> Result<Self, Self::Error> {
78		Self::try_from_json_object_at(object, code_map, 0)
79	}
80
81	/// Tries to convert the given JSON object into `Self`, using the given
82	/// `code_map` and the offset of `object` in the code map.
83	///
84	/// Note to implementors: use the [`JsonArray::iter_mapped`] and
85	/// [`Object::iter_mapped`] methods to visit arrays and objects while
86	/// keeping track of the code map offset of each visited item.
87	fn try_from_json_object_at(
88		object: &Object,
89		code_map: &CodeMap,
90		offset: usize,
91	) -> Result<Self, Self::Error>;
92}
93
94impl<T: TryFromJsonObject> TryFromJsonObject for Box<T> {
95	type Error = T::Error;
96
97	fn try_from_json_object_at(
98		object: &Object,
99		code_map: &CodeMap,
100		offset: usize,
101	) -> Result<Self, Self::Error> {
102		T::try_from_json_object_at(object, code_map, offset).map(Box::new)
103	}
104}
105
106/// Unexpected JSON value kind error.
107///
108/// This error may be returned by [`TryFromJson`] and [`TryFromJsonObject`]
109/// when trying to convert a value of the wrong [`Kind`].
110#[derive(Debug)]
111pub struct Unexpected {
112	/// Expected kind(s).
113	pub expected: KindSet,
114
115	/// Found kind.
116	pub found: Kind,
117}
118
119impl fmt::Display for Unexpected {
120	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121		write!(
122			f,
123			"expected {}, found {}",
124			self.expected.as_disjunction(),
125			self.found
126		)
127	}
128}
129
130impl TryFromJson for () {
131	type Error = Mapped<Unexpected>;
132
133	fn try_from_json_at(
134		json: &Value,
135		_code_map: &CodeMap,
136		offset: usize,
137	) -> Result<Self, Self::Error> {
138		match json {
139			Value::Null => Ok(()),
140			other => Err(Mapped::new(
141				offset,
142				Unexpected {
143					expected: KindSet::NULL,
144					found: other.kind(),
145				},
146			)),
147		}
148	}
149}
150
151impl TryFromJson for bool {
152	type Error = Mapped<Unexpected>;
153
154	fn try_from_json_at(
155		json: &Value,
156		_code_map: &CodeMap,
157		offset: usize,
158	) -> Result<Self, Self::Error> {
159		match json {
160			Value::Boolean(value) => Ok(*value),
161			other => Err(Mapped::new(
162				offset,
163				Unexpected {
164					expected: KindSet::BOOLEAN,
165					found: other.kind(),
166				},
167			)),
168		}
169	}
170}
171
172pub struct NumberType<T>(PhantomData<T>);
173
174impl<T> Clone for NumberType<T> {
175	fn clone(&self) -> Self {
176		*self
177	}
178}
179
180impl<T> Copy for NumberType<T> {}
181
182impl<T> Default for NumberType<T> {
183	fn default() -> Self {
184		Self(PhantomData)
185	}
186}
187
188impl<T> fmt::Debug for NumberType<T> {
189	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190		write!(f, "NumberType")
191	}
192}
193
194impl<T> fmt::Display for NumberType<T> {
195	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196		write!(f, "NumberType")
197	}
198}
199
200pub enum TryIntoNumberError<T> {
201	Unexpected(Unexpected),
202	OutOfBounds(T),
203}
204
205impl<T> TryIntoNumberError<T> {
206	pub fn map<U>(self, f: impl FnOnce(T) -> U) -> TryIntoNumberError<U> {
207		match self {
208			Self::Unexpected(e) => TryIntoNumberError::Unexpected(e),
209			Self::OutOfBounds(t) => TryIntoNumberError::OutOfBounds(f(t)),
210		}
211	}
212}
213
214impl std::error::Error for Unexpected {}
215
216macro_rules! number_from_json {
217	($($ty:ident),*) => {
218		$(
219			impl TryFromJson for $ty {
220				type Error = Mapped<TryIntoNumberError<NumberType<$ty>>>;
221
222				fn try_from_json_at(json: &Value, _code_map: &CodeMap, offset: usize) -> Result<Self, Self::Error> {
223					match json {
224						Value::Number(value) => value.parse().map_err(|_| Mapped::new(offset, TryIntoNumberError::OutOfBounds(NumberType::default()))),
225						other => Err(Mapped::new(offset, TryIntoNumberError::Unexpected(Unexpected {
226							expected: KindSet::NUMBER,
227							found: other.kind()
228						})))
229					}
230				}
231			}
232		)*
233	};
234}
235
236number_from_json!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
237
238impl TryFromJson for String {
239	type Error = Mapped<Unexpected>;
240
241	fn try_from_json_at(
242		json: &Value,
243		_code_map: &CodeMap,
244		offset: usize,
245	) -> Result<Self, Self::Error> {
246		match json {
247			Value::String(value) => Ok(value.to_string()),
248			other => Err(Mapped::new(
249				offset,
250				Unexpected {
251					expected: KindSet::STRING,
252					found: other.kind(),
253				},
254			)),
255		}
256	}
257}
258
259impl<T: TryFromJson> TryFromJson for Vec<T>
260where
261	T::Error: From<Mapped<Unexpected>>,
262{
263	type Error = T::Error;
264
265	fn try_from_json_at(
266		json: &Value,
267		code_map: &CodeMap,
268		offset: usize,
269	) -> Result<Self, Self::Error> {
270		match json {
271			Value::Array(value) => value
272				.iter_mapped(code_map, offset)
273				.map(|item| T::try_from_json_at(item.value, code_map, item.offset))
274				.collect::<Result<Vec<_>, _>>(),
275			other => Err(Mapped::new(
276				offset,
277				Unexpected {
278					expected: KindSet::ARRAY,
279					found: other.kind(),
280				},
281			)
282			.into()),
283		}
284	}
285}
286
287impl<K: FromStr + Ord, V: TryFromJson> TryFromJson for BTreeMap<K, V>
288where
289	V::Error: From<Mapped<Unexpected>> + From<Mapped<K::Err>>,
290{
291	type Error = V::Error;
292
293	fn try_from_json_at(
294		json: &Value,
295		code_map: &CodeMap,
296		offset: usize,
297	) -> Result<Self, Self::Error> {
298		match json {
299			Value::Object(object) => {
300				let mut result = BTreeMap::new();
301
302				for entry in object.iter_mapped(code_map, offset) {
303					result.insert(
304						entry
305							.value
306							.key
307							.value
308							.parse()
309							.map_err(|e| Mapped::new(entry.value.key.offset, e))?,
310						V::try_from_json_at(
311							entry.value.value.value,
312							code_map,
313							entry.value.value.offset,
314						)?,
315					);
316				}
317
318				Ok(result)
319			}
320			other => Err(Mapped::new(
321				offset,
322				Unexpected {
323					expected: KindSet::OBJECT,
324					found: other.kind(),
325				},
326			)
327			.into()),
328		}
329	}
330}