1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, BTreeSet, HashMap},
4 fmt::{self, Debug, Display},
5 rc::Rc,
6 sync::Arc,
7};
8
9use crate::{Arena, Array, DateTime, Item, Key, Table, item::Value};
10
11fn optional_to_required<'a>(
13 optional: Result<Option<Item<'a>>, ToTomlError>,
14) -> Result<Item<'a>, ToTomlError> {
15 match optional {
16 Ok(Some(item)) => Ok(item),
17 Ok(None) => Err(ToTomlError::from("required value was None")),
18 Err(e) => Err(e),
19 }
20}
21
22fn required_to_optional<'a>(
23 required: Result<Item<'a>, ToTomlError>,
24) -> Result<Option<Item<'a>>, ToTomlError> {
25 match required {
26 Ok(item) => Ok(Some(item)),
27 Err(e) => Err(e),
28 }
29}
30
31pub trait ToToml {
66 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
72 optional_to_required(self.to_optional_toml(arena))
73 }
74 fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
80 required_to_optional(self.to_toml(arena))
81 }
82}
83
84impl<K: ToToml> ToToml for BTreeSet<K> {
85 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
86 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
87 return length_of_array_exceeded_maximum();
88 };
89 for item in self {
90 array.push(item.to_toml(arena)?, arena);
91 }
92 Ok(array.into_item())
93 }
94}
95
96impl<K: ToToml, H> ToToml for std::collections::HashSet<K, H> {
97 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
98 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
99 return length_of_array_exceeded_maximum();
100 };
101 for item in self {
102 array.push(item.to_toml(arena)?, arena);
103 }
104 Ok(array.into_item())
105 }
106}
107
108impl<const N: usize, T: ToToml> ToToml for [T; N] {
109 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
110 self.as_slice().to_toml(arena)
111 }
112}
113
114macro_rules! impl_to_toml_tuple {
115 ($len:expr, $($idx:tt => $T:ident),+) => {
116 impl<$($T: ToToml),+> ToToml for ($($T,)+) {
117 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
118 let Some(mut array) = Array::try_with_capacity($len, arena) else {
119 return length_of_array_exceeded_maximum();
120 };
121 $(
122 array.push(self.$idx.to_toml(arena)?, arena);
123 )+
124 Ok(array.into_item())
125 }
126 }
127 };
128}
129
130impl_to_toml_tuple!(1, 0 => A);
131impl_to_toml_tuple!(2, 0 => A, 1 => B);
132impl_to_toml_tuple!(3, 0 => A, 1 => B, 2 => C);
133
134impl<T: ToToml> ToToml for Option<T> {
135 fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
136 match self {
137 Some(value) => value.to_optional_toml(arena),
138 None => Ok(None),
139 }
140 }
141}
142
143impl ToToml for str {
144 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
145 Ok(Item::string(self))
146 }
147}
148
149impl ToToml for String {
150 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
151 Ok(Item::string(self))
152 }
153}
154
155impl<T: ToToml> ToToml for Box<T> {
156 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
157 <T as ToToml>::to_toml(self, arena)
158 }
159}
160
161impl<T: ToToml> ToToml for [T] {
162 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
163 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
164 return length_of_array_exceeded_maximum();
165 };
166 for item in self {
167 array.push(item.to_toml(arena)?, arena);
168 }
169 Ok(array.into_item())
170 }
171}
172
173impl<T: ToToml> ToToml for Vec<T> {
174 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
175 self.as_slice().to_toml(arena)
176 }
177}
178
179impl ToToml for f32 {
180 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
181 Ok(Item::from(*self as f64))
182 }
183}
184
185impl ToToml for f64 {
186 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
187 Ok(Item::from(*self))
188 }
189}
190
191impl ToToml for bool {
192 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
193 Ok(Item::from(*self))
194 }
195}
196
197impl ToToml for DateTime {
198 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
199 Ok(Item::from(*self))
200 }
201}
202
203impl<T: ToToml + ?Sized> ToToml for &T {
204 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
205 <T as ToToml>::to_toml(self, arena)
206 }
207}
208
209impl<T: ToToml + ?Sized> ToToml for &mut T {
210 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
211 <T as ToToml>::to_toml(self, arena)
212 }
213}
214
215impl<T: ToToml> ToToml for Rc<T> {
216 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
217 <T as ToToml>::to_toml(self, arena)
218 }
219}
220
221impl<T: ToToml> ToToml for Arc<T> {
222 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
223 <T as ToToml>::to_toml(self, arena)
224 }
225}
226
227impl<'b, T: ToToml + Clone> ToToml for Cow<'b, T> {
228 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
229 <T as ToToml>::to_toml(self, arena)
230 }
231}
232
233impl ToToml for char {
234 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
235 let mut buf = [0; 4];
236 Ok(Item::string(arena.alloc_str(self.encode_utf8(&mut buf))))
237 }
238}
239
240impl ToToml for std::path::Path {
241 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
242 match self.to_str() {
243 Some(s) => Ok(Item::string(s)),
244 None => ToTomlError::msg("path contains invalid UTF-8 characters"),
245 }
246 }
247}
248
249impl ToToml for std::path::PathBuf {
250 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
251 self.as_path().to_toml(arena)
252 }
253}
254
255impl ToToml for Array<'_> {
256 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
257 Ok(self.clone_in(arena).into_item())
258 }
259}
260
261impl ToToml for Table<'_> {
262 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
263 Ok(self.clone_in(arena).into_item())
264 }
265}
266
267impl ToToml for Item<'_> {
268 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
269 Ok(self.clone_in(arena))
270 }
271}
272
273macro_rules! direct_upcast_integers {
274 ($($tt:tt),*) => {
275 $(impl ToToml for $tt {
276 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
277 Ok(Item::from(*self as i128))
278 }
279 })*
280 };
281}
282
283direct_upcast_integers!(u8, i8, i16, u16, i32, u32, i64, u64, i128);
284
285impl ToToml for u128 {
286 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
287 if *self > i128::MAX as u128 {
288 return ToTomlError::msg("u128 value exceeds i128::MAX");
289 }
290 Ok(Item::from(*self as i128))
291 }
292}
293
294#[diagnostic::on_unimplemented(
303 message = "`{Self}` does not implement `ToFlattened`",
304 note = "if `{Self}` implements `ToToml`, you can use `#[toml(flatten, with = flatten_any)]` instead of a manual `ToFlattened` impl"
305)]
306pub trait ToFlattened {
307 fn to_flattened<'a>(
312 &'a self,
313 arena: &'a Arena,
314 table: &mut Table<'a>,
315 ) -> Result<(), ToTomlError>;
316}
317
318fn key_to_str<'a>(item: &Item<'a>) -> Option<&'a str> {
320 match item.value() {
321 Value::String(s) => Some(*s),
322 _ => None,
323 }
324}
325
326impl<K: ToToml, V: ToToml> ToFlattened for BTreeMap<K, V> {
327 fn to_flattened<'a>(
328 &'a self,
329 arena: &'a Arena,
330 table: &mut Table<'a>,
331 ) -> Result<(), ToTomlError> {
332 for (k, v) in self {
333 let key_item = k.to_toml(arena)?;
334 let Some(key_str) = key_to_str(&key_item) else {
335 return map_key_did_not_serialize_to_string();
336 };
337 table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
338 }
339 Ok(())
340 }
341}
342
343impl<K: ToToml, V: ToToml, H> ToFlattened for HashMap<K, V, H> {
344 fn to_flattened<'a>(
345 &'a self,
346 arena: &'a Arena,
347 table: &mut Table<'a>,
348 ) -> Result<(), ToTomlError> {
349 for (k, v) in self {
350 let key_item = k.to_toml(arena)?;
351 let Some(key_str) = key_to_str(&key_item) else {
352 return map_key_did_not_serialize_to_string();
353 };
354 table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
355 }
356 Ok(())
357 }
358}
359
360impl ToFlattened for Table<'_> {
361 fn to_flattened<'a>(
362 &'a self,
363 arena: &'a Arena,
364 table: &mut Table<'a>,
365 ) -> Result<(), ToTomlError> {
366 for (key, val) in self {
367 table.insert_unique(*key, val.clone_in(arena), arena);
368 }
369 Ok(())
370 }
371}
372
373impl ToFlattened for Item<'_> {
374 fn to_flattened<'a>(
375 &'a self,
376 arena: &'a Arena,
377 table: &mut Table<'a>,
378 ) -> Result<(), ToTomlError> {
379 let Some(src) = self.as_table() else {
380 return Err(ToTomlError::from("flatten: expected a table"));
381 };
382 src.to_flattened(arena, table)
383 }
384}
385
386impl<K: ToToml, V: ToToml> ToToml for BTreeMap<K, V> {
387 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
388 let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
389 return length_of_table_exceeded_maximum();
390 };
391 self.to_flattened(arena, &mut table)?;
392 Ok(table.into_item())
393 }
394}
395
396impl<K: ToToml, V: ToToml, H> ToToml for HashMap<K, V, H> {
397 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
398 let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
399 return length_of_table_exceeded_maximum();
400 };
401 self.to_flattened(arena, &mut table)?;
402 Ok(table.into_item())
403 }
404}
405
406#[cold]
407fn map_key_did_not_serialize_to_string() -> Result<(), ToTomlError> {
408 Err(ToTomlError::from("map key did not serialize to a string"))
409}
410#[cold]
411fn length_of_array_exceeded_maximum<T>() -> Result<T, ToTomlError> {
412 Err(ToTomlError::from(
413 "length of array exceeded maximum capacity",
414 ))
415}
416
417#[cold]
418fn length_of_table_exceeded_maximum<T>() -> Result<T, ToTomlError> {
419 Err(ToTomlError::from(
420 "length of table exceeded maximum capacity",
421 ))
422}
423
424pub struct ToTomlError {
430 pub message: Cow<'static, str>,
432}
433
434impl ToTomlError {
435 #[cold]
437 pub fn msg<T>(msg: &'static str) -> Result<T, Self> {
438 Err(Self {
439 message: Cow::Borrowed(msg),
440 })
441 }
442}
443
444impl Display for ToTomlError {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 f.write_str(&self.message)
447 }
448}
449
450impl Debug for ToTomlError {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 f.debug_struct("ToTomlError")
453 .field("message", &self.message)
454 .finish()
455 }
456}
457
458impl std::error::Error for ToTomlError {}
459
460impl From<Cow<'static, str>> for ToTomlError {
461 fn from(message: Cow<'static, str>) -> Self {
462 Self { message }
463 }
464}
465
466impl From<&'static str> for ToTomlError {
467 fn from(message: &'static str) -> Self {
468 Self {
469 message: Cow::Borrowed(message),
470 }
471 }
472}