Skip to main content

apple_plist/value/
ser.rs

1//! The serde bridge from Rust values to [`Value`] trees: the
2//! serialization-side kind switch.
3
4#[cfg(test)]
5use std::collections::BTreeMap;
6
7use serde::ser::{
8    Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant,
9    SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer,
10};
11use time::{Month, PrimitiveDateTime, Time};
12
13use crate::date::Date;
14use crate::error::{Error, Result};
15use crate::uid::Uid;
16use crate::value::{Dictionary, Integer, Real, Value};
17
18/// Sentinel newtype name carrying a [`Date`] through serde as an RFC 3339
19/// string payload. Never re-exported.
20pub(crate) const DATE_NEWTYPE: &str = "$__plist_private_Date";
21
22/// Sentinel newtype name carrying a [`Uid`] through serde as a `u64` payload.
23/// Never re-exported.
24pub(crate) const UID_NEWTYPE: &str = "$__plist_private_Uid";
25
26const NANOS_PER_SECOND: i128 = 1_000_000_000;
27
28/// Converts any [`Serialize`] value into an owned [`Value`] tree.
29///
30/// `None`, `()`, and unit structs cannot be represented (property lists have
31/// no null): inside containers they are dropped, at the root they error.
32///
33/// # Errors
34///
35/// Returns [`Error::NoRootElement`] when the root value serializes to null,
36/// [`Error::UnknownType`] for non-string map keys and out-of-range 128-bit
37/// integers, and [`Error::Message`] for errors raised by custom `Serialize`
38/// implementations. [`Error::NullNotRepresentable`] is internal and never
39/// escapes this function.
40///
41/// # Examples
42///
43/// ```
44/// use std::collections::BTreeMap;
45///
46/// use apple_plist::Value;
47///
48/// let tree = apple_plist::to_value(&BTreeMap::from([("answer", 42)]))?;
49/// let dict = tree.as_dictionary().expect("maps become dictionaries");
50/// assert_eq!(
51///     dict.get("answer").and_then(Value::as_integer),
52///     Some(42.into())
53/// );
54/// # Ok::<(), apple_plist::Error>(())
55/// ```
56pub fn to_value<T>(value: &T) -> Result<Value>
57where
58    T: Serialize + ?Sized,
59{
60    match value.serialize(ValueSerializer) {
61        Err(Error::NullNotRepresentable) => Err(Error::NoRootElement),
62        result => result,
63    }
64}
65
66/// Formats the [`Date`] sentinel payload: RFC 3339 in UTC with a `Z` suffix
67/// and up to nine fractional digits (trailing zeros trimmed), enough to
68/// round-trip the nanosecond model losslessly.
69pub(crate) fn format_sentinel_date(date: Date) -> String {
70    let mut formatted = date.format_rfc3339();
71    let (_, nanos) = date.unix_parts();
72    if nanos != 0 && formatted.ends_with('Z') {
73        formatted.truncate(formatted.len() - 1);
74        let fraction = format!("{nanos:09}");
75        formatted.push('.');
76        formatted.push_str(fraction.trim_end_matches('0'));
77        formatted.push('Z');
78    }
79    formatted
80}
81
82/// Parses the [`Date`] sentinel payload: the RFC 3339 grammar, extended
83/// with the negative-year form [`format_sentinel_date`] emits for dates
84/// before year zero (which plain RFC 3339 cannot express).
85pub(crate) fn parse_sentinel_date(payload: &str) -> Option<Date> {
86    payload
87        .strip_prefix('-')
88        .map_or_else(|| Date::parse_rfc3339(payload), parse_negative_year_date)
89}
90
91/// The original `f32` behind a narrow [`Real`]. Narrow reals always hold
92/// `f32`-widened values, so the cast is exact.
93pub(crate) const fn narrow_f32(real: Real) -> f32 {
94    #[expect(
95        clippy::cast_possible_truncation,
96        reason = "narrow reals are constructed from f32, so the round-trip is exact"
97    )]
98    let narrow = real.value() as f32;
99    narrow
100}
101
102fn ascii_number(bytes: &[u8]) -> Option<u32> {
103    bytes.iter().try_fold(0_u32, |acc, &byte| {
104        byte.is_ascii_digit()
105            .then(|| acc * 10 + u32::from(byte - b'0'))
106    })
107}
108
109fn byte_at(bytes: &[u8], index: usize, expected: u8) -> Option<()> {
110    (bytes.get(index) == Some(&expected)).then_some(())
111}
112
113/// Parses `YYYY-MM-DDTHH:MM:SS[.f{1,9}]Z` with the year negated — only this
114/// crate's own formatter produces the shape, so the grammar is fixed.
115fn parse_negative_year_date(rest: &str) -> Option<Date> {
116    let bytes = rest.as_bytes();
117    let year = ascii_number(bytes.get(0..4)?)?;
118    byte_at(bytes, 4, b'-')?;
119    let month = ascii_number(bytes.get(5..7)?)?;
120    byte_at(bytes, 7, b'-')?;
121    let day = ascii_number(bytes.get(8..10)?)?;
122    byte_at(bytes, 10, b'T')?;
123    let hour = ascii_number(bytes.get(11..13)?)?;
124    byte_at(bytes, 13, b':')?;
125    let minute = ascii_number(bytes.get(14..16)?)?;
126    byte_at(bytes, 16, b':')?;
127    let second = ascii_number(bytes.get(17..19)?)?;
128    let nanos = match bytes.get(19) {
129        Some(b'Z') if bytes.len() == 20 => 0,
130        Some(b'.') if bytes.last() == Some(&b'Z') => {
131            let fraction = bytes.get(20..bytes.len() - 1)?;
132            if fraction.is_empty() || fraction.len() > 9 {
133                return None;
134            }
135            let mut value = ascii_number(fraction)?;
136            for _ in fraction.len()..9 {
137                value *= 10;
138            }
139            value
140        }
141        _ => return None,
142    };
143
144    let calendar = time::Date::from_calendar_date(
145        -i32::try_from(year).ok()?,
146        Month::try_from(u8::try_from(month).ok()?).ok()?,
147        u8::try_from(day).ok()?,
148    )
149    .ok()?;
150    let clock = Time::from_hms_nano(
151        u8::try_from(hour).ok()?,
152        u8::try_from(minute).ok()?,
153        u8::try_from(second).ok()?,
154        nanos,
155    )
156    .ok()?;
157    let unix_nanos = PrimitiveDateTime::new(calendar, clock)
158        .assume_utc()
159        .unix_timestamp_nanos();
160    Some(Date::from_unix(
161        i64::try_from(unix_nanos.div_euclid(NANOS_PER_SECOND)).ok()?,
162        i64::try_from(unix_nanos.rem_euclid(NANOS_PER_SECOND)).ok()?,
163    ))
164}
165
166fn uid_payload_error() -> Error {
167    Error::Message("uid sentinel payload must be an unsigned integer".to_owned())
168}
169
170fn date_payload_error() -> Error {
171    Error::Message("date sentinel payload must be an rfc 3339 string".to_owned())
172}
173
174/// The [`Serializer`] building an owned [`Value`] tree; stateless, a
175/// switch over the value kind.
176pub(crate) struct ValueSerializer;
177
178macro_rules! serialize_integer {
179    ($($method:ident: $ty:ty,)*) => {$(
180        fn $method(self, value: $ty) -> Result<Value> {
181            Ok(Value::Integer(Integer::from(value)))
182        }
183    )*};
184}
185
186impl Serializer for ValueSerializer {
187    type Ok = Value;
188    type Error = Error;
189    type SerializeSeq = SeqBuilder;
190    type SerializeTuple = SeqBuilder;
191    type SerializeTupleStruct = SeqBuilder;
192    type SerializeTupleVariant = VariantSeqBuilder;
193    type SerializeMap = MapBuilder;
194    type SerializeStruct = MapBuilder;
195    type SerializeStructVariant = VariantMapBuilder;
196
197    serialize_integer! {
198        serialize_i8: i8,
199        serialize_i16: i16,
200        serialize_i32: i32,
201        serialize_i64: i64,
202        serialize_u8: u8,
203        serialize_u16: u16,
204        serialize_u32: u32,
205        serialize_u64: u64,
206    }
207
208    fn serialize_bool(self, value: bool) -> Result<Value> {
209        Ok(Value::Boolean(value))
210    }
211
212    fn serialize_i128(self, value: i128) -> Result<Value> {
213        i64::try_from(value)
214            .map(Value::from)
215            .or_else(|_| u64::try_from(value).map(Value::from))
216            .map_err(|_| Error::UnknownType("i128"))
217    }
218
219    fn serialize_u128(self, value: u128) -> Result<Value> {
220        u64::try_from(value)
221            .map(Value::from)
222            .map_err(|_| Error::UnknownType("u128"))
223    }
224
225    fn serialize_f32(self, value: f32) -> Result<Value> {
226        Ok(Value::Real(Real::from(value)))
227    }
228
229    fn serialize_f64(self, value: f64) -> Result<Value> {
230        Ok(Value::Real(Real::from(value)))
231    }
232
233    fn serialize_char(self, value: char) -> Result<Value> {
234        Ok(Value::String(value.to_string()))
235    }
236
237    fn serialize_str(self, value: &str) -> Result<Value> {
238        Ok(Value::String(value.to_owned()))
239    }
240
241    fn serialize_bytes(self, value: &[u8]) -> Result<Value> {
242        Ok(Value::Data(value.to_vec()))
243    }
244
245    fn serialize_none(self) -> Result<Value> {
246        Err(Error::NullNotRepresentable)
247    }
248
249    fn serialize_some<T>(self, value: &T) -> Result<Value>
250    where
251        T: Serialize + ?Sized,
252    {
253        value.serialize(self)
254    }
255
256    fn serialize_unit(self) -> Result<Value> {
257        Err(Error::NullNotRepresentable)
258    }
259
260    fn serialize_unit_struct(self, _name: &'static str) -> Result<Value> {
261        Err(Error::NullNotRepresentable)
262    }
263
264    fn serialize_unit_variant(
265        self,
266        _name: &'static str,
267        _variant_index: u32,
268        variant: &'static str,
269    ) -> Result<Value> {
270        Ok(Value::String(variant.to_owned()))
271    }
272
273    fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<Value>
274    where
275        T: Serialize + ?Sized,
276    {
277        match name {
278            UID_NEWTYPE => {
279                if let Value::Integer(integer) = value.serialize(Self)?
280                    && let Some(uid) = integer.as_unsigned()
281                {
282                    Ok(Value::Uid(Uid::from(uid)))
283                } else {
284                    Err(uid_payload_error())
285                }
286            }
287            DATE_NEWTYPE => {
288                if let Value::String(payload) = value.serialize(Self)?
289                    && let Some(date) = parse_sentinel_date(&payload)
290                {
291                    Ok(Value::Date(date))
292                } else {
293                    Err(date_payload_error())
294                }
295            }
296            _ => value.serialize(self),
297        }
298    }
299
300    fn serialize_newtype_variant<T>(
301        self,
302        _name: &'static str,
303        _variant_index: u32,
304        variant: &'static str,
305        value: &T,
306    ) -> Result<Value>
307    where
308        T: Serialize + ?Sized,
309    {
310        let inner = value.serialize(Self)?;
311        Ok(Value::Dictionary(Dictionary::from([(
312            variant.to_owned(),
313            inner,
314        )])))
315    }
316
317    fn serialize_seq(self, _len: Option<usize>) -> Result<SeqBuilder> {
318        Ok(SeqBuilder::default())
319    }
320
321    fn serialize_tuple(self, _len: usize) -> Result<SeqBuilder> {
322        Ok(SeqBuilder::default())
323    }
324
325    fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<SeqBuilder> {
326        Ok(SeqBuilder::default())
327    }
328
329    fn serialize_tuple_variant(
330        self,
331        _name: &'static str,
332        _variant_index: u32,
333        variant: &'static str,
334        _len: usize,
335    ) -> Result<VariantSeqBuilder> {
336        Ok(VariantSeqBuilder {
337            variant,
338            elements: SeqBuilder::default(),
339        })
340    }
341
342    fn serialize_map(self, _len: Option<usize>) -> Result<MapBuilder> {
343        Ok(MapBuilder::default())
344    }
345
346    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<MapBuilder> {
347        Ok(MapBuilder::default())
348    }
349
350    fn serialize_struct_variant(
351        self,
352        _name: &'static str,
353        _variant_index: u32,
354        variant: &'static str,
355        _len: usize,
356    ) -> Result<VariantMapBuilder> {
357        Ok(VariantMapBuilder {
358            variant,
359            entries: MapBuilder::default(),
360        })
361    }
362
363    fn is_human_readable(&self) -> bool {
364        true
365    }
366}
367
368/// Accumulates array elements, dropping the ones that serialize to null
369/// (the wire effect of nil holes).
370#[derive(Default)]
371pub(crate) struct SeqBuilder {
372    elements: Vec<Value>,
373}
374
375impl SeqBuilder {
376    fn push<T>(&mut self, value: &T) -> Result<()>
377    where
378        T: Serialize + ?Sized,
379    {
380        match value.serialize(ValueSerializer) {
381            Ok(element) => {
382                self.elements.push(element);
383                Ok(())
384            }
385            Err(Error::NullNotRepresentable) => Ok(()),
386            Err(error) => Err(error),
387        }
388    }
389}
390
391impl SerializeSeq for SeqBuilder {
392    type Ok = Value;
393    type Error = Error;
394
395    fn serialize_element<T>(&mut self, value: &T) -> Result<()>
396    where
397        T: Serialize + ?Sized,
398    {
399        self.push(value)
400    }
401
402    fn end(self) -> Result<Value> {
403        Ok(Value::Array(self.elements))
404    }
405}
406
407impl SerializeTuple for SeqBuilder {
408    type Ok = Value;
409    type Error = Error;
410
411    fn serialize_element<T>(&mut self, value: &T) -> Result<()>
412    where
413        T: Serialize + ?Sized,
414    {
415        self.push(value)
416    }
417
418    fn end(self) -> Result<Value> {
419        Ok(Value::Array(self.elements))
420    }
421}
422
423impl SerializeTupleStruct for SeqBuilder {
424    type Ok = Value;
425    type Error = Error;
426
427    fn serialize_field<T>(&mut self, value: &T) -> Result<()>
428    where
429        T: Serialize + ?Sized,
430    {
431        self.push(value)
432    }
433
434    fn end(self) -> Result<Value> {
435        Ok(Value::Array(self.elements))
436    }
437}
438
439/// [`SeqBuilder`] for a tuple variant; `end` wraps the array in the
440/// externally-tagged single-key dictionary.
441pub(crate) struct VariantSeqBuilder {
442    variant: &'static str,
443    elements: SeqBuilder,
444}
445
446impl SerializeTupleVariant for VariantSeqBuilder {
447    type Ok = Value;
448    type Error = Error;
449
450    fn serialize_field<T>(&mut self, value: &T) -> Result<()>
451    where
452        T: Serialize + ?Sized,
453    {
454        self.elements.push(value)
455    }
456
457    fn end(self) -> Result<Value> {
458        Ok(Value::Dictionary(Dictionary::from([(
459            self.variant.to_owned(),
460            Value::Array(self.elements.elements),
461        )])))
462    }
463}
464
465/// Accumulates dictionary entries, dropping the ones whose value serializes
466/// to null. Duplicate keys overwrite, last writer
467/// wins — load-bearing for `#[serde(flatten)]` collisions.
468#[derive(Default)]
469pub(crate) struct MapBuilder {
470    entries: Dictionary,
471    pending_key: Option<String>,
472}
473
474impl MapBuilder {
475    fn insert_pending<T>(&mut self, value: &T) -> Result<()>
476    where
477        T: Serialize + ?Sized,
478    {
479        let key = self
480            .pending_key
481            .take()
482            .ok_or_else(|| Error::Message("map value serialized before its key".to_owned()))?;
483        match value.serialize(ValueSerializer) {
484            Ok(entry) => {
485                drop(self.entries.insert(key, entry));
486                Ok(())
487            }
488            Err(Error::NullNotRepresentable) => Ok(()),
489            Err(error) => Err(error),
490        }
491    }
492}
493
494impl SerializeMap for MapBuilder {
495    type Ok = Value;
496    type Error = Error;
497
498    fn serialize_key<T>(&mut self, key: &T) -> Result<()>
499    where
500        T: Serialize + ?Sized,
501    {
502        self.pending_key = Some(key.serialize(MapKeySerializer)?);
503        Ok(())
504    }
505
506    fn serialize_value<T>(&mut self, value: &T) -> Result<()>
507    where
508        T: Serialize + ?Sized,
509    {
510        self.insert_pending(value)
511    }
512
513    fn end(self) -> Result<Value> {
514        Ok(Value::Dictionary(self.entries))
515    }
516}
517
518impl SerializeStruct for MapBuilder {
519    type Ok = Value;
520    type Error = Error;
521
522    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
523    where
524        T: Serialize + ?Sized,
525    {
526        self.pending_key = Some(key.to_owned());
527        self.insert_pending(value)
528    }
529
530    fn end(self) -> Result<Value> {
531        Ok(Value::Dictionary(self.entries))
532    }
533}
534
535/// [`MapBuilder`] for a struct variant; `end` wraps the dictionary in the
536/// externally-tagged single-key dictionary.
537pub(crate) struct VariantMapBuilder {
538    variant: &'static str,
539    entries: MapBuilder,
540}
541
542impl SerializeStructVariant for VariantMapBuilder {
543    type Ok = Value;
544    type Error = Error;
545
546    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
547    where
548        T: Serialize + ?Sized,
549    {
550        self.entries.pending_key = Some(key.to_owned());
551        self.entries.insert_pending(value)
552    }
553
554    fn end(self) -> Result<Value> {
555        Ok(Value::Dictionary(Dictionary::from([(
556            self.variant.to_owned(),
557            Value::Dictionary(self.entries.entries),
558        )])))
559    }
560}
561
562/// Accepts only string-kinded map keys (plain strings, newtype strings, and
563/// unit variants), rejecting any non-string key.
564pub(crate) struct MapKeySerializer;
565
566macro_rules! reject_key {
567    ($($method:ident($($arg:ty),*) -> $label:literal,)*) => {$(
568        fn $method(self, $(_: $arg),*) -> Result<String> {
569            Err(Error::UnknownType($label))
570        }
571    )*};
572}
573
574impl Serializer for MapKeySerializer {
575    type Ok = String;
576    type Error = Error;
577    type SerializeSeq = Impossible<String, Error>;
578    type SerializeTuple = Impossible<String, Error>;
579    type SerializeTupleStruct = Impossible<String, Error>;
580    type SerializeTupleVariant = Impossible<String, Error>;
581    type SerializeMap = Impossible<String, Error>;
582    type SerializeStruct = Impossible<String, Error>;
583    type SerializeStructVariant = Impossible<String, Error>;
584
585    reject_key! {
586        serialize_bool(bool) -> "boolean map key",
587        serialize_i8(i8) -> "integer map key",
588        serialize_i16(i16) -> "integer map key",
589        serialize_i32(i32) -> "integer map key",
590        serialize_i64(i64) -> "integer map key",
591        serialize_i128(i128) -> "integer map key",
592        serialize_u8(u8) -> "integer map key",
593        serialize_u16(u16) -> "integer map key",
594        serialize_u32(u32) -> "integer map key",
595        serialize_u64(u64) -> "integer map key",
596        serialize_u128(u128) -> "integer map key",
597        serialize_f32(f32) -> "real map key",
598        serialize_f64(f64) -> "real map key",
599        serialize_char(char) -> "char map key",
600        serialize_bytes(&[u8]) -> "data map key",
601        serialize_none() -> "optional map key",
602        serialize_unit() -> "unit map key",
603        serialize_unit_struct(&'static str) -> "unit map key",
604    }
605
606    fn serialize_str(self, value: &str) -> Result<String> {
607        Ok(value.to_owned())
608    }
609
610    fn serialize_some<T>(self, _value: &T) -> Result<String>
611    where
612        T: Serialize + ?Sized,
613    {
614        Err(Error::UnknownType("optional map key"))
615    }
616
617    fn serialize_unit_variant(
618        self,
619        _name: &'static str,
620        _variant_index: u32,
621        variant: &'static str,
622    ) -> Result<String> {
623        Ok(variant.to_owned())
624    }
625
626    fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<String>
627    where
628        T: Serialize + ?Sized,
629    {
630        match name {
631            UID_NEWTYPE => Err(Error::UnknownType("UID map key")),
632            DATE_NEWTYPE => Err(Error::UnknownType("date map key")),
633            _ => value.serialize(self),
634        }
635    }
636
637    fn serialize_newtype_variant<T>(
638        self,
639        _name: &'static str,
640        _variant_index: u32,
641        _variant: &'static str,
642        _value: &T,
643    ) -> Result<String>
644    where
645        T: Serialize + ?Sized,
646    {
647        Err(Error::UnknownType("enum map key"))
648    }
649
650    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
651        Err(Error::UnknownType("array map key"))
652    }
653
654    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
655        Err(Error::UnknownType("array map key"))
656    }
657
658    fn serialize_tuple_struct(
659        self,
660        _name: &'static str,
661        _len: usize,
662    ) -> Result<Self::SerializeTupleStruct> {
663        Err(Error::UnknownType("array map key"))
664    }
665
666    fn serialize_tuple_variant(
667        self,
668        _name: &'static str,
669        _variant_index: u32,
670        _variant: &'static str,
671        _len: usize,
672    ) -> Result<Self::SerializeTupleVariant> {
673        Err(Error::UnknownType("enum map key"))
674    }
675
676    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
677        Err(Error::UnknownType("dictionary map key"))
678    }
679
680    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
681        Err(Error::UnknownType("dictionary map key"))
682    }
683
684    fn serialize_struct_variant(
685        self,
686        _name: &'static str,
687        _variant_index: u32,
688        _variant: &'static str,
689        _len: usize,
690    ) -> Result<Self::SerializeStructVariant> {
691        Err(Error::UnknownType("enum map key"))
692    }
693
694    fn is_human_readable(&self) -> bool {
695        true
696    }
697}
698
699impl Serialize for Value {
700    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
701    where
702        S: Serializer,
703    {
704        match self {
705            Self::Dictionary(dict) => serializer.collect_map(dict),
706            Self::Array(values) => serializer.collect_seq(values),
707            Self::String(s) => serializer.serialize_str(s),
708            Self::Integer(integer) => integer.serialize(serializer),
709            Self::Real(real) => real.serialize(serializer),
710            Self::Boolean(b) => serializer.serialize_bool(*b),
711            Self::Uid(uid) => uid.serialize(serializer),
712            Self::Data(data) => serializer.serialize_bytes(data),
713            Self::Date(date) => date.serialize(serializer),
714        }
715    }
716}
717
718impl Serialize for Integer {
719    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
720    where
721        S: Serializer,
722    {
723        match *self {
724            Self::Signed(value) => serializer.serialize_i64(value),
725            Self::Unsigned(value) => serializer.serialize_u64(value),
726        }
727    }
728}
729
730impl Serialize for Real {
731    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
732    where
733        S: Serializer,
734    {
735        if self.wide() {
736            serializer.serialize_f64(self.value())
737        } else {
738            serializer.serialize_f32(narrow_f32(*self))
739        }
740    }
741}
742
743impl Serialize for Uid {
744    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
745    where
746        S: Serializer,
747    {
748        serializer.serialize_newtype_struct(UID_NEWTYPE, &self.get())
749    }
750}
751
752impl Serialize for Date {
753    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
754    where
755        S: Serializer,
756    {
757        serializer.serialize_newtype_struct(DATE_NEWTYPE, &format_sentinel_date(*self))
758    }
759}
760
761#[cfg(test)]
762mod tests {
763    #![expect(
764        clippy::unwrap_used,
765        clippy::panic,
766        reason = "test code: unwrap/panic are the assertions"
767    )]
768
769    use serde::Serialize;
770
771    use super::*;
772
773    fn dict<const N: usize>(entries: [(&str, Value); N]) -> Value {
774        entries
775            .into_iter()
776            .map(|(key, value)| (key.to_owned(), value))
777            .collect()
778    }
779
780    fn rfc(s: &str) -> Date {
781        Date::parse_rfc3339(s).unwrap()
782    }
783
784    fn real_of(value: &Value) -> Real {
785        match value {
786            Value::Real(real) => *real,
787            other => panic!("expected a real, got {other:?}"),
788        }
789    }
790
791    #[test]
792    fn scalar_roots_map_to_their_variants() {
793        assert_eq!(to_value(&true).unwrap(), Value::Boolean(true));
794        assert_eq!(to_value("x").unwrap(), Value::from("x"));
795        assert!(matches!(
796            to_value(&-1i8).unwrap(),
797            Value::Integer(Integer::Signed(-1))
798        ));
799        assert!(matches!(
800            to_value(&u64::MAX).unwrap(),
801            Value::Integer(Integer::Unsigned(u64::MAX))
802        ));
803        assert_eq!(to_value(&1.5f64).unwrap(), Value::from(1.5));
804        assert_eq!(to_value(&[1u8, 2]).unwrap().type_name(), "array");
805    }
806
807    #[test]
808    fn all_integer_widths_widen_to_their_signedness() {
809        for value in [
810            to_value(&5i8).unwrap(),
811            to_value(&5i16).unwrap(),
812            to_value(&5i32).unwrap(),
813            to_value(&5i64).unwrap(),
814        ] {
815            assert!(matches!(value, Value::Integer(Integer::Signed(5))));
816        }
817        for value in [
818            to_value(&5u8).unwrap(),
819            to_value(&5u16).unwrap(),
820            to_value(&5u32).unwrap(),
821            to_value(&5u64).unwrap(),
822        ] {
823            assert!(matches!(value, Value::Integer(Integer::Unsigned(5))));
824        }
825    }
826
827    #[test]
828    fn real_width_follows_the_serialize_call() {
829        let narrow = to_value(&1.5f32).unwrap();
830        assert!(!real_of(&narrow).wide());
831        assert_eq!(narrow, Value::from(1.5));
832
833        let wide = to_value(&1.5f64).unwrap();
834        assert!(real_of(&wide).wide());
835    }
836
837    #[test]
838    fn char_serializes_as_a_one_char_string() {
839        assert_eq!(to_value(&'a').unwrap(), Value::from("a"));
840        assert_eq!(to_value(&'£').unwrap(), Value::from("£"));
841    }
842
843    #[test]
844    fn int128_fits_or_errors() {
845        assert!(matches!(
846            to_value(&5i128).unwrap(),
847            Value::Integer(Integer::Signed(5))
848        ));
849        assert!(matches!(
850            to_value(&-5i128).unwrap(),
851            Value::Integer(Integer::Signed(-5))
852        ));
853        assert!(matches!(
854            to_value(&i128::from(u64::MAX)).unwrap(),
855            Value::Integer(Integer::Unsigned(u64::MAX))
856        ));
857        assert!(matches!(
858            to_value(&7u128).unwrap(),
859            Value::Integer(Integer::Unsigned(7))
860        ));
861        assert!(matches!(
862            to_value(&i128::MAX),
863            Err(Error::UnknownType("i128"))
864        ));
865        assert!(matches!(
866            to_value(&i128::MIN),
867            Err(Error::UnknownType("i128"))
868        ));
869        assert!(matches!(
870            to_value(&u128::MAX),
871            Err(Error::UnknownType("u128"))
872        ));
873    }
874
875    #[test]
876    fn null_roots_error_with_no_root_element() {
877        #[derive(Serialize)]
878        struct Unit;
879
880        assert!(matches!(to_value(&None::<i32>), Err(Error::NoRootElement)));
881        assert!(matches!(to_value(&()), Err(Error::NoRootElement)));
882        assert!(matches!(to_value(&Unit), Err(Error::NoRootElement)));
883    }
884
885    #[test]
886    fn containers_drop_null_elements_and_entries() {
887        #[derive(Serialize)]
888        struct WithOption {
889            present: u8,
890            absent: Option<u8>,
891        }
892
893        let array = to_value(&vec![Some(1u8), None, Some(2u8)]).unwrap();
894        assert_eq!(array, Value::from(vec![Value::from(1u8), Value::from(2u8)]));
895
896        let value = to_value(&WithOption {
897            present: 1,
898            absent: None,
899        })
900        .unwrap();
901        assert_eq!(value, dict([("present", Value::from(1u8))]));
902
903        let mut map = BTreeMap::new();
904        assert!(map.insert("gone".to_owned(), None::<u8>).is_none());
905        assert!(map.insert("kept".to_owned(), Some(3u8)).is_none());
906        assert_eq!(to_value(&map).unwrap(), dict([("kept", Value::from(3u8))]));
907    }
908
909    #[test]
910    fn null_variant_payloads_cascade_to_the_enclosing_container() {
911        #[derive(Serialize)]
912        enum Holder {
913            Inner(Option<i32>),
914        }
915        #[derive(Serialize)]
916        struct Outer {
917            field: Option<Holder>,
918        }
919
920        let dropped = to_value(&Outer {
921            field: Some(Holder::Inner(None)),
922        })
923        .unwrap();
924        assert_eq!(dropped, dict([]));
925
926        assert!(matches!(
927            to_value(&Holder::Inner(None)),
928            Err(Error::NoRootElement)
929        ));
930    }
931
932    #[test]
933    fn map_keys_must_be_string_kinded() {
934        #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
935        struct NewtypeKey(String);
936        #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
937        enum VariantKey {
938            A,
939        }
940
941        let int_keys = BTreeMap::from([(1i32, "hi")]);
942        assert!(matches!(
943            to_value(&int_keys),
944            Err(Error::UnknownType("integer map key"))
945        ));
946
947        let bool_keys = BTreeMap::from([(true, 1u8)]);
948        assert!(matches!(
949            to_value(&bool_keys),
950            Err(Error::UnknownType("boolean map key"))
951        ));
952
953        let char_keys = BTreeMap::from([('a', 1u8)]);
954        assert!(matches!(
955            to_value(&char_keys),
956            Err(Error::UnknownType("char map key"))
957        ));
958
959        let string_keys = BTreeMap::from([("k".to_owned(), 1u8)]);
960        assert_eq!(
961            to_value(&string_keys).unwrap(),
962            dict([("k", Value::from(1u8))])
963        );
964
965        let newtype_keys = BTreeMap::from([(NewtypeKey("n".to_owned()), 1u8)]);
966        assert_eq!(
967            to_value(&newtype_keys).unwrap(),
968            dict([("n", Value::from(1u8))])
969        );
970
971        let variant_keys = BTreeMap::from([(VariantKey::A, 1u8)]);
972        assert_eq!(
973            to_value(&variant_keys).unwrap(),
974            dict([("A", Value::from(1u8))])
975        );
976    }
977
978    #[test]
979    fn enums_are_externally_tagged() {
980        #[derive(Serialize)]
981        enum Repr {
982            Unit,
983            New(u8),
984            Tuple(u8, u8),
985            Struct { f: bool },
986        }
987
988        assert_eq!(to_value(&Repr::Unit).unwrap(), Value::from("Unit"));
989        assert_eq!(
990            to_value(&Repr::New(1)).unwrap(),
991            dict([("New", Value::from(1u8))])
992        );
993        assert_eq!(
994            to_value(&Repr::Tuple(1, 2)).unwrap(),
995            dict([(
996                "Tuple",
997                Value::from(vec![Value::from(1u8), Value::from(2u8)])
998            )])
999        );
1000        assert_eq!(
1001            to_value(&Repr::Struct { f: true }).unwrap(),
1002            dict([("Struct", dict([("f", Value::from(true))]))])
1003        );
1004    }
1005
1006    #[test]
1007    fn byte_vectors_under_derive_are_arrays_not_data() {
1008        #[derive(Serialize)]
1009        struct Bytes {
1010            raw: Vec<u8>,
1011        }
1012        let value = to_value(&Bytes { raw: vec![1, 2] }).unwrap();
1013        assert_eq!(
1014            value,
1015            dict([("raw", Value::from(vec![Value::from(1u8), Value::from(2u8)]))])
1016        );
1017
1018        // Only serialize_bytes produces Data; Value::Data routes through it.
1019        assert_eq!(
1020            to_value(&Value::Data(vec![1, 2])).unwrap(),
1021            Value::Data(vec![1, 2])
1022        );
1023    }
1024
1025    #[test]
1026    fn flatten_collisions_resolve_last_writer_wins() {
1027        #[derive(Serialize)]
1028        struct Outer {
1029            a: u8,
1030            #[serde(flatten)]
1031            rest: BTreeMap<String, u8>,
1032        }
1033
1034        let outer = Outer {
1035            a: 1,
1036            rest: BTreeMap::from([("a".to_owned(), 9u8)]),
1037        };
1038        assert_eq!(to_value(&outer).unwrap(), dict([("a", Value::from(9u8))]));
1039    }
1040
1041    #[test]
1042    fn uid_and_date_serialize_to_their_variants() {
1043        assert_eq!(
1044            to_value(&Uid::from(1024)).unwrap(),
1045            Value::Uid(Uid::from(1024))
1046        );
1047        assert_eq!(
1048            to_value(&Uid::from(u64::MAX)).unwrap(),
1049            Value::Uid(Uid::from(u64::MAX))
1050        );
1051
1052        let date = rfc("2013-11-27T00:34:00Z");
1053        assert_eq!(to_value(&date).unwrap(), Value::Date(date));
1054
1055        let fractional = rfc("2013-11-27T00:34:00.123456789Z");
1056        assert_eq!(to_value(&fractional).unwrap(), Value::Date(fractional));
1057
1058        let ancient = Date::from_apple_epoch(-1e300);
1059        assert_eq!(to_value(&ancient).unwrap(), Value::Date(ancient));
1060    }
1061
1062    #[test]
1063    fn value_trees_round_trip_structurally() {
1064        let tree = dict([
1065            (
1066                "array",
1067                Value::from(vec![Value::from(-1i64), Value::from(2u8)]),
1068            ),
1069            ("bool", Value::from(true)),
1070            ("data", Value::Data(vec![0xDE, 0xAD])),
1071            ("date", Value::Date(rfc("2013-11-27T00:34:00.5Z"))),
1072            ("narrow", Value::Real(Real::from(32.5f32))),
1073            ("nested", dict([("uid", Value::Uid(Uid::from(7)))])),
1074            ("string", Value::from("s")),
1075            ("unsigned", Value::from(u64::MAX)),
1076            ("wide", Value::from(1.5)),
1077        ]);
1078        let round_tripped = to_value(&tree).unwrap();
1079        assert_eq!(round_tripped, tree);
1080
1081        let narrow = round_tripped
1082            .as_dictionary()
1083            .and_then(|d| d.get("narrow"))
1084            .unwrap();
1085        assert!(!real_of(narrow).wide());
1086        let wide = round_tripped
1087            .as_dictionary()
1088            .and_then(|d| d.get("wide"))
1089            .unwrap();
1090        assert!(real_of(wide).wide());
1091    }
1092
1093    #[test]
1094    fn sentinel_payload_misuse_errors() {
1095        let serializer = ValueSerializer;
1096        assert!(matches!(
1097            serializer.serialize_newtype_struct(UID_NEWTYPE, "nope"),
1098            Err(Error::Message(_))
1099        ));
1100        assert!(matches!(
1101            ValueSerializer.serialize_newtype_struct(UID_NEWTYPE, &-1i32),
1102            Err(Error::Message(_))
1103        ));
1104        assert!(matches!(
1105            ValueSerializer.serialize_newtype_struct(DATE_NEWTYPE, &5u8),
1106            Err(Error::Message(_))
1107        ));
1108        assert!(matches!(
1109            ValueSerializer.serialize_newtype_struct(DATE_NEWTYPE, "not a date"),
1110            Err(Error::Message(_))
1111        ));
1112    }
1113
1114    #[test]
1115    fn sentinel_date_format_is_lossless_rfc3339() {
1116        assert_eq!(
1117            format_sentinel_date(rfc("2013-11-27T00:34:00Z")),
1118            "2013-11-27T00:34:00Z"
1119        );
1120        assert_eq!(
1121            format_sentinel_date(rfc("2013-11-27T00:34:00.5Z")),
1122            "2013-11-27T00:34:00.5Z"
1123        );
1124        assert_eq!(
1125            format_sentinel_date(rfc("2013-11-27T00:34:00.000000001Z")),
1126            "2013-11-27T00:34:00.000000001Z"
1127        );
1128
1129        for date in [
1130            rfc("2013-11-27T00:34:00Z"),
1131            rfc("2013-11-27T00:34:00.123456789Z"),
1132            rfc("0001-01-01T00:00:00Z"),
1133            Date::from_apple_epoch(-1e300),
1134            Date::from_apple_epoch(1e300),
1135            Date::from_unix(-1, 500_000_000),
1136        ] {
1137            assert_eq!(parse_sentinel_date(&format_sentinel_date(date)), Some(date));
1138        }
1139    }
1140
1141    #[test]
1142    fn sentinel_date_parse_rejects_malformed_negative_years() {
1143        for payload in [
1144            "-",
1145            "-2013-11-27T00:34:00",
1146            "-2013-11-27T00:34:00z",
1147            "-13-11-27T00:34:00Z",
1148            "-2013-13-27T00:34:00Z",
1149            "-2013-11-27T00:34:00.Z",
1150            "-2013-11-27T00:34:00.1234567890Z",
1151            "-2013-11-27T00:34:00Z ",
1152        ] {
1153            assert!(parse_sentinel_date(payload).is_none(), "{payload}");
1154        }
1155    }
1156}