Skip to main content

jacquard_common/types/
value.rs

1use crate::{
2    Bos, DefaultStr, IntoStatic,
3    types::{DataModelType, LexiconStringType, UriType, blob::Blob, string::*},
4};
5use alloc::boxed::Box;
6use alloc::collections::BTreeMap;
7use alloc::vec::Vec;
8use bytes::Bytes;
9use core::convert::Infallible;
10use ipld_core::ipld::Ipld;
11use serde::Serialize;
12use smol_str::{SmolStr, ToSmolStr};
13
14/// Conversion utilities for Data types
15pub mod convert;
16/// String parsing for AT Protocol types
17pub mod parsing;
18/// Serde implementations for Data types
19pub mod serde_impl;
20
21pub use serde_impl::{DataDeserializerError, RawDataSerializerError};
22
23#[cfg(test)]
24mod tests;
25
26/// AT Protocol data model value
27///
28/// Represents any valid value in the AT Protocol data model, which supports JSON and CBOR
29/// serialization with specific constraints (no floats, CID links, blobs with metadata).
30///
31/// This is the generic "unknown data" type used for lexicon values, extra fields captured
32/// by `#[lexicon]`, and IPLD data structures.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum Data<S: Bos<str> + AsRef<str> = DefaultStr> {
35    /// Null value
36    Null,
37    /// Boolean value
38    Boolean(bool),
39    /// Integer value (no floats in AT Protocol)
40    Integer(i64),
41    /// String value (parsed into specific AT Protocol types when possible)
42    String(AtprotoStr<S>),
43    /// Raw bytes
44    Bytes(Bytes),
45    /// CID link reference
46    CidLink(Cid<S>),
47    /// Array of values
48    Array(Array<S>),
49    /// Object/map of values
50    Object(Object<S>),
51    /// Blob reference with metadata
52    Blob(Blob<S>),
53    /// Invalid number (floating point)
54    InvalidNumber(S),
55}
56
57/// Errors that can occur when working with AT Protocol data
58#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
59#[non_exhaustive]
60pub enum AtDataError {
61    /// Floating point numbers are not allowed in AT Protocol
62    #[error("floating point numbers not allowed in AT protocol data")]
63    FloatNotAllowed,
64    /// Invalid data type for AT Protocol data
65    #[error("invalid data type for AT protocol data")]
66    InvalidType,
67    /// Deserialization error
68    #[error("deserialization error")]
69    Deserialization,
70}
71
72impl<S> Data<S>
73where
74    S: Bos<str> + AsRef<str>,
75{
76    /// Get the data model type of this value
77    pub fn data_type(&self) -> DataModelType {
78        match self {
79            Data::Null => DataModelType::Null,
80            Data::Boolean(_) => DataModelType::Boolean,
81            Data::Integer(_) => DataModelType::Integer,
82            Data::String(s) => match s {
83                AtprotoStr::Datetime(_) => DataModelType::String(LexiconStringType::Datetime),
84                AtprotoStr::Language(_) => DataModelType::String(LexiconStringType::Language),
85                AtprotoStr::Tid(_) => DataModelType::String(LexiconStringType::Tid),
86                AtprotoStr::Nsid(_) => DataModelType::String(LexiconStringType::Nsid),
87                AtprotoStr::Did(_) => DataModelType::String(LexiconStringType::Did),
88                AtprotoStr::Handle(_) => DataModelType::String(LexiconStringType::Handle),
89                AtprotoStr::AtIdentifier(_) => {
90                    DataModelType::String(LexiconStringType::AtIdentifier)
91                }
92                AtprotoStr::AtUri(_) => DataModelType::String(LexiconStringType::AtUri),
93                AtprotoStr::Uri(uri) => match uri {
94                    UriValue::Did(_) => DataModelType::String(LexiconStringType::Uri(UriType::Did)),
95                    UriValue::At(_) => DataModelType::String(LexiconStringType::Uri(UriType::At)),
96                    UriValue::Https(_) => {
97                        DataModelType::String(LexiconStringType::Uri(UriType::Https))
98                    }
99                    UriValue::Wss(_) => DataModelType::String(LexiconStringType::Uri(UriType::Wss)),
100                    UriValue::Cid(_) => DataModelType::String(LexiconStringType::Uri(UriType::Cid)),
101                    UriValue::Any(_) => DataModelType::String(LexiconStringType::Uri(UriType::Any)),
102                },
103                AtprotoStr::Cid(_) => DataModelType::String(LexiconStringType::Cid),
104                AtprotoStr::RecordKey(_) => DataModelType::String(LexiconStringType::RecordKey),
105                AtprotoStr::String(_) => DataModelType::String(LexiconStringType::String),
106            },
107            Data::Bytes(_) => DataModelType::Bytes,
108            Data::CidLink(_) => DataModelType::CidLink,
109            Data::Array(_) => DataModelType::Array,
110            Data::Object(_) => DataModelType::Object,
111            Data::Blob(_) => DataModelType::Blob,
112            Data::InvalidNumber(_) => DataModelType::Bytes,
113        }
114    }
115
116    /// Get as object if this is an Object variant
117    pub fn as_object(&self) -> Option<&Object<S>> {
118        if let Data::Object(obj) = self {
119            Some(obj)
120        } else {
121            None
122        }
123    }
124
125    /// Get as array if this is an Array variant
126    pub fn as_array(&self) -> Option<&Array<S>> {
127        if let Data::Array(arr) = self {
128            Some(arr)
129        } else {
130            None
131        }
132    }
133
134    /// Get as string if this is a String variant
135    pub fn as_str(&self) -> Option<&str> {
136        if let Data::String(s) = self {
137            Some(s.as_str())
138        } else {
139            None
140        }
141    }
142
143    /// Get as object if this is an Object variant
144    pub fn as_object_mut<'a>(&'a mut self) -> Option<&'a mut Object<S>> {
145        if let Data::Object(obj) = self {
146            Some(obj)
147        } else {
148            None
149        }
150    }
151
152    /// Get as array if this is an Array variant
153    pub fn as_array_mut<'a>(&'a mut self) -> Option<&'a mut Array<S>> {
154        if let Data::Array(arr) = self {
155            Some(arr)
156        } else {
157            None
158        }
159    }
160
161    /// Get as string if this is a String variant
162    pub fn as_str_mut<'s>(&'s mut self) -> Option<&'s mut AtprotoStr<S>> {
163        if let Data::String(s) = self {
164            Some(s)
165        } else {
166            None
167        }
168    }
169
170    /// Get as integer if this is an Integer variant
171    pub fn as_integer_mut(&mut self) -> Option<&mut i64> {
172        if let Data::Integer(i) = self {
173            Some(i)
174        } else {
175            None
176        }
177    }
178
179    /// Get a mutable reference to the boolean if this is a Boolean variant
180    pub fn as_boolean_mut(&mut self) -> Option<&mut bool> {
181        if let Data::Boolean(b) = self {
182            Some(b)
183        } else {
184            None
185        }
186    }
187
188    /// Get as integer if this is an Integer variant
189    pub fn as_integer(&self) -> Option<i64> {
190        if let Data::Integer(i) = self {
191            Some(*i)
192        } else {
193            None
194        }
195    }
196
197    /// Get as boolean if this is a Boolean variant
198    pub fn as_boolean(&self) -> Option<bool> {
199        if let Data::Boolean(b) = self {
200            Some(*b)
201        } else {
202            None
203        }
204    }
205
206    /// Check if this is a null value
207    pub fn is_null(&self) -> bool {
208        matches!(self, Data::Null)
209    }
210
211    /// Get the "$type" discriminator field if this is an object with a string "$type" field
212    ///
213    /// This is a shortcut for union type discrimination in AT Protocol.
214    /// Returns `None` if this is not an object or if the "$type" field is missing/not a string.
215    pub fn type_discriminator(&self) -> Option<&str> {
216        self.as_object()?.type_discriminator()
217    }
218
219    /// Serialize to canonical DAG-CBOR bytes for CID computation
220    ///
221    /// This produces the deterministic CBOR encoding used for content-addressing.
222    pub fn to_dag_cbor(
223        &self,
224    ) -> Result<Vec<u8>, serde_ipld_dagcbor::EncodeError<alloc::collections::TryReserveError>>
225    where
226        S: Serialize,
227    {
228        serde_ipld_dagcbor::to_vec(self)
229    }
230
231    /// Get a value at a path within nested Data structures
232    ///
233    /// Path syntax:
234    /// - `.field` or `field` - access object field
235    /// - `[0]` - access array index
236    /// - Combined: `embed.images[0].alt`
237    ///
238    /// # Example
239    /// ```ignore
240    /// let data: Data = ...;
241    /// if let Some(alt_text) = data.get_at_path("embed.images[0].alt") {
242    ///     println!("Alt text: {}", alt_text.as_str().unwrap());
243    /// }
244    /// ```
245    pub fn get_at_path<'s>(&'s self, path: &str) -> Option<&'s Data<S>> {
246        parse_and_traverse_path(self, path)
247    }
248
249    /// Get a mutable reference to a field at the given path
250    ///
251    /// Uses the same path syntax as [`get_at_path`](Self::get_at_path).
252    pub fn get_at_path_mut(&mut self, path: &str) -> Option<&mut Data<S>> {
253        parse_and_traverse_path_mut(self, path)
254    }
255
256    /// Set the value at the given path, returning true if successful
257    ///
258    /// Uses the same path syntax as [`get_at_path`](Self::get_at_path).
259    pub fn set_at_path(&mut self, path: &str, new_data: Data<S>) -> bool {
260        if let Some(data) = parse_and_traverse_path_mut(self, path) {
261            *data = new_data;
262            true
263        } else {
264            false
265        }
266    }
267
268    /// Query data with pattern matching
269    ///
270    /// Pattern syntax:
271    /// - `field.nested` - exact path navigation
272    /// - `[..]` - wildcard over collection (array elements or object values)
273    /// - `field..nested` - scoped recursion (find nested within field, expect one)
274    /// - `...field` - global recursion (find all occurrences anywhere)
275    ///
276    /// # Examples
277    /// ```ignore
278    /// // Exact path with wildcard
279    /// let alts = data.query("embed.[..].alt");
280    ///
281    /// // Scoped recursion
282    /// let handle = data.query("post..handle"); // finds post.author.handle
283    ///
284    /// // Global recursion
285    /// let all_cids = data.query("...cid"); // all CIDs anywhere
286    /// ```
287    pub fn query<'s>(&'s self, pattern: &str) -> QueryResult<'s, S> {
288        query_data(self, pattern)
289    }
290}
291
292impl<S: Bos<str> + AsRef<str>> Data<S> {
293    /// Convert to a `Data` with a different backing type.
294    pub fn convert<B: Bos<str> + AsRef<str> + From<S>>(self) -> Data<B> {
295        match self {
296            Data::Null => Data::Null,
297            Data::Boolean(b) => Data::Boolean(b),
298            Data::Integer(i) => Data::Integer(i),
299            Data::String(s) => Data::String(s.convert()),
300            Data::Bytes(b) => Data::Bytes(b),
301            Data::CidLink(cid) => Data::CidLink(cid.convert()),
302            Data::Array(arr) => Data::Array(arr.convert()),
303            Data::Object(obj) => Data::Object(obj.convert()),
304            Data::Blob(blob) => Data::Blob(blob.convert()),
305            Data::InvalidNumber(float) => Data::InvalidNumber(float.into()),
306        }
307    }
308}
309
310impl<S> IntoStatic for Data<S>
311where
312    S: Bos<str> + AsRef<str> + IntoStatic,
313    <S as IntoStatic>::Output: AsRef<str> + Bos<str>,
314{
315    type Output = Data<<S as IntoStatic>::Output>;
316    fn into_static(self) -> Data<<S as IntoStatic>::Output> {
317        match self {
318            Data::Null => Data::Null,
319            Data::Boolean(bool) => Data::Boolean(bool),
320            Data::Integer(int) => Data::Integer(int),
321            Data::String(string) => Data::String(string.into_static()),
322            Data::Bytes(bytes) => Data::Bytes(bytes),
323            Data::Array(array) => Data::Array(array.into_static()),
324            Data::Object(object) => Data::Object(object.into_static()),
325            Data::CidLink(cid) => Data::CidLink(cid.into_static()),
326            Data::Blob(blob) => Data::Blob(blob.into_static()),
327            Data::InvalidNumber(float) => Data::InvalidNumber(float.into_static()),
328        }
329    }
330}
331
332/// Array of AT Protocol data values
333#[derive(Debug, Clone, PartialEq, Eq)]
334pub struct Array<S = DefaultStr>(pub Vec<Data<S>>)
335where
336    S: Bos<str> + AsRef<str>;
337
338impl<S: Bos<str> + AsRef<str>> Array<S> {
339    /// Convert to an `Array` with a different backing type.
340    pub fn convert<B: Bos<str> + AsRef<str> + From<S>>(self) -> Array<B> {
341        Array(self.0.into_iter().map(|d| d.convert()).collect())
342    }
343}
344
345impl<S> IntoStatic for Array<S>
346where
347    S: Bos<str> + AsRef<str> + IntoStatic,
348    <S as IntoStatic>::Output: AsRef<str> + Bos<str>,
349{
350    type Output = Array<<S as IntoStatic>::Output>;
351    fn into_static(self) -> Array<<S as IntoStatic>::Output> {
352        Array(self.0.into_static())
353    }
354}
355
356impl<S> Array<S>
357where
358    S: Bos<str> + AsRef<str>,
359{
360    /// Get the number of elements in the array
361    pub fn len(&self) -> usize {
362        self.0.len()
363    }
364
365    /// Check if the array is empty
366    pub fn is_empty(&self) -> bool {
367        self.0.is_empty()
368    }
369
370    /// Get an element by index
371    pub fn get(&self, index: usize) -> Option<&Data<S>> {
372        self.0.get(index)
373    }
374
375    /// Get a mutable reference to an element by index
376    pub fn get_mut(&mut self, index: usize) -> Option<&mut Data<S>> {
377        self.0.get_mut(index)
378    }
379
380    /// Get an iterator over the array elements
381    pub fn iter(&self) -> core::slice::Iter<'_, Data<S>> {
382        self.0.iter()
383    }
384}
385
386impl<S> core::ops::Index<usize> for Array<S>
387where
388    S: AsRef<str> + Bos<str>,
389{
390    type Output = Data<S>;
391
392    fn index(&self, index: usize) -> &Self::Output {
393        &self.0[index]
394    }
395}
396
397/// Object/map of AT Protocol data values
398#[derive(Debug, Clone, PartialEq, Eq)]
399pub struct Object<S = DefaultStr>(pub BTreeMap<SmolStr, Data<S>>)
400where
401    S: Bos<str> + AsRef<str>;
402
403impl<S: Bos<str> + AsRef<str>> Object<S> {
404    /// Convert to an `Object` with a different backing type.
405    pub fn convert<B: Bos<str> + AsRef<str> + From<S>>(self) -> Object<B> {
406        Object(self.0.into_iter().map(|(k, v)| (k, v.convert())).collect())
407    }
408}
409
410impl<S> IntoStatic for Object<S>
411where
412    S: Bos<str> + AsRef<str> + IntoStatic,
413    <S as IntoStatic>::Output: AsRef<str> + Bos<str>,
414{
415    type Output = Object<<S as IntoStatic>::Output>;
416    fn into_static(self) -> Object<<S as IntoStatic>::Output> {
417        Object(self.0.into_static())
418    }
419}
420
421impl<S> Object<S>
422where
423    S: AsRef<str> + Bos<str>,
424{
425    /// Get a value by key
426    pub fn get(&self, key: &str) -> Option<&Data<S>> {
427        self.0.get(key)
428    }
429
430    /// Get a mutable reference to a value by key
431    pub fn get_mut(&mut self, key: &str) -> Option<&mut Data<S>> {
432        self.0.get_mut(key)
433    }
434
435    /// Check if a key exists in the object
436    pub fn contains_key(&self, key: &str) -> bool {
437        self.0.contains_key(key)
438    }
439
440    /// Get the number of key-value pairs in the object
441    pub fn len(&self) -> usize {
442        self.0.len()
443    }
444
445    /// Check if the object is empty
446    pub fn is_empty(&self) -> bool {
447        self.0.is_empty()
448    }
449
450    /// Get an iterator over the key-value pairs
451    pub fn iter(&self) -> alloc::collections::btree_map::Iter<'_, SmolStr, Data<S>> {
452        self.0.iter()
453    }
454
455    /// Get an iterator over the keys
456    pub fn keys(&self) -> alloc::collections::btree_map::Keys<'_, SmolStr, Data<S>> {
457        self.0.keys()
458    }
459
460    /// Get the "$type" discriminator field if present and it's a string
461    ///
462    /// This is a shortcut for union type discrimination in AT Protocol.
463    pub fn type_discriminator(&self) -> Option<&str> {
464        self.get("$type")?.as_str()
465    }
466
467    /// Get an iterator over the values
468    pub fn values(&self) -> alloc::collections::btree_map::Values<'_, SmolStr, Data<S>> {
469        self.0.values()
470    }
471}
472
473impl<S> core::ops::Index<&str> for Object<S>
474where
475    S: AsRef<str> + Bos<str>,
476{
477    type Output = Data<S>;
478
479    fn index(&self, key: &str) -> &Self::Output {
480        &self.0[key]
481    }
482}
483
484/// Level 1 deserialization of raw atproto data
485///
486/// Maximally permissive with zero inference for cases where you just want to pass through the data
487/// and don't necessarily care if it's totally valid, or you want to validate later.
488/// E.g. lower-level services, PDS implementations, firehose indexers, relay implementations.
489#[derive(Debug, Clone, PartialEq, Eq)]
490pub enum RawData<'s> {
491    /// Null value
492    Null,
493    /// Boolean value
494    Boolean(bool),
495    /// Signed integer
496    SignedInt(i64),
497    /// Unsigned integer
498    UnsignedInt(u64),
499    /// String value (no type inference)
500    String(CowStr<'s>),
501    /// Raw bytes
502    Bytes(Bytes),
503    /// CID link reference
504    CidLink(Cid<CowStr<'s>>),
505    /// Array of raw values
506    Array(Vec<RawData<'s>>),
507    /// Object/map of raw values
508    Object(BTreeMap<SmolStr, RawData<'s>>),
509    /// Valid blob reference
510    Blob(Blob<CowStr<'s>>),
511    /// Invalid blob structure (captured for debugging)
512    InvalidBlob(Box<RawData<'s>>),
513    /// Invalid number format, generally a floating point number (captured as bytes)
514    InvalidNumber(Bytes),
515    /// Invalid/unknown data (captured as bytes)
516    InvalidData(Bytes),
517}
518
519impl<'d> RawData<'d> {
520    /// Get as object if this is an Object variant
521    pub fn as_object(&self) -> Option<&BTreeMap<SmolStr, RawData<'d>>> {
522        if let RawData::Object(obj) = self {
523            Some(obj)
524        } else {
525            None
526        }
527    }
528
529    /// Get as array if this is an Array variant
530    pub fn as_array(&self) -> Option<&Vec<RawData<'d>>> {
531        if let RawData::Array(arr) = self {
532            Some(arr)
533        } else {
534            None
535        }
536    }
537
538    /// Get as string if this is a String variant
539    pub fn as_str(&self) -> Option<&str> {
540        if let RawData::String(s) = self {
541            Some(s.as_ref())
542        } else {
543            None
544        }
545    }
546
547    /// Get as boolean if this is a Boolean variant
548    pub fn as_boolean(&self) -> Option<bool> {
549        if let RawData::Boolean(b) = self {
550            Some(*b)
551        } else {
552            None
553        }
554    }
555
556    /// get as object if this is an Object variant
557    pub fn as_object_mut(&mut self) -> Option<&mut BTreeMap<SmolStr, RawData<'d>>> {
558        if let RawData::Object(obj) = self {
559            Some(obj)
560        } else {
561            None
562        }
563    }
564
565    /// Get as array if this is an Array variant
566    pub fn as_array_mut(&mut self) -> Option<&mut Vec<RawData<'d>>> {
567        if let RawData::Array(arr) = self {
568            Some(arr)
569        } else {
570            None
571        }
572    }
573
574    /// Get as string if this is a String variant
575    pub fn as_str_mut(&mut self) -> Option<&mut CowStr<'d>> {
576        if let RawData::String(s) = self {
577            Some(s)
578        } else {
579            None
580        }
581    }
582
583    /// Get as boolean if this is a Boolean variant
584    pub fn as_boolean_mut(&mut self) -> Option<&mut bool> {
585        if let RawData::Boolean(b) = self {
586            Some(b)
587        } else {
588            None
589        }
590    }
591
592    /// Check if this is a null value
593    pub fn is_null(&self) -> bool {
594        matches!(self, RawData::Null)
595    }
596
597    /// Get the "$type" discriminator field if this is an object with a string "$type" field
598    ///
599    /// This is a shortcut for union type discrimination in AT Protocol.
600    /// Returns `None` if this is not an object or if the "$type" field is missing/not a string.
601    pub fn type_discriminator(&self) -> Option<&str> {
602        let obj = self.as_object()?;
603        let type_val = obj.get("$type")?;
604        type_val.as_str()
605    }
606
607    /// Serialize to canonical DAG-CBOR bytes for CID computation
608    ///
609    /// This produces the deterministic CBOR encoding used for content-addressing.
610    pub fn to_dag_cbor(
611        &self,
612    ) -> Result<Vec<u8>, serde_ipld_dagcbor::EncodeError<alloc::collections::TryReserveError>> {
613        serde_ipld_dagcbor::to_vec(self)
614    }
615
616    /// Get a value at a path within nested RawData structures
617    ///
618    /// Path syntax:
619    /// - `.field` or `field` - access object field
620    /// - `[0]` - access array index
621    /// - Combined: `embed.images[0].alt`
622    ///
623    /// # Example
624    /// ```ignore
625    /// let data: RawData = ...;
626    /// if let Some(alt_text) = data.get_at_path("embed.images[0].alt") {
627    ///     println!("Alt text: {}", alt_text.as_str().unwrap());
628    /// }
629    /// ```
630    pub fn get_at_path(&'d self, path: &str) -> Option<&'d RawData<'d>> {
631        parse_and_traverse_raw_path(self, path)
632    }
633
634    /// Get a mutable reference to a field at the given path
635    ///
636    /// Uses the same path syntax as [`get_at_path`](Self::get_at_path).
637    pub fn get_at_path_mut<'a>(&'a mut self, path: &str) -> Option<&'a mut RawData<'d>> {
638        parse_and_traverse_raw_path_mut(self, path)
639    }
640
641    /// Convert a CBOR-encoded byte slice into a `RawData` value.
642    /// Parse a Data value from an IPLD value (CBOR)
643    pub fn from_cbor(cbor: &'d Ipld) -> Result<Self, AtDataError> {
644        Ok(match cbor {
645            Ipld::Null => RawData::Null,
646            Ipld::Bool(bool) => RawData::Boolean(*bool),
647            Ipld::Integer(int) => {
648                if *int > i64::MAX as i128 {
649                    RawData::UnsignedInt(*int as u64)
650                } else {
651                    RawData::SignedInt(*int as i64)
652                }
653            }
654            Ipld::Float(_) => {
655                return Err(AtDataError::FloatNotAllowed);
656            }
657            Ipld::String(string) => Self::String(CowStr::Borrowed(&string)),
658            Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())),
659            Ipld::List(iplds) => Self::Array(
660                iplds
661                    .into_iter()
662                    .filter_map(|item| RawData::from_cbor(item).ok())
663                    .collect(),
664            ),
665            Ipld::Map(btree_map) => Self::Object(
666                btree_map
667                    .into_iter()
668                    .filter_map(|(key, value)| {
669                        if let Ok(value) = RawData::from_cbor(value) {
670                            Some((key.to_smolstr(), value))
671                        } else {
672                            None
673                        }
674                    })
675                    .collect(),
676            ),
677            Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)),
678        })
679    }
680}
681
682impl IntoStatic for RawData<'_> {
683    type Output = RawData<'static>;
684
685    fn into_static(self) -> Self::Output {
686        match self {
687            RawData::Null => RawData::Null,
688            RawData::Boolean(b) => RawData::Boolean(b),
689            RawData::SignedInt(i) => RawData::SignedInt(i),
690            RawData::UnsignedInt(u) => RawData::UnsignedInt(u),
691            RawData::String(s) => RawData::String(s.into_static()),
692            RawData::Bytes(b) => RawData::Bytes(b.into_static()),
693            RawData::CidLink(c) => RawData::CidLink(c.into_static()),
694            RawData::Array(a) => RawData::Array(a.into_static()),
695            RawData::Object(o) => RawData::Object(o.into_static()),
696            RawData::Blob(b) => RawData::Blob(b.into_static()),
697            RawData::InvalidBlob(b) => RawData::InvalidBlob(b.into_static()),
698            RawData::InvalidNumber(b) => RawData::InvalidNumber(b.into_static()),
699            RawData::InvalidData(b) => RawData::InvalidData(b.into_static()),
700        }
701    }
702}
703
704/// Deserialize a typed value from a `Data` value
705///
706/// Allows extracting strongly-typed structures from untyped `Data` values,
707/// similar to `serde_json::from_value()`.
708///
709/// # Example
710/// ```
711/// # use jacquard_common::{atproto, Data};
712/// # use jacquard_common::types::value::from_data;
713/// # use serde::Deserialize;
714/// #
715/// #[derive(Deserialize)]
716/// struct Post<'a> {
717///     #[serde(borrow)]
718///     text: &'a str,
719///     #[serde(borrow)]
720///     author: &'a str,
721/// }
722///
723/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
724/// # let data: Data = atproto!({"text": "hello", "author": "alice"});
725/// let post: Post = from_data(&data)?;
726/// # Ok(())
727/// # }
728/// ```
729pub fn from_data<'de, T, S>(data: &'de Data<S>) -> Result<T, DataDeserializerError>
730where
731    T: serde::Deserialize<'de>,
732    S: Bos<str> + AsRef<str> + serde::Deserialize<'de> + core::convert::From<&'de str>,
733{
734    T::deserialize(data)
735}
736
737/// Deserialize a typed value from a `Data` value
738///
739/// Takes ownership rather than borrows. Will allocate.
740pub fn from_data_owned<'de, T, S>(
741    data: Data<S>,
742) -> Result<<T as IntoStatic>::Output, DataDeserializerError>
743where
744    T: serde::Deserialize<'de> + IntoStatic,
745    S: Bos<str> + AsRef<str> + serde::Deserialize<'de> + IntoStatic + core::convert::From<&'de str>,
746    <S as IntoStatic>::Output: Bos<str> + AsRef<str>,
747{
748    T::deserialize(data).map(|d| d.into_static())
749}
750
751/// Deserialize a typed value from a `serde_json::Value`
752///
753/// Returns an owned version, will allocate
754pub fn from_json_value<'de, T>(
755    json: serde_json::Value,
756) -> Result<<T as IntoStatic>::Output, serde_json::Error>
757where
758    T: serde::Deserialize<'de> + IntoStatic,
759{
760    T::deserialize(json).map(IntoStatic::into_static)
761}
762
763/// Deserialize a typed value from cbor bytes
764///
765/// Returns an owned version, will allocate
766pub fn from_cbor<'de, T>(
767    cbor: &'de [u8],
768) -> Result<<T as IntoStatic>::Output, serde_ipld_dagcbor::DecodeError<Infallible>>
769where
770    T: serde::Deserialize<'de> + IntoStatic,
771{
772    serde_ipld_dagcbor::from_slice::<T>(cbor).map(|d| d.into_static())
773}
774
775/// Deserialize a typed value from postcard bytes
776///
777/// Returns an owned version, will allocate
778pub fn from_postcard<'de, T>(bytes: &'de [u8]) -> Result<<T as IntoStatic>::Output, postcard::Error>
779where
780    T: serde::Deserialize<'de> + IntoStatic,
781{
782    postcard::from_bytes::<T>(bytes).map(|d| d.into_static())
783}
784
785/// Deserialize a typed value from a `RawData` value
786///
787/// Allows extracting strongly-typed structures from untyped `RawData` values.
788///
789/// # Example
790/// ```
791/// # use jacquard_common::types::value::{RawData, from_raw_data, to_raw_data};
792/// # use serde::{Serialize, Deserialize};
793/// #
794/// #[derive(Serialize, Deserialize)]
795/// struct Post {
796///     text: String,
797///     author: String,
798/// }
799///
800/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
801/// # let orig = Post { text: "hello".to_string(), author: "alice".to_string() };
802/// # let data = to_raw_data(&orig)?;
803/// let post: Post = from_raw_data(&data)?;
804/// # Ok(())
805/// # }
806/// ```
807pub fn from_raw_data<'de, T>(data: &'de RawData<'de>) -> Result<T, DataDeserializerError>
808where
809    T: serde::Deserialize<'de>,
810{
811    T::deserialize(data)
812}
813
814/// Deserialize a typed value from a `RawData` value
815///
816/// Takes ownership rather than borrows. Will allocate.
817pub fn from_raw_data_owned<'de, T>(data: RawData<'_>) -> Result<T, DataDeserializerError>
818where
819    T: serde::Deserialize<'de>,
820{
821    T::deserialize(data.into_static())
822}
823
824/// Serialize a typed value into a `RawData` value
825///
826/// Allows converting strongly-typed structures into untyped `RawData` values.
827///
828/// # Example
829/// ```
830/// # use jacquard_common::types::value::{RawData, to_raw_data};
831/// # use serde::Serialize;
832/// #
833/// #[derive(Serialize)]
834/// struct Post {
835///     text: String,
836///     likes: i64,
837/// }
838///
839/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
840/// let post = Post { text: "hello".to_string(), likes: 42 };
841/// let data: RawData = to_raw_data(&post)?;
842/// # Ok(())
843/// # }
844/// ```
845pub fn to_raw_data<T>(value: &T) -> Result<RawData<'static>, serde_impl::RawDataSerializerError>
846where
847    T: serde::Serialize,
848{
849    value.serialize(serde_impl::RawDataSerializer)
850}
851
852/// Serialize a typed value into a validated `Data` value with type inference
853///
854/// Combines `to_raw_data()` and validation/type inference in one step.
855///
856/// # Example
857/// ```
858/// # use jacquard_common::types::value::{Data, to_data};
859/// # use serde::Serialize;
860/// #
861/// #[derive(Serialize)]
862/// struct Post {
863///     text: String,
864///     did: String,  // Will be inferred as Did if valid
865/// }
866///
867/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
868/// let post = Post {
869///     text: "hello".to_string(),
870///     did: "did:plc:abc123".to_string()
871/// };
872/// let data: Data = to_data(&post)?;
873/// # Ok(())
874/// # }
875/// ```
876pub fn to_data<'s, T>(value: &T) -> Result<Data<SmolStr>, serde_impl::RawDataSerializerError>
877where
878    T: serde::Serialize,
879{
880    value.serialize(serde_impl::DataSerializer)
881}
882
883/// Parse and traverse a path through nested Data structures
884fn parse_and_traverse_path<'s, S>(data: &'s Data<S>, path: &str) -> Option<&'s Data<S>>
885where
886    S: AsRef<str> + Bos<str>,
887{
888    let mut current = data;
889    let mut path = path.trim_start_matches('.');
890
891    while !path.is_empty() {
892        if path.starts_with('[') {
893            // Array index: [N]
894            let idx_end = path.find(']')?;
895            let idx_str = &path[1..idx_end];
896            let idx: usize = idx_str.parse().ok()?;
897
898            current = current.as_array()?.get(idx)?;
899            path = &path[idx_end + 1..].trim_start_matches('.');
900        } else {
901            // Field access: extract next segment (up to '.' or '[')
902            let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
903            let field = &path[..next_sep];
904
905            if field.is_empty() {
906                break;
907            }
908
909            current = current.as_object()?.get(field)?;
910            path = &path[next_sep..].trim_start_matches('.');
911        }
912    }
913
914    Some(current)
915}
916
917/// Parse and traverse a path through nested RawData structures
918fn parse_and_traverse_raw_path<'d>(data: &'d RawData<'d>, path: &str) -> Option<&'d RawData<'d>> {
919    let mut current = data;
920    let mut path = path.trim_start_matches('.');
921
922    while !path.is_empty() {
923        if path.starts_with('[') {
924            // Array index: [N]
925            let idx_end = path.find(']')?;
926            let idx_str = &path[1..idx_end];
927            let idx: usize = idx_str.parse().ok()?;
928
929            current = current.as_array()?.get(idx)?;
930            path = &path[idx_end + 1..].trim_start_matches('.');
931        } else {
932            // Field access: extract next segment (up to '.' or '[')
933            let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
934            let field = &path[..next_sep];
935
936            if field.is_empty() {
937                break;
938            }
939
940            current = current.as_object()?.get(field as &str)?;
941            path = &path[next_sep..].trim_start_matches('.');
942        }
943    }
944
945    Some(current)
946}
947
948/// Parse and traverse a path through nested Data structures
949fn parse_and_traverse_path_mut<'d, 's, S>(
950    data: &'s mut Data<S>,
951    path: &str,
952) -> Option<&'s mut Data<S>>
953where
954    S: AsRef<str> + Bos<str>,
955{
956    let mut current = data;
957    let mut path = path.trim_start_matches('.');
958
959    while !path.is_empty() {
960        if path.starts_with('[') {
961            // Array index: [N]
962            let idx_end = path.find(']')?;
963            let idx_str = &path[1..idx_end];
964            let idx: usize = idx_str.parse().ok()?;
965
966            current = current.as_array_mut()?.get_mut(idx)?;
967            path = &path[idx_end + 1..].trim_start_matches('.');
968        } else {
969            // Field access: extract next segment (up to '.' or '[')
970            let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
971            let field = &path[..next_sep];
972
973            if field.is_empty() {
974                break;
975            }
976
977            current = current.as_object_mut()?.get_mut(field)?;
978            path = &path[next_sep..].trim_start_matches('.');
979        }
980    }
981
982    Some(current)
983}
984
985/// Parse and traverse a path through nested RawData structures
986fn parse_and_traverse_raw_path_mut<'a, 'd>(
987    data: &'a mut RawData<'d>,
988    path: &str,
989) -> Option<&'a mut RawData<'d>> {
990    let mut current = data;
991    let mut path = path.trim_start_matches('.');
992
993    while !path.is_empty() {
994        if path.starts_with('[') {
995            // Array index: [N]
996            let idx_end = path.find(']')?;
997            let idx_str = &path[1..idx_end];
998            let idx: usize = idx_str.parse().ok()?;
999
1000            current = current.as_array_mut()?.get_mut(idx)?;
1001            path = &path[idx_end + 1..].trim_start_matches('.');
1002        } else {
1003            // Field access: extract next segment (up to '.' or '[')
1004            let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
1005            let field = &path[..next_sep];
1006
1007            if field.is_empty() {
1008                break;
1009            }
1010
1011            current = current.as_object_mut()?.get_mut(field as &str)?;
1012            path = &path[next_sep..].trim_start_matches('.');
1013        }
1014    }
1015
1016    Some(current)
1017}
1018
1019/// Result of a data query operation
1020#[derive(Debug, Clone, PartialEq)]
1021pub enum QueryResult<'s, S>
1022where
1023    S: AsRef<str> + Bos<str>,
1024{
1025    /// Single value expected and found
1026    Single(&'s Data<S>),
1027
1028    /// Multiple values from wildcard or global recursion
1029    Multiple(Vec<QueryMatch<'s, S>>),
1030
1031    /// No matches found
1032    None,
1033}
1034
1035impl<'s, S> QueryResult<'s, S>
1036where
1037    S: AsRef<str> + Bos<str>,
1038{
1039    /// Get single value if available
1040    pub fn single(&self) -> Option<&'s Data<S>> {
1041        match self {
1042            QueryResult::Single(data) => Some(data),
1043            _ => None,
1044        }
1045    }
1046
1047    /// Get multiple matches if available
1048    pub fn multiple(&self) -> Option<&[QueryMatch<'s, S>]> {
1049        match self {
1050            QueryResult::Multiple(matches) => Some(matches),
1051            _ => None,
1052        }
1053    }
1054
1055    /// Get first value regardless of result type
1056    pub fn first(&self) -> Option<&'s Data<S>> {
1057        match self {
1058            QueryResult::Single(data) => Some(data),
1059            QueryResult::Multiple(matches) => matches.first().and_then(|m| m.value),
1060            QueryResult::None => None,
1061        }
1062    }
1063
1064    /// Check if any results were found
1065    pub fn is_empty(&self) -> bool {
1066        matches!(self, QueryResult::None)
1067    }
1068
1069    /// Get all values as an iterator (flattens single/multiple)
1070    pub fn values(&self) -> impl Iterator<Item = &'s Data<S>> {
1071        match self {
1072            QueryResult::Single(data) => vec![*data].into_iter(),
1073            QueryResult::Multiple(matches) => matches
1074                .iter()
1075                .filter_map(|m| m.value)
1076                .collect::<Vec<_>>()
1077                .into_iter(),
1078            QueryResult::None => vec![].into_iter(),
1079        }
1080    }
1081}
1082
1083/// A single match from a query operation
1084#[derive(Debug, Clone, PartialEq)]
1085pub struct QueryMatch<'s, S>
1086where
1087    S: AsRef<str> + Bos<str>,
1088{
1089    /// Path where this value was found (e.g., "actors\[0\].handle")
1090    pub path: SmolStr,
1091    /// The value (None if field was missing during wildcard iteration)
1092    pub value: Option<&'s Data<S>>,
1093}
1094
1095/// Query pattern segment
1096#[derive(Debug, Clone, PartialEq)]
1097enum QuerySegment {
1098    /// Exact field name
1099    Field(SmolStr),
1100    /// Wildcard [..]
1101    Wildcard,
1102    /// Scoped recursion ..field
1103    ScopedRecursion(SmolStr),
1104    /// Global recursion ...field
1105    GlobalRecursion(SmolStr),
1106}
1107
1108/// Parse a query pattern into segments
1109fn parse_query_pattern(pattern: &str) -> Vec<QuerySegment> {
1110    let mut segments = Vec::new();
1111    let mut remaining = pattern;
1112
1113    // Skip single leading dot if present
1114    if remaining.starts_with('.') && !remaining.starts_with("..") {
1115        remaining = &remaining[1..];
1116    }
1117
1118    while !remaining.is_empty() {
1119        if remaining.starts_with("...") {
1120            // Global recursion
1121            let rest = &remaining[3..];
1122            let end = rest.find(&['.', '['][..]).unwrap_or(rest.len());
1123            let field = SmolStr::new(&rest[..end]);
1124            segments.push(QuerySegment::GlobalRecursion(field));
1125            remaining = &rest[end..];
1126            // Skip single dot separator
1127            if remaining.starts_with('.') && !remaining.starts_with("..") {
1128                remaining = &remaining[1..];
1129            }
1130        } else if remaining.starts_with("..") {
1131            // Scoped recursion
1132            let rest = &remaining[2..];
1133            let end = rest.find(&['.', '['][..]).unwrap_or(rest.len());
1134            let field = SmolStr::new(&rest[..end]);
1135            segments.push(QuerySegment::ScopedRecursion(field));
1136            remaining = &rest[end..];
1137            // Skip single dot separator
1138            if remaining.starts_with('.') && !remaining.starts_with("..") {
1139                remaining = &remaining[1..];
1140            }
1141        } else if remaining.starts_with("[..]") {
1142            // Wildcard
1143            segments.push(QuerySegment::Wildcard);
1144            remaining = &remaining[4..];
1145            // Skip single dot separator
1146            if remaining.starts_with('.') && !remaining.starts_with("..") {
1147                remaining = &remaining[1..];
1148            }
1149        } else {
1150            // Regular field
1151            let end = remaining.find(&['.', '['][..]).unwrap_or(remaining.len());
1152            let field = &remaining[..end];
1153            if !field.is_empty() {
1154                segments.push(QuerySegment::Field(SmolStr::new(field)));
1155            }
1156            remaining = &remaining[end..];
1157            // Skip single dot separator
1158            if remaining.starts_with('.') && !remaining.starts_with("..") {
1159                remaining = &remaining[1..];
1160            }
1161        }
1162    }
1163
1164    segments
1165}
1166
1167/// Execute a query on data
1168fn query_data<'s, S>(data: &'s Data<S>, pattern: &str) -> QueryResult<'s, S>
1169where
1170    S: AsRef<str> + Bos<str>,
1171{
1172    let segments = parse_query_pattern(pattern);
1173    if segments.is_empty() {
1174        return QueryResult::None;
1175    }
1176
1177    let mut results = vec![QueryMatch {
1178        path: SmolStr::new_static(""),
1179        value: Some(data),
1180    }];
1181
1182    // Determine result type based on segment types before consuming segments
1183    let has_wildcard = segments.iter().any(|s| matches!(s, QuerySegment::Wildcard));
1184    let has_global = segments
1185        .iter()
1186        .any(|s| matches!(s, QuerySegment::GlobalRecursion(_)));
1187
1188    for segment in segments {
1189        results = execute_segment(&results, &segment);
1190        if results.is_empty() {
1191            return QueryResult::None;
1192        }
1193    }
1194
1195    if has_wildcard || has_global || results.len() > 1 {
1196        QueryResult::Multiple(results)
1197    } else if results.len() == 1 {
1198        if let Some(value) = results[0].value {
1199            QueryResult::Single(value)
1200        } else {
1201            QueryResult::None
1202        }
1203    } else {
1204        QueryResult::None
1205    }
1206}
1207
1208/// Execute a single segment on current results
1209fn execute_segment<'s, S>(
1210    current: &[QueryMatch<'s, S>],
1211    segment: &QuerySegment,
1212) -> Vec<QueryMatch<'s, S>>
1213where
1214    S: AsRef<str> + Bos<str>,
1215{
1216    let mut next = Vec::new();
1217
1218    for qm in current {
1219        let Some(data) = qm.value else { continue };
1220
1221        match segment {
1222            QuerySegment::Field(field) => {
1223                if let Some(obj) = data.as_object() {
1224                    if let Some(value) = obj.get(field.as_str()) {
1225                        let new_path = append_path(&qm.path, field.as_str());
1226                        next.push(QueryMatch {
1227                            path: new_path,
1228                            value: Some(value),
1229                        });
1230                    }
1231                }
1232            }
1233
1234            QuerySegment::Wildcard => match data {
1235                Data::Array(arr) => {
1236                    for (idx, item) in arr.iter().enumerate() {
1237                        let new_path = append_path(&qm.path, &format!("[{}]", idx));
1238                        next.push(QueryMatch {
1239                            path: new_path,
1240                            value: Some(item),
1241                        });
1242                    }
1243                }
1244                Data::Object(obj) => {
1245                    for (key, value) in obj.iter() {
1246                        let new_path = append_path(&qm.path, key.as_str());
1247                        next.push(QueryMatch {
1248                            path: new_path,
1249                            value: Some(value),
1250                        });
1251                    }
1252                }
1253                _ => {}
1254            },
1255
1256            QuerySegment::ScopedRecursion(field) => {
1257                if let Some(found) = find_field_recursive(data, field.as_str(), &qm.path) {
1258                    next.push(found);
1259                }
1260            }
1261
1262            QuerySegment::GlobalRecursion(field) => {
1263                find_all_fields_recursive(data, field.as_str(), &qm.path, &mut next);
1264            }
1265        }
1266    }
1267
1268    next
1269}
1270
1271/// Recursively find first occurrence of a field (scoped recursion)
1272fn find_field_recursive<'s, S>(
1273    data: &'s Data<S>,
1274    field: &str,
1275    base_path: &SmolStr,
1276) -> Option<QueryMatch<'s, S>>
1277where
1278    S: AsRef<str> + Bos<str>,
1279{
1280    match data {
1281        Data::Object(obj) => {
1282            // Check direct children first
1283            if let Some(value) = obj.get(field) {
1284                let new_path = append_path(base_path, field);
1285                return Some(QueryMatch {
1286                    path: new_path,
1287                    value: Some(value),
1288                });
1289            }
1290
1291            // Recurse into nested objects
1292            for (key, value) in obj.iter() {
1293                let new_path = append_path(base_path, key.as_str());
1294                if let Some(found) = find_field_recursive(value, field, &new_path) {
1295                    return Some(found);
1296                }
1297            }
1298        }
1299        Data::Array(arr) => {
1300            for (idx, item) in arr.iter().enumerate() {
1301                let new_path = append_path(base_path, &format!("[{}]", idx));
1302                if let Some(found) = find_field_recursive(item, field, &new_path) {
1303                    return Some(found);
1304                }
1305            }
1306        }
1307        _ => {}
1308    }
1309
1310    None
1311}
1312
1313/// Recursively find all occurrences of a field (global recursion)
1314fn find_all_fields_recursive<'s, S>(
1315    data: &'s Data<S>,
1316    field: &str,
1317    base_path: &SmolStr,
1318    results: &mut Vec<QueryMatch<'s, S>>,
1319) where
1320    S: AsRef<str> + Bos<str>,
1321{
1322    match data {
1323        Data::Object(obj) => {
1324            // Check direct children
1325            if let Some(value) = obj.get(field) {
1326                let new_path = append_path(base_path, field);
1327                results.push(QueryMatch {
1328                    path: new_path,
1329                    value: Some(value),
1330                });
1331            }
1332
1333            // Recurse into all nested values
1334            for (key, value) in obj.iter() {
1335                let new_path = append_path(base_path, key.as_str());
1336                find_all_fields_recursive(value, field, &new_path, results);
1337            }
1338        }
1339        Data::Array(arr) => {
1340            for (idx, item) in arr.iter().enumerate() {
1341                let new_path = append_path(base_path, &format!("[{}]", idx));
1342                find_all_fields_recursive(item, field, &new_path, results);
1343            }
1344        }
1345        _ => {}
1346    }
1347}
1348
1349/// Append a segment to a path
1350fn append_path(base: &SmolStr, segment: &str) -> SmolStr {
1351    if base.is_empty() {
1352        SmolStr::new(segment)
1353    } else if segment.starts_with('[') {
1354        SmolStr::new(format!("{}{}", base, segment))
1355    } else {
1356        SmolStr::new(format!("{}.{}", base, segment))
1357    }
1358}