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, 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 {
56 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
62 optional_to_required(self.to_optional_toml(arena))
63 }
64 fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
70 required_to_optional(self.to_toml(arena))
71 }
72}
73
74impl<K: ToToml> ToToml for BTreeSet<K> {
75 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
76 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
77 return length_of_array_exceeded_maximum();
78 };
79 for item in self {
80 array.push(item.to_toml(arena)?, arena);
81 }
82 Ok(array.into_item())
83 }
84}
85
86impl<K: ToToml, H> ToToml for std::collections::HashSet<K, H> {
87 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
88 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
89 return length_of_array_exceeded_maximum();
90 };
91 for item in self {
92 array.push(item.to_toml(arena)?, arena);
93 }
94 Ok(array.into_item())
95 }
96}
97
98impl<const N: usize, T: ToToml> ToToml for [T; N] {
99 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
100 self.as_slice().to_toml(arena)
101 }
102}
103
104macro_rules! impl_to_toml_tuple {
105 ($len:expr, $($idx:tt => $T:ident),+) => {
106 impl<$($T: ToToml),+> ToToml for ($($T,)+) {
107 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
108 let Some(mut array) = Array::try_with_capacity($len, arena) else {
109 return length_of_array_exceeded_maximum();
110 };
111 $(
112 array.push(self.$idx.to_toml(arena)?, arena);
113 )+
114 Ok(array.into_item())
115 }
116 }
117 };
118}
119
120impl_to_toml_tuple!(1, 0 => A);
121impl_to_toml_tuple!(2, 0 => A, 1 => B);
122impl_to_toml_tuple!(3, 0 => A, 1 => B, 2 => C);
123
124impl<T: ToToml> ToToml for Option<T> {
125 fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
126 match self {
127 Some(value) => value.to_optional_toml(arena),
128 None => Ok(None),
129 }
130 }
131}
132
133impl ToToml for str {
134 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
135 Ok(Item::string(self))
136 }
137}
138
139impl ToToml for String {
140 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
141 Ok(Item::string(self))
142 }
143}
144
145impl<T: ToToml> ToToml for Box<T> {
146 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
147 <T as ToToml>::to_toml(self, arena)
148 }
149}
150
151impl<T: ToToml> ToToml for [T] {
152 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
153 let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
154 return length_of_array_exceeded_maximum();
155 };
156 for item in self {
157 array.push(item.to_toml(arena)?, arena);
158 }
159 Ok(array.into_item())
160 }
161}
162
163impl<T: ToToml> ToToml for Vec<T> {
164 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
165 self.as_slice().to_toml(arena)
166 }
167}
168
169impl ToToml for f32 {
170 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
171 Ok(Item::from(*self as f64))
172 }
173}
174
175impl ToToml for f64 {
176 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
177 Ok(Item::from(*self))
178 }
179}
180
181impl ToToml for bool {
182 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
183 Ok(Item::from(*self))
184 }
185}
186
187impl<T: ToToml + ?Sized> ToToml for &T {
188 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
189 <T as ToToml>::to_toml(self, arena)
190 }
191}
192
193impl<T: ToToml + ?Sized> ToToml for &mut T {
194 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
195 <T as ToToml>::to_toml(self, arena)
196 }
197}
198
199impl<T: ToToml> ToToml for Rc<T> {
200 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
201 <T as ToToml>::to_toml(self, arena)
202 }
203}
204
205impl<T: ToToml> ToToml for Arc<T> {
206 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
207 <T as ToToml>::to_toml(self, arena)
208 }
209}
210
211impl<'b, T: ToToml + Clone> ToToml for Cow<'b, T> {
212 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
213 <T as ToToml>::to_toml(self, arena)
214 }
215}
216
217impl ToToml for char {
218 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
219 let mut buf = [0; 4];
220 Ok(Item::string(arena.alloc_str(self.encode_utf8(&mut buf))))
221 }
222}
223
224impl ToToml for std::path::Path {
225 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
226 match self.to_str() {
227 Some(s) => Ok(Item::string(s)),
228 None => ToTomlError::msg("path contains invalid UTF-8 characters"),
229 }
230 }
231}
232
233impl ToToml for std::path::PathBuf {
234 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
235 self.as_path().to_toml(arena)
236 }
237}
238
239impl ToToml for Array<'_> {
240 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
241 Ok(self.clone_in(arena).into_item())
242 }
243}
244
245impl ToToml for Table<'_> {
246 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
247 Ok(self.clone_in(arena).into_item())
248 }
249}
250
251impl ToToml for Item<'_> {
252 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
253 Ok(self.clone_in(arena))
254 }
255}
256
257macro_rules! direct_upcast_integers {
258 ($($tt:tt),*) => {
259 $(impl ToToml for $tt {
260 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
261 Ok(Item::from(*self as i128))
262 }
263 })*
264 };
265}
266
267direct_upcast_integers!(u8, i8, i16, u16, i32, u32, i64, u64, i128);
268
269impl ToToml for u128 {
270 fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
271 if *self > i128::MAX as u128 {
272 return ToTomlError::msg("u128 value exceeds i128::MAX");
273 }
274 Ok(Item::from(*self as i128))
275 }
276}
277
278#[diagnostic::on_unimplemented(
287 message = "`{Self}` does not implement `ToFlattened`",
288 note = "if `{Self}` implements `ToToml`, you can use `#[toml(flatten, with = flatten_any)]` instead of a manual `ToFlattened` impl"
289)]
290pub trait ToFlattened {
291 fn to_flattened<'a>(
296 &'a self,
297 arena: &'a Arena,
298 table: &mut Table<'a>,
299 ) -> Result<(), ToTomlError>;
300}
301
302fn key_to_str<'a>(item: &Item<'a>) -> Option<&'a str> {
304 match item.value() {
305 Value::String(s) => Some(*s),
306 _ => None,
307 }
308}
309
310impl<K: ToToml, V: ToToml> ToFlattened for BTreeMap<K, V> {
311 fn to_flattened<'a>(
312 &'a self,
313 arena: &'a Arena,
314 table: &mut Table<'a>,
315 ) -> Result<(), ToTomlError> {
316 for (k, v) in self {
317 let key_item = k.to_toml(arena)?;
318 let Some(key_str) = key_to_str(&key_item) else {
319 return map_key_did_not_serialize_to_string();
320 };
321 table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
322 }
323 Ok(())
324 }
325}
326
327impl<K: ToToml, V: ToToml, H> ToFlattened for HashMap<K, V, H> {
328 fn to_flattened<'a>(
329 &'a self,
330 arena: &'a Arena,
331 table: &mut Table<'a>,
332 ) -> Result<(), ToTomlError> {
333 for (k, v) in self {
334 let key_item = k.to_toml(arena)?;
335 let Some(key_str) = key_to_str(&key_item) else {
336 return map_key_did_not_serialize_to_string();
337 };
338 table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
339 }
340 Ok(())
341 }
342}
343
344impl<K: ToToml, V: ToToml> ToToml for BTreeMap<K, V> {
345 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
346 let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
347 return length_of_table_exceeded_maximum();
348 };
349 self.to_flattened(arena, &mut table)?;
350 Ok(table.into_item())
351 }
352}
353
354impl<K: ToToml, V: ToToml, H> ToToml for HashMap<K, V, H> {
355 fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
356 let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
357 return length_of_table_exceeded_maximum();
358 };
359 self.to_flattened(arena, &mut table)?;
360 Ok(table.into_item())
361 }
362}
363
364#[cold]
365fn map_key_did_not_serialize_to_string() -> Result<(), ToTomlError> {
366 Err(ToTomlError::from("map key did not serialize to a string"))
367}
368#[cold]
369fn length_of_array_exceeded_maximum<T>() -> Result<T, ToTomlError> {
370 Err(ToTomlError::from(
371 "length of array exceeded maximum capacity",
372 ))
373}
374
375#[cold]
376fn length_of_table_exceeded_maximum<T>() -> Result<T, ToTomlError> {
377 Err(ToTomlError::from(
378 "length of table exceeded maximum capacity",
379 ))
380}
381
382pub struct ToTomlError {
388 pub message: Cow<'static, str>,
390}
391
392impl ToTomlError {
393 #[cold]
395 pub fn msg<T>(msg: &'static str) -> Result<T, Self> {
396 Err(Self {
397 message: Cow::Borrowed(msg),
398 })
399 }
400}
401
402impl Display for ToTomlError {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 f.write_str(&self.message)
405 }
406}
407
408impl Debug for ToTomlError {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 f.debug_struct("ToTomlError")
411 .field("message", &self.message)
412 .finish()
413 }
414}
415
416impl std::error::Error for ToTomlError {}
417
418impl From<Cow<'static, str>> for ToTomlError {
419 fn from(message: Cow<'static, str>) -> Self {
420 Self { message }
421 }
422}
423
424impl From<&'static str> for ToTomlError {
425 fn from(message: &'static str) -> Self {
426 Self {
427 message: Cow::Borrowed(message),
428 }
429 }
430}