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.
31///
32/// **This is basically a marker only.** The number refers to a position
33/// in the whole input which the value that holds the reference cannot
34/// know, and what it refers to is gone once the input was deserialized:
35/// the values that the types skipped have a number too, and the numbering
36/// rules (keys and `R:` have no number, `r:` has one) are those of PHP.
37/// Short of reimplementing the deserializer there is no way to look up the
38/// value. What this type is good for is to detect references and to
39/// write the input back unchanged:
40///
41/// ```
42/// use deser_php::{Reference, ReferenceKind};
43///
44/// // `[$a, &$a]` with `$a = 5`, the second entry refers to the first
45/// let input = b"a:2:{i:0;i:5;i:1;R:2;}";
46/// let value: (u32, Reference) = deser_php::from_slice(input).unwrap();
47/// assert_eq!(value.1, Reference::new(ReferenceKind::Value, 2));
48/// assert_eq!(deser_php::to_vec(&value).unwrap(), input);
49///
50/// // the fallback is the number: this is not the value it refers to
51/// let value: Vec<u32> = deser_php::from_slice(input).unwrap();
52/// assert_eq!(value, [5, 2]);
53/// ```
54///
55/// The fallback of the extension is the number, other types than this one
56/// receive an integer.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub struct Reference {
59 kind: ReferenceKind,
60 number: u64,
61}
62
63impl Reference {
64 /// Creates a reference.
65 pub const fn new(kind: ReferenceKind, number: u64) -> Reference {
66 Reference { kind, number }
67 }
68
69 /// Returns the kind of the reference.
70 pub const fn kind(self) -> ReferenceKind {
71 self.kind
72 }
73
74 /// Returns the number of the value it refers to.
75 ///
76 /// The top-level value is 1.
77 pub const fn number(self) -> u64 {
78 self.number
79 }
80}
81
82impl Extension for Reference {
83 fn name(&self) -> &str {
84 "php reference"
85 }
86
87 fn fallback(&self) -> Atom<'_> {
88 Atom::U64(self.number)
89 }
90}
91
92impl Serialize for Reference {
93 fn serialize<'a>(value: &'a Self, _state: &mut State) -> Result<Emit<'a>, Error> {
94 Ok(Emit::Atom(Atom::Ext(ExtValue::borrowed(value))))
95 }
96}
97
98impl<'de> Deserialize<'de> for Reference {
99 fn deserialize_atom(slot: &mut Slot<Self>, atom: Atom, state: &mut State) -> Result<(), Error> {
100 match atom {
101 Atom::Ext(ref ext) => match ext.downcast_ref::<Reference>() {
102 Some(&value) => {
103 slot.set(value);
104 Ok(())
105 }
106 None => default_atom(slot, atom, state),
107 },
108 other => default_atom(slot, other, state),
109 }
110 }
111
112 fn expecting() -> Cow<'static, str> {
113 Cow::Borrowed("php reference")
114 }
115}