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}