dcbor/simple.rs
1import_stdlib!();
2
3use super::varint::{EncodeVarInt, MajorType};
4use crate::{CBOR, CBORCase, Error, Result, float::f64_cbor_data};
5
6/// Represents CBOR simple values (major type 7).
7///
8/// In CBOR, simple values are a special category that includes booleans (`true`
9/// and `false`), `null`, and floating point numbers.
10///
11/// Per Section 2.4 of the dCBOR specification, only these specific simple
12/// values are valid in dCBOR. All other major type 7 values (such as undefined
13/// or other simple values) are invalid and will be rejected by dCBOR decoders.
14///
15/// When encoding floating point values, dCBOR follows specific numeric
16/// reduction rules detailed in Section 2.3 of the dCBOR specification,
17/// including:
18/// - Integral floating point values must be reduced to integers when possible
19/// - NaN values must be normalized to the canonical form `f97e00`
20///
21/// # Note
22///
23/// This type is primarily an implementation detail. Users should generally use
24/// Rust's native types instead:
25///
26/// - Use Rust's `true` and `false` booleans directly
27/// - Use the convenience methods `CBOR::r#true()`, `CBOR::r#false()`, and
28/// `CBOR::null()`
29/// - Use Rust's floating point types like `f64` directly
30///
31/// # Examples
32///
33/// ```
34/// use dcbor::prelude::*;
35///
36/// // Use Rust's native boolean and numeric types
37/// let false_cbor = CBOR::from(false);
38/// let true_cbor = CBOR::from(true);
39/// let float_cbor = CBOR::from(3.14159);
40///
41/// // Using convenience methods for common values
42/// let false_value = CBOR::r#false();
43/// let true_value = CBOR::r#true();
44/// let null_value = CBOR::null();
45///
46/// // Verify they produce the same encodings
47/// assert_eq!(false_cbor, false_value);
48/// assert_eq!(true_cbor, true_value);
49/// ```
50#[derive(Clone)]
51pub enum Simple {
52 /// The boolean value `false`.
53 /// Encoded as `0xf4` in CBOR, or `0x14` (20) with major type 7.
54 False,
55
56 /// The boolean value `true`.
57 /// Encoded as `0xf5` in CBOR, or `0x15` (21) with major type 7.
58 True,
59
60 /// The value representing `null` (`None`).
61 /// Encoded as `0xf6` in CBOR, or `0x16` (22) with major type 7.
62 Null,
63
64 /// A floating point value.
65 ///
66 /// In dCBOR, floating point values follow these encoding rules:
67 /// - Values are encoded in the shortest form that preserves precision
68 /// - Integral floating point values are encoded as integers when in range
69 /// - NaN values are normalized to `f97e00`
70 Float(f64),
71}
72
73impl Eq for Simple {
74 fn assert_receiver_is_total_eq(&self) {}
75}
76
77impl hash::Hash for Simple {
78 fn hash<H: hash::Hasher>(&self, state: &mut H) {
79 match self {
80 Self::False => (0u8).hash(state),
81 Self::True => (1u8).hash(state),
82 Self::Null => (2u8).hash(state),
83 Self::Float(v) => v.to_bits().hash(state),
84 }
85 }
86}
87
88impl Simple {
89 /// Returns the standard name of the simple value as a string.
90 ///
91 /// For `False`, `True`, and `Null`, this returns their lowercase string
92 /// representation. For `Float` values, it returns their numeric
93 /// representation.
94 ///
95 /// # Note
96 ///
97 /// This method is primarily used internally. Users should generally
98 /// interact with Rust's native types rather than with `Simple` values
99 /// directly.
100 pub fn name(&self) -> String { format!("{:?}", self) }
101
102 /// Checks if the simple value is a floating point number.
103 pub fn is_float(&self) -> bool { matches!(self, Self::Float(_)) }
104
105 /// Checks if the simple value is the NaN (Not a Number) representation.
106 pub fn is_nan(&self) -> bool {
107 matches!(self, Self::Float(v) if v.is_nan())
108 }
109
110 /// Encodes the simple value to its raw CBOR byte representation.
111 ///
112 /// Returns the CBOR bytes that represent this simple value according to the
113 /// dCBOR deterministic encoding rules:
114 /// - `False` encodes as `0xf4`
115 /// - `True` encodes as `0xf5`
116 /// - `Null` encodes as `0xf6`
117 /// - `Float` values encode according to the IEEE 754 floating point rules,
118 /// using the shortest representation that preserves precision.
119 ///
120 /// # Note
121 ///
122 /// This method is primarily used internally. For encoding simple values,
123 /// users should use the `to_cbor_data` method on CBOR values created from
124 /// Rust's native types.
125 pub fn cbor_data(&self) -> Vec<u8> {
126 match self {
127 Self::False => (20u8).encode_varint(MajorType::Simple),
128 Self::True => (21u8).encode_varint(MajorType::Simple),
129 Self::Null => (22u8).encode_varint(MajorType::Simple),
130 Self::Float(v) => f64_cbor_data(*v),
131 }
132 }
133}
134
135/// Converts a `Simple` value into a CBOR representation.
136///
137/// This conversion allows `Simple` values to be seamlessly used where CBOR
138/// values are expected, creating a CBOR value of type `CBORCase::Simple` that
139/// wraps the simple value.
140///
141/// # Note
142///
143/// This conversion is primarily used internally. Users should generally prefer
144/// converting from Rust's native types (bool, f64) directly to CBOR instead of
145/// using the `Simple` type.
146impl From<Simple> for CBOR {
147 fn from(value: Simple) -> Self { CBORCase::Simple(value.clone()).into() }
148}
149
150/// Attempts to convert a CBOR value to a `Simple` value.
151///
152/// If the CBOR value is a `CBORCase::Simple`, this conversion will succeed.
153/// For any other CBOR type, it will return a `WrongType` error.
154///
155/// # Note
156///
157/// This conversion is primarily used internally. Users should generally prefer
158/// converting CBOR values to Rust's native types (bool, f64, etc.) instead of
159/// to the `Simple` type.
160impl TryFrom<CBOR> for Simple {
161 type Error = Error;
162
163 fn try_from(cbor: CBOR) -> Result<Self> {
164 match cbor.into_case() {
165 CBORCase::Simple(simple) => Ok(simple),
166 _ => Err(Error::WrongType),
167 }
168 }
169}
170
171/// Implements equality comparison for `Simple` values.
172///
173/// Two `Simple` values are equal if they're the same variant. For `Float`
174/// variants, the contained floating point values are compared for equality
175/// according to Rust's floating point equality rules.
176impl PartialEq for Simple {
177 fn eq(&self, other: &Self) -> bool {
178 match (self, other) {
179 (Self::False, Self::False) => true,
180 (Self::True, Self::True) => true,
181 (Self::Null, Self::Null) => true,
182 (Self::Float(v1), Self::Float(v2)) => {
183 v1 == v2 || (v1.is_nan() && v2.is_nan())
184 }
185 _ => false,
186 }
187 }
188}
189
190/// Implements debug formatting for `Simple` values.
191///
192/// This is used to generate string representations for debugging purposes.
193/// The format matches the standard string representations of these values:
194/// - `false` for `Simple::False`
195/// - `true` for `Simple::True`
196/// - `null` for `Simple::Null`
197/// - The debug representation of the float for `Simple::Float`
198///
199/// This implementation is used internally by the `name` method.
200impl fmt::Debug for Simple {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 let s = match self {
203 Self::False => "false".to_owned(),
204 Self::True => "true".to_owned(),
205 Self::Null => "null".to_owned(),
206 Self::Float(v) => format!("{:?}", v),
207 };
208 f.write_str(&s)
209 }
210}
211
212/// Implements string display formatting for `Simple` values.
213///
214/// This is used when converting a `Simple` value to a string, such as with
215/// `to_string()`. The format matches the standard string representations:
216/// - `false` for `Simple::False`
217/// - `true` for `Simple::True`
218/// - `null` for `Simple::Null`
219/// - The debug representation of the float for `Simple::Float`
220impl fmt::Display for Simple {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 let s = match self {
223 Self::False => "false".to_owned(),
224 Self::True => "true".to_owned(),
225 Self::Null => "null".to_owned(),
226 Self::Float(v) => {
227 if v.is_nan() {
228 "NaN".to_owned()
229 } else if v.is_infinite() {
230 if v.is_sign_positive() {
231 "Infinity".to_owned()
232 } else {
233 "-Infinity".to_owned()
234 }
235 } else {
236 format!("{:?}", v)
237 }
238 }
239 };
240 f.write_str(&s)
241 }
242}