Skip to main content

deser_php/
reference.rs

1use alloc::borrow::Cow;
2
3use deser_core::State;
4use deser_core::de::{Deserialize, Slot, default_atom};
5use deser_core::ext::{ExtValue, Extension};
6use deser_core::ser::{Emit, Serialize};
7use deser_core::{Atom, Error};
8
9/// The kind of a [`Reference`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum ReferenceKind {
12    /// The same object again (`r:`).
13    ///
14    /// PHP writes this when an object appears more than once.  It can
15    /// only refer to objects.
16    Object,
17    /// A PHP reference (`R:`), a value that was assigned by reference
18    /// (`$b = &$a`).
19    ///
20    /// It can refer to any value.
21    Value,
22}
23
24/// A reference to another value of the input (`r:` and `R:`).
25///
26/// PHP numbers the values of the input in the order they appear (starting
27/// with 1 for the top-level value) and a reference repeats the value of a
28/// number.  The deserializer does not resolve references: it passes them
29/// on as extension atoms of this type, the serializer writes them as they
30/// are.  Like when deserializing, references have to refer to a value
31/// before them (`r:` to an object), otherwise they are an error.
32///
33/// **This is basically a marker only.**  The number refers to a position
34/// in the whole input which the value that holds the reference cannot
35/// know, and what it refers to is gone once the input was deserialized:
36/// the values that the types skipped have a number too, and the numbering
37/// rules (keys and `R:` have no number, `r:` has one) are those of PHP.
38/// Short of reimplementing the deserializer there is no way to look up the
39/// value.  What this type is good for is to detect references and to
40/// write the input back unchanged:
41///
42/// ```
43/// use deser_php::{Reference, ReferenceKind};
44///
45/// // `[$a, &$a]` with `$a = 5`, the second entry refers to the first
46/// let input = b"a:2:{i:0;i:5;i:1;R:2;}";
47/// let value: (u32, Reference) = deser_php::from_slice(input).unwrap();
48/// assert_eq!(value.1, Reference::new(ReferenceKind::Value, 2));
49/// assert_eq!(deser_php::to_vec(&value).unwrap(), input);
50///
51/// // the fallback is the number: this is not the value it refers to
52/// let value: Vec<u32> = deser_php::from_slice(input).unwrap();
53/// assert_eq!(value, [5, 2]);
54/// ```
55///
56/// The fallback of the extension is the number, other types than this one
57/// receive an integer.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct Reference {
60    kind: ReferenceKind,
61    number: u64,
62}
63
64impl Reference {
65    /// Creates a reference.
66    pub const fn new(kind: ReferenceKind, number: u64) -> Reference {
67        Reference { kind, number }
68    }
69
70    /// Returns the kind of the reference.
71    pub const fn kind(self) -> ReferenceKind {
72        self.kind
73    }
74
75    /// Returns the number of the value it refers to.
76    ///
77    /// The top-level value is 1.
78    pub const fn number(self) -> u64 {
79        self.number
80    }
81}
82
83impl Extension for Reference {
84    fn name(&self) -> &str {
85        "php reference"
86    }
87
88    fn fallback(&self) -> Atom<'_> {
89        Atom::U64(self.number)
90    }
91}
92
93impl Serialize for Reference {
94    fn serialize<'a>(value: &'a Self, _state: &mut State) -> Result<Emit<'a>, Error> {
95        Ok(Emit::Atom(Atom::Ext(ExtValue::borrowed(value))))
96    }
97}
98
99impl<'de> Deserialize<'de> for Reference {
100    fn deserialize_atom(slot: &mut Slot<Self>, atom: Atom, state: &mut State) -> Result<(), Error> {
101        match atom {
102            Atom::Ext(ref ext) => match ext.downcast_ref::<Reference>() {
103                Some(&value) => {
104                    slot.set(value);
105                    Ok(())
106                }
107                None => default_atom(slot, atom, state),
108            },
109            other => default_atom(slot, other, state),
110        }
111    }
112
113    fn expecting() -> Cow<'static, str> {
114        Cow::Borrowed("php reference")
115    }
116}