eure_document/parse/
object_key.rs

1//! ParseObjectKey trait and implementations for object key types.
2
3use crate::{identifier::Identifier, parse::ParseErrorKind, prelude_internal::*};
4use num_bigint::BigInt;
5
6/// Trait for types that can be used as object keys when parsing from Eure documents.
7///
8/// This trait abstracts over key types that can be used in `Map<K, V>`, supporting
9/// owned (`ObjectKey`), borrowed (`&'doc ObjectKey`), and primitive types (`bool`,
10/// `BigInt`, `String`) for type-constrained maps.
11///
12/// # Lifetime Parameter
13///
14/// The `'doc` lifetime ties the parsed key to the document's lifetime, allowing
15/// zero-copy parsing for reference types.
16///
17/// # Type Constraints
18///
19/// Implementors must satisfy:
20/// - `Eq + Hash` for use with `AHashMap` (std feature)
21/// - `Ord` for use with `BTreeMap` (no_std)
22///
23/// # Examples
24///
25/// ```ignore
26/// use eure_document::{EureDocument, Map, ObjectKey, ParseDocument};
27///
28/// // Parse map with borrowed keys (zero-copy)
29/// let map: Map<&ObjectKey, String> = doc.parse(root_id)?;
30///
31/// // Parse map with owned keys
32/// let map: Map<ObjectKey, String> = doc.parse(root_id)?;
33///
34/// // Parse map with type-constrained keys
35/// let map: Map<String, i32> = doc.parse(root_id)?;
36/// ```
37pub trait ParseObjectKey<'doc>: Sized + Eq + core::hash::Hash + Ord {
38    /// Parse an object key from the given ObjectKey reference in the document.
39    ///
40    /// # Arguments
41    ///
42    /// * `key` - Reference to the ObjectKey in the document's NodeMap
43    ///
44    /// # Returns
45    ///
46    /// Returns `Self` on success, or `ParseErrorKind` if the key cannot be
47    /// converted to the target type (e.g., trying to parse a Bool as String).
48    ///
49    /// # Errors
50    ///
51    /// Returns `ParseErrorKind::TypeMismatch` when the ObjectKey variant doesn't
52    /// match the expected type.
53    fn from_object_key(key: &'doc ObjectKey) -> Result<Self, ParseErrorKind>;
54
55    /// Parse a key from an extension identifier.
56    ///
57    /// Extension keys are always string identifiers. This method converts them
58    /// to the target key type. This is separate from `from_object_key` because
59    /// extension identifiers don't have `'doc` lifetime (they're not ObjectKeys
60    /// stored in the document's NodeMap).
61    ///
62    /// # Errors
63    ///
64    /// Returns `ParseErrorKind::TypeMismatch` if the target type cannot be
65    /// constructed from an identifier (e.g., borrowed `&ObjectKey` or numeric keys).
66    fn from_extension_ident(ident: &Identifier) -> Result<Self, ParseErrorKind>;
67}
68
69// ============================================================================
70// Implementation for &'doc ObjectKey (borrowed, zero-copy)
71// ============================================================================
72
73impl<'doc> ParseObjectKey<'doc> for &'doc ObjectKey {
74    fn from_object_key(key: &'doc ObjectKey) -> Result<Self, ParseErrorKind> {
75        Ok(key)
76    }
77
78    fn from_extension_ident(_ident: &Identifier) -> Result<Self, ParseErrorKind> {
79        // Cannot return a borrowed ObjectKey from an Identifier
80        Err(ParseErrorKind::TypeMismatch {
81            expected: crate::value::ValueKind::Map,
82            actual: crate::value::ValueKind::Text,
83        })
84    }
85}
86
87// ============================================================================
88// Implementation for ObjectKey (owned, cloned)
89// ============================================================================
90
91impl ParseObjectKey<'_> for ObjectKey {
92    fn from_object_key(key: &ObjectKey) -> Result<Self, ParseErrorKind> {
93        Ok(key.clone())
94    }
95
96    fn from_extension_ident(ident: &Identifier) -> Result<Self, ParseErrorKind> {
97        Ok(ObjectKey::String(ident.as_ref().to_string()))
98    }
99}
100
101// ============================================================================
102// Implementation for bool (type-constrained)
103// Parses from String keys "true" or "false"
104// ============================================================================
105
106impl ParseObjectKey<'_> for bool {
107    fn from_object_key(key: &ObjectKey) -> Result<Self, ParseErrorKind> {
108        match key {
109            ObjectKey::String(s) if s == "true" => Ok(true),
110            ObjectKey::String(s) if s == "false" => Ok(false),
111            _ => Err(ParseErrorKind::TypeMismatch {
112                expected: crate::value::ValueKind::Bool,
113                actual: key_to_value_kind(key),
114            }),
115        }
116    }
117
118    fn from_extension_ident(ident: &Identifier) -> Result<Self, ParseErrorKind> {
119        match ident.as_ref() {
120            "true" => Ok(true),
121            "false" => Ok(false),
122            _ => Err(ParseErrorKind::TypeMismatch {
123                expected: crate::value::ValueKind::Bool,
124                actual: crate::value::ValueKind::Text,
125            }),
126        }
127    }
128}
129
130// ============================================================================
131// Implementation for BigInt (type-constrained)
132// ============================================================================
133
134impl ParseObjectKey<'_> for BigInt {
135    fn from_object_key(key: &ObjectKey) -> Result<Self, ParseErrorKind> {
136        match key {
137            ObjectKey::Number(n) => Ok(n.clone()),
138            _ => Err(ParseErrorKind::TypeMismatch {
139                expected: crate::value::ValueKind::Integer,
140                actual: key_to_value_kind(key),
141            }),
142        }
143    }
144
145    fn from_extension_ident(_ident: &Identifier) -> Result<Self, ParseErrorKind> {
146        // Extension identifiers are always strings, not numbers
147        Err(ParseErrorKind::TypeMismatch {
148            expected: crate::value::ValueKind::Integer,
149            actual: crate::value::ValueKind::Text,
150        })
151    }
152}
153
154// ============================================================================
155// Implementation for String (type-constrained)
156// ============================================================================
157
158impl ParseObjectKey<'_> for String {
159    fn from_object_key(key: &ObjectKey) -> Result<Self, ParseErrorKind> {
160        match key {
161            ObjectKey::String(s) => Ok(s.clone()),
162            _ => Err(ParseErrorKind::TypeMismatch {
163                expected: crate::value::ValueKind::Text,
164                actual: key_to_value_kind(key),
165            }),
166        }
167    }
168
169    fn from_extension_ident(ident: &Identifier) -> Result<Self, ParseErrorKind> {
170        Ok(ident.as_ref().to_string())
171    }
172}
173
174// ============================================================================
175// Helper function to convert ObjectKey to ValueKind for error messages
176// ============================================================================
177
178fn key_to_value_kind(key: &ObjectKey) -> crate::value::ValueKind {
179    match key {
180        ObjectKey::Number(_) => crate::value::ValueKind::Integer,
181        ObjectKey::String(_) => crate::value::ValueKind::Text,
182        ObjectKey::Tuple(_) => crate::value::ValueKind::Tuple,
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::value::Tuple;
190
191    #[test]
192    fn test_parse_object_key_borrowed() {
193        let key = ObjectKey::String("test".into());
194        let borrowed: &ObjectKey = ParseObjectKey::from_object_key(&key).unwrap();
195        assert_eq!(borrowed, &key);
196    }
197
198    #[test]
199    fn test_parse_object_key_owned() {
200        let key = ObjectKey::String("test".into());
201        let owned: ObjectKey = ParseObjectKey::from_object_key(&key).unwrap();
202        assert_eq!(owned, key);
203    }
204
205    #[test]
206    fn test_borrowed_key_is_zero_copy() {
207        let key = ObjectKey::String("test".into());
208        let borrowed: &ObjectKey = ParseObjectKey::from_object_key(&key).unwrap();
209        assert!(core::ptr::eq(&key, borrowed));
210    }
211
212    #[test]
213    #[allow(clippy::bool_assert_comparison)]
214    fn test_parse_bool_key() {
215        let key = ObjectKey::String("true".into());
216        let b: bool = ParseObjectKey::from_object_key(&key).unwrap();
217        assert_eq!(b, true);
218
219        let key_false = ObjectKey::String("false".into());
220        let b_false: bool = ParseObjectKey::from_object_key(&key_false).unwrap();
221        assert_eq!(b_false, false);
222    }
223
224    #[test]
225    fn test_parse_bool_key_type_mismatch() {
226        let key = ObjectKey::String("not a bool".into());
227        let result: Result<bool, _> = ParseObjectKey::from_object_key(&key);
228        assert!(result.is_err());
229    }
230
231    #[test]
232    fn test_parse_bigint_key() {
233        let key = ObjectKey::Number(BigInt::from(42));
234        let n: BigInt = ParseObjectKey::from_object_key(&key).unwrap();
235        assert_eq!(n, BigInt::from(42));
236    }
237
238    #[test]
239    fn test_parse_bigint_key_type_mismatch() {
240        let key = ObjectKey::String("not a number".into());
241        let result: Result<BigInt, _> = ParseObjectKey::from_object_key(&key);
242        assert!(result.is_err());
243    }
244
245    #[test]
246    fn test_parse_string_key() {
247        let key = ObjectKey::String("hello".into());
248        let s: String = ParseObjectKey::from_object_key(&key).unwrap();
249        assert_eq!(s, "hello");
250    }
251
252    #[test]
253    fn test_parse_string_key_type_mismatch() {
254        let key = ObjectKey::Number(BigInt::from(123));
255        let result: Result<String, _> = ParseObjectKey::from_object_key(&key);
256        assert!(result.is_err());
257    }
258
259    #[test]
260    fn test_parse_tuple_key() {
261        let key = ObjectKey::Tuple(Tuple(vec![
262            ObjectKey::String("a".into()),
263            ObjectKey::Number(BigInt::from(1)),
264        ]));
265        let owned: ObjectKey = ParseObjectKey::from_object_key(&key).unwrap();
266        assert_eq!(owned, key);
267    }
268}