use crate::{
IntoStatic,
types::{DataModelType, LexiconStringType, UriType, blob::Blob, string::*},
};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use bytes::Bytes;
use core::convert::Infallible;
use ipld_core::ipld::Ipld;
use smol_str::{SmolStr, ToSmolStr};
pub mod convert;
pub mod parsing;
pub mod serde_impl;
pub use serde_impl::{DataDeserializerError, RawDataSerializerError};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Data<'s> {
Null,
Boolean(bool),
Integer(i64),
String(AtprotoStr<'s>),
Bytes(Bytes),
CidLink(Cid<'s>),
Array(Array<'s>),
Object(Object<'s>),
Blob(Blob<'s>),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
#[non_exhaustive]
pub enum AtDataError {
#[error("floating point numbers not allowed in AT protocol data")]
FloatNotAllowed,
}
impl<'s> Data<'s> {
pub fn data_type(&self) -> DataModelType {
match self {
Data::Null => DataModelType::Null,
Data::Boolean(_) => DataModelType::Boolean,
Data::Integer(_) => DataModelType::Integer,
Data::String(s) => match s {
AtprotoStr::Datetime(_) => DataModelType::String(LexiconStringType::Datetime),
AtprotoStr::Language(_) => DataModelType::String(LexiconStringType::Language),
AtprotoStr::Tid(_) => DataModelType::String(LexiconStringType::Tid),
AtprotoStr::Nsid(_) => DataModelType::String(LexiconStringType::Nsid),
AtprotoStr::Did(_) => DataModelType::String(LexiconStringType::Did),
AtprotoStr::Handle(_) => DataModelType::String(LexiconStringType::Handle),
AtprotoStr::AtIdentifier(_) => {
DataModelType::String(LexiconStringType::AtIdentifier)
}
AtprotoStr::AtUri(_) => DataModelType::String(LexiconStringType::AtUri),
AtprotoStr::Uri(uri) => match uri {
UriValue::Did(_) => DataModelType::String(LexiconStringType::Uri(UriType::Did)),
UriValue::At(_) => DataModelType::String(LexiconStringType::Uri(UriType::At)),
UriValue::Https(_) => {
DataModelType::String(LexiconStringType::Uri(UriType::Https))
}
UriValue::Wss(_) => DataModelType::String(LexiconStringType::Uri(UriType::Wss)),
UriValue::Cid(_) => DataModelType::String(LexiconStringType::Uri(UriType::Cid)),
UriValue::Any(_) => DataModelType::String(LexiconStringType::Uri(UriType::Any)),
},
AtprotoStr::Cid(_) => DataModelType::String(LexiconStringType::Cid),
AtprotoStr::RecordKey(_) => DataModelType::String(LexiconStringType::RecordKey),
AtprotoStr::String(_) => DataModelType::String(LexiconStringType::String),
},
Data::Bytes(_) => DataModelType::Bytes,
Data::CidLink(_) => DataModelType::CidLink,
Data::Array(_) => DataModelType::Array,
Data::Object(_) => DataModelType::Object,
Data::Blob(_) => DataModelType::Blob,
}
}
pub fn from_json(json: &'s serde_json::Value) -> Result<Self, AtDataError> {
Ok(if let Some(value) = json.as_bool() {
Self::Boolean(value)
} else if let Some(value) = json.as_i64() {
Self::Integer(value)
} else if let Some(value) = json.as_str() {
Self::String(parsing::parse_string(value))
} else if let Some(value) = json.as_array() {
Self::Array(Array::from_json(value)?)
} else if let Some(value) = json.as_object() {
Object::from_json(value)?
} else if json.is_f64() {
return Err(AtDataError::FloatNotAllowed);
} else {
Self::Null
})
}
pub fn from_json_owned(json: serde_json::Value) -> Result<Data<'static>, AtDataError> {
Data::from_json(&json).map(|data| data.into_static())
}
pub fn as_object(&self) -> Option<&Object<'s>> {
if let Data::Object(obj) = self {
Some(obj)
} else {
None
}
}
pub fn as_array(&self) -> Option<&Array<'s>> {
if let Data::Array(arr) = self {
Some(arr)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let Data::String(s) = self {
Some(s.as_str())
} else {
None
}
}
pub fn as_object_mut<'a>(&'a mut self) -> Option<&'a mut Object<'s>> {
if let Data::Object(obj) = self {
Some(obj)
} else {
None
}
}
pub fn as_array_mut<'a>(&'a mut self) -> Option<&'a mut Array<'s>> {
if let Data::Array(arr) = self {
Some(arr)
} else {
None
}
}
pub fn as_str_mut(&'s mut self) -> Option<&'s mut AtprotoStr<'s>> {
if let Data::String(s) = self {
Some(s)
} else {
None
}
}
pub fn as_integer_mut(&mut self) -> Option<&mut i64> {
if let Data::Integer(i) = self {
Some(i)
} else {
None
}
}
pub fn as_boolean_mut(&mut self) -> Option<&mut bool> {
if let Data::Boolean(b) = self {
Some(b)
} else {
None
}
}
pub fn as_integer(&self) -> Option<i64> {
if let Data::Integer(i) = self {
Some(*i)
} else {
None
}
}
pub fn as_boolean(&self) -> Option<bool> {
if let Data::Boolean(b) = self {
Some(*b)
} else {
None
}
}
pub fn is_null(&self) -> bool {
matches!(self, Data::Null)
}
pub fn type_discriminator(&self) -> Option<&str> {
self.as_object()?.type_discriminator()
}
pub fn to_dag_cbor(
&self,
) -> Result<Vec<u8>, serde_ipld_dagcbor::EncodeError<alloc::collections::TryReserveError>> {
serde_ipld_dagcbor::to_vec(self)
}
pub fn get_at_path(&'s self, path: &str) -> Option<&'s Data<'s>> {
parse_and_traverse_path(self, path)
}
pub fn get_at_path_mut(&mut self, path: &str) -> Option<&mut Data<'s>> {
parse_and_traverse_path_mut(self, path)
}
pub fn set_at_path(&mut self, path: &str, new_data: Data<'_>) -> bool {
if let Some(data) = parse_and_traverse_path_mut(self, path) {
*data = new_data.into_static();
true
} else {
false
}
}
pub fn query(&'s self, pattern: &str) -> QueryResult<'s> {
query_data(self, pattern)
}
pub fn from_cbor(cbor: &'s Ipld) -> Result<Self, AtDataError> {
Ok(match cbor {
Ipld::Null => Data::Null,
Ipld::Bool(bool) => Data::Boolean(*bool),
Ipld::Integer(int) => Data::Integer(*int as i64),
Ipld::Float(_) => {
return Err(AtDataError::FloatNotAllowed);
}
Ipld::String(string) => Self::String(parsing::parse_string(string)),
Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())),
Ipld::List(iplds) => Self::Array(Array::from_cbor(iplds)?),
Ipld::Map(btree_map) => Object::from_cbor(btree_map)?,
Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)),
})
}
}
impl IntoStatic for Data<'_> {
type Output = Data<'static>;
fn into_static(self) -> Data<'static> {
match self {
Data::Null => Data::Null,
Data::Boolean(bool) => Data::Boolean(bool),
Data::Integer(int) => Data::Integer(int),
Data::String(string) => Data::String(string.into_static()),
Data::Bytes(bytes) => Data::Bytes(bytes),
Data::Array(array) => Data::Array(array.into_static()),
Data::Object(object) => Data::Object(object.into_static()),
Data::CidLink(cid) => Data::CidLink(cid.into_static()),
Data::Blob(blob) => Data::Blob(blob.into_static()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Array<'s>(pub Vec<Data<'s>>);
impl IntoStatic for Array<'_> {
type Output = Array<'static>;
fn into_static(self) -> Array<'static> {
Array(self.0.into_static())
}
}
impl<'s> Array<'s> {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get(&self, index: usize) -> Option<&Data<'s>> {
self.0.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Data<'s>> {
self.0.get_mut(index)
}
pub fn iter(&self) -> core::slice::Iter<'_, Data<'s>> {
self.0.iter()
}
pub fn from_json(json: &'s Vec<serde_json::Value>) -> Result<Self, AtDataError> {
let mut array = Vec::with_capacity(json.len());
for item in json {
array.push(Data::from_json(item)?);
}
Ok(Self(array))
}
pub fn from_cbor(cbor: &'s Vec<Ipld>) -> Result<Self, AtDataError> {
let mut array = Vec::with_capacity(cbor.len());
for item in cbor {
array.push(Data::from_cbor(item)?);
}
Ok(Self(array))
}
}
impl<'s> core::ops::Index<usize> for Array<'s> {
type Output = Data<'s>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Object<'s>(pub BTreeMap<SmolStr, Data<'s>>);
impl IntoStatic for Object<'_> {
type Output = Object<'static>;
fn into_static(self) -> Object<'static> {
Object(self.0.into_static())
}
}
impl<'s> Object<'s> {
pub fn get(&self, key: &str) -> Option<&Data<'s>> {
self.0.get(key)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut Data<'s>> {
self.0.get_mut(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.0.contains_key(key)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> alloc::collections::btree_map::Iter<'_, SmolStr, Data<'s>> {
self.0.iter()
}
pub fn keys(&self) -> alloc::collections::btree_map::Keys<'_, SmolStr, Data<'s>> {
self.0.keys()
}
pub fn type_discriminator(&self) -> Option<&str> {
self.get("$type")?.as_str()
}
pub fn values(&self) -> alloc::collections::btree_map::Values<'_, SmolStr, Data<'s>> {
self.0.values()
}
pub fn from_json(
json: &'s serde_json::Map<String, serde_json::Value>,
) -> Result<Data<'s>, AtDataError> {
if let Some(type_field) = json.get("$type").and_then(|v| v.as_str()) {
if parsing::infer_from_type(type_field) == DataModelType::Blob {
if let Some(blob) = parsing::json_to_blob(json) {
return Ok(Data::Blob(blob));
}
}
}
let mut map = BTreeMap::new();
for (key, value) in json {
if key == "$type" {
map.insert(key.to_smolstr(), Data::from_json(value)?);
}
match parsing::string_key_type_guess(key) {
DataModelType::Null if value.is_null() => {
map.insert(key.to_smolstr(), Data::Null);
}
DataModelType::Boolean if value.is_boolean() => {
map.insert(key.to_smolstr(), Data::Boolean(value.as_bool().unwrap()));
}
DataModelType::Integer if value.is_i64() => {
map.insert(key.to_smolstr(), Data::Integer(value.as_i64().unwrap()));
}
DataModelType::Bytes if value.is_string() => {
map.insert(
key.to_smolstr(),
parsing::decode_bytes(value.as_str().unwrap()),
);
}
DataModelType::CidLink => {
if let Some(value) = value.as_object() {
if let Some(value) = value.get("$link").and_then(|v| v.as_str()) {
map.insert(key.to_smolstr(), Data::CidLink(Cid::Str(value.into())));
} else {
map.insert(key.to_smolstr(), Object::from_json(value)?);
}
} else {
map.insert(key.to_smolstr(), Data::from_json(value)?);
}
}
DataModelType::Blob if value.is_object() => {
map.insert(
key.to_smolstr(),
Object::from_json(value.as_object().unwrap())?,
);
}
DataModelType::Array if value.is_array() => {
map.insert(
key.to_smolstr(),
Data::Array(Array::from_json(value.as_array().unwrap())?),
);
}
DataModelType::Object if value.is_object() => {
map.insert(
key.to_smolstr(),
Object::from_json(value.as_object().unwrap())?,
);
}
DataModelType::String(string_type) if value.is_string() => {
parsing::insert_string(&mut map, key, value.as_str().unwrap(), string_type)?;
}
_ => {
map.insert(key.to_smolstr(), Data::from_json(value)?);
}
}
}
Ok(Data::Object(Object(map)))
}
pub fn from_cbor(cbor: &'s BTreeMap<String, Ipld>) -> Result<Data<'s>, AtDataError> {
if let Some(Ipld::String(type_field)) = cbor.get("$type") {
if parsing::infer_from_type(type_field) == DataModelType::Blob {
if let Some(blob) = parsing::cbor_to_blob(cbor) {
return Ok(Data::Blob(blob));
}
}
}
let mut map = BTreeMap::new();
for (key, value) in cbor {
if key == "$type" {
map.insert(key.to_smolstr(), Data::from_cbor(value)?);
}
match (parsing::string_key_type_guess(key), value) {
(DataModelType::Null, Ipld::Null) => {
map.insert(key.to_smolstr(), Data::Null);
}
(DataModelType::Boolean, Ipld::Bool(value)) => {
map.insert(key.to_smolstr(), Data::Boolean(*value));
}
(DataModelType::Integer, Ipld::Integer(int)) => {
map.insert(key.to_smolstr(), Data::Integer(*int as i64));
}
(DataModelType::Bytes, Ipld::Bytes(value)) => {
map.insert(key.to_smolstr(), Data::Bytes(Bytes::copy_from_slice(value)));
}
(DataModelType::Blob, Ipld::Map(value)) => {
map.insert(key.to_smolstr(), Object::from_cbor(value)?);
}
(DataModelType::Array, Ipld::List(value)) => {
map.insert(key.to_smolstr(), Data::Array(Array::from_cbor(value)?));
}
(DataModelType::Object, Ipld::Map(value)) => {
map.insert(key.to_smolstr(), Object::from_cbor(value)?);
}
(DataModelType::String(string_type), Ipld::String(value)) => {
parsing::insert_string(&mut map, key, value, string_type)?;
}
_ => {
map.insert(key.to_smolstr(), Data::from_cbor(value)?);
}
}
}
Ok(Data::Object(Object(map)))
}
}
impl<'s> core::ops::Index<&str> for Object<'s> {
type Output = Data<'s>;
fn index(&self, key: &str) -> &Self::Output {
&self.0[key]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RawData<'s> {
Null,
Boolean(bool),
SignedInt(i64),
UnsignedInt(u64),
String(CowStr<'s>),
Bytes(Bytes),
CidLink(Cid<'s>),
Array(Vec<RawData<'s>>),
Object(BTreeMap<SmolStr, RawData<'s>>),
Blob(Blob<'s>),
InvalidBlob(Box<RawData<'s>>),
InvalidNumber(Bytes),
InvalidData(Bytes),
}
impl<'d> RawData<'d> {
pub fn as_object(&self) -> Option<&BTreeMap<SmolStr, RawData<'d>>> {
if let RawData::Object(obj) = self {
Some(obj)
} else {
None
}
}
pub fn as_array(&self) -> Option<&Vec<RawData<'d>>> {
if let RawData::Array(arr) = self {
Some(arr)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let RawData::String(s) = self {
Some(s.as_ref())
} else {
None
}
}
pub fn as_boolean(&self) -> Option<bool> {
if let RawData::Boolean(b) = self {
Some(*b)
} else {
None
}
}
pub fn as_object_mut(&mut self) -> Option<&mut BTreeMap<SmolStr, RawData<'d>>> {
if let RawData::Object(obj) = self {
Some(obj)
} else {
None
}
}
pub fn as_array_mut(&mut self) -> Option<&mut Vec<RawData<'d>>> {
if let RawData::Array(arr) = self {
Some(arr)
} else {
None
}
}
pub fn as_str_mut(&mut self) -> Option<&mut CowStr<'d>> {
if let RawData::String(s) = self {
Some(s)
} else {
None
}
}
pub fn as_boolean_mut(&mut self) -> Option<&mut bool> {
if let RawData::Boolean(b) = self {
Some(b)
} else {
None
}
}
pub fn is_null(&self) -> bool {
matches!(self, RawData::Null)
}
pub fn type_discriminator(&self) -> Option<&str> {
let obj = self.as_object()?;
let type_val = obj.get("$type")?;
type_val.as_str()
}
pub fn to_dag_cbor(
&self,
) -> Result<Vec<u8>, serde_ipld_dagcbor::EncodeError<alloc::collections::TryReserveError>> {
serde_ipld_dagcbor::to_vec(self)
}
pub fn get_at_path(&'d self, path: &str) -> Option<&'d RawData<'d>> {
parse_and_traverse_raw_path(self, path)
}
pub fn get_at_path_mut<'a>(&'a mut self, path: &str) -> Option<&'a mut RawData<'d>> {
parse_and_traverse_raw_path_mut(self, path)
}
pub fn from_cbor(cbor: &'d Ipld) -> Result<Self, AtDataError> {
Ok(match cbor {
Ipld::Null => RawData::Null,
Ipld::Bool(bool) => RawData::Boolean(*bool),
Ipld::Integer(int) => {
if *int > i64::MAX as i128 {
RawData::UnsignedInt(*int as u64)
} else {
RawData::SignedInt(*int as i64)
}
}
Ipld::Float(_) => {
return Err(AtDataError::FloatNotAllowed);
}
Ipld::String(string) => Self::String(CowStr::Borrowed(&string)),
Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())),
Ipld::List(iplds) => Self::Array(
iplds
.into_iter()
.filter_map(|item| RawData::from_cbor(item).ok())
.collect(),
),
Ipld::Map(btree_map) => Self::Object(
btree_map
.into_iter()
.filter_map(|(key, value)| {
if let Ok(value) = RawData::from_cbor(value) {
Some((key.to_smolstr(), value))
} else {
None
}
})
.collect(),
),
Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)),
})
}
}
impl IntoStatic for RawData<'_> {
type Output = RawData<'static>;
fn into_static(self) -> Self::Output {
match self {
RawData::Null => RawData::Null,
RawData::Boolean(b) => RawData::Boolean(b),
RawData::SignedInt(i) => RawData::SignedInt(i),
RawData::UnsignedInt(u) => RawData::UnsignedInt(u),
RawData::String(s) => RawData::String(s.into_static()),
RawData::Bytes(b) => RawData::Bytes(b.into_static()),
RawData::CidLink(c) => RawData::CidLink(c.into_static()),
RawData::Array(a) => RawData::Array(a.into_static()),
RawData::Object(o) => RawData::Object(o.into_static()),
RawData::Blob(b) => RawData::Blob(b.into_static()),
RawData::InvalidBlob(b) => RawData::InvalidBlob(b.into_static()),
RawData::InvalidNumber(b) => RawData::InvalidNumber(b.into_static()),
RawData::InvalidData(b) => RawData::InvalidData(b.into_static()),
}
}
}
pub fn from_data<'de, T>(data: &'de Data<'de>) -> Result<T, DataDeserializerError>
where
T: serde::Deserialize<'de>,
{
T::deserialize(data)
}
pub fn from_data_owned<'de, T>(data: Data<'_>) -> Result<T, DataDeserializerError>
where
T: serde::Deserialize<'de>,
{
T::deserialize(data.into_static())
}
pub fn from_json_value<'de, T>(
json: serde_json::Value,
) -> Result<<T as IntoStatic>::Output, serde_json::Error>
where
T: serde::Deserialize<'de> + IntoStatic,
{
T::deserialize(json).map(IntoStatic::into_static)
}
pub fn from_cbor<'de, T>(
cbor: &'de [u8],
) -> Result<<T as IntoStatic>::Output, serde_ipld_dagcbor::DecodeError<Infallible>>
where
T: serde::Deserialize<'de> + IntoStatic,
{
serde_ipld_dagcbor::from_slice::<T>(cbor).map(|d| d.into_static())
}
pub fn from_postcard<'de, T>(bytes: &'de [u8]) -> Result<<T as IntoStatic>::Output, postcard::Error>
where
T: serde::Deserialize<'de> + IntoStatic,
{
postcard::from_bytes::<T>(bytes).map(|d| d.into_static())
}
pub fn from_raw_data<'de, T>(data: &'de RawData<'de>) -> Result<T, DataDeserializerError>
where
T: serde::Deserialize<'de>,
{
T::deserialize(data)
}
pub fn from_raw_data_owned<'de, T>(data: RawData<'_>) -> Result<T, DataDeserializerError>
where
T: serde::Deserialize<'de>,
{
T::deserialize(data.into_static())
}
pub fn to_raw_data<T>(value: &T) -> Result<RawData<'static>, serde_impl::RawDataSerializerError>
where
T: serde::Serialize,
{
value.serialize(serde_impl::RawDataSerializer)
}
pub fn to_data<T>(value: &T) -> Result<Data<'static>, convert::ConversionError>
where
T: serde::Serialize,
{
let raw = to_raw_data(value).map_err(|e| convert::ConversionError::InvalidRawData {
message: e.to_string(),
})?;
raw.try_into()
}
fn parse_and_traverse_path<'s>(data: &'s Data<'s>, path: &str) -> Option<&'s Data<'s>> {
let mut current = data;
let mut path = path.trim_start_matches('.');
while !path.is_empty() {
if path.starts_with('[') {
let idx_end = path.find(']')?;
let idx_str = &path[1..idx_end];
let idx: usize = idx_str.parse().ok()?;
current = current.as_array()?.get(idx)?;
path = &path[idx_end + 1..].trim_start_matches('.');
} else {
let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
let field = &path[..next_sep];
if field.is_empty() {
break;
}
current = current.as_object()?.get(field)?;
path = &path[next_sep..].trim_start_matches('.');
}
}
Some(current)
}
fn parse_and_traverse_raw_path<'d>(data: &'d RawData<'d>, path: &str) -> Option<&'d RawData<'d>> {
let mut current = data;
let mut path = path.trim_start_matches('.');
while !path.is_empty() {
if path.starts_with('[') {
let idx_end = path.find(']')?;
let idx_str = &path[1..idx_end];
let idx: usize = idx_str.parse().ok()?;
current = current.as_array()?.get(idx)?;
path = &path[idx_end + 1..].trim_start_matches('.');
} else {
let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
let field = &path[..next_sep];
if field.is_empty() {
break;
}
current = current.as_object()?.get(field as &str)?;
path = &path[next_sep..].trim_start_matches('.');
}
}
Some(current)
}
fn parse_and_traverse_path_mut<'d, 's>(
data: &'s mut Data<'d>,
path: &str,
) -> Option<&'s mut Data<'d>> {
let mut current = data;
let mut path = path.trim_start_matches('.');
while !path.is_empty() {
if path.starts_with('[') {
let idx_end = path.find(']')?;
let idx_str = &path[1..idx_end];
let idx: usize = idx_str.parse().ok()?;
current = current.as_array_mut()?.get_mut(idx)?;
path = &path[idx_end + 1..].trim_start_matches('.');
} else {
let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
let field = &path[..next_sep];
if field.is_empty() {
break;
}
current = current.as_object_mut()?.get_mut(field)?;
path = &path[next_sep..].trim_start_matches('.');
}
}
Some(current)
}
fn parse_and_traverse_raw_path_mut<'a, 'd>(
data: &'a mut RawData<'d>,
path: &str,
) -> Option<&'a mut RawData<'d>> {
let mut current = data;
let mut path = path.trim_start_matches('.');
while !path.is_empty() {
if path.starts_with('[') {
let idx_end = path.find(']')?;
let idx_str = &path[1..idx_end];
let idx: usize = idx_str.parse().ok()?;
current = current.as_array_mut()?.get_mut(idx)?;
path = &path[idx_end + 1..].trim_start_matches('.');
} else {
let next_sep = path.find(&['.', '['][..]).unwrap_or(path.len());
let field = &path[..next_sep];
if field.is_empty() {
break;
}
current = current.as_object_mut()?.get_mut(field as &str)?;
path = &path[next_sep..].trim_start_matches('.');
}
}
Some(current)
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryResult<'s> {
Single(&'s Data<'s>),
Multiple(Vec<QueryMatch<'s>>),
None,
}
impl<'s> QueryResult<'s> {
pub fn single(&self) -> Option<&'s Data<'s>> {
match self {
QueryResult::Single(data) => Some(data),
_ => None,
}
}
pub fn multiple(&self) -> Option<&[QueryMatch<'s>]> {
match self {
QueryResult::Multiple(matches) => Some(matches),
_ => None,
}
}
pub fn first(&self) -> Option<&'s Data<'s>> {
match self {
QueryResult::Single(data) => Some(data),
QueryResult::Multiple(matches) => matches.first().and_then(|m| m.value),
QueryResult::None => None,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, QueryResult::None)
}
pub fn values(&self) -> impl Iterator<Item = &'s Data<'s>> {
match self {
QueryResult::Single(data) => vec![*data].into_iter(),
QueryResult::Multiple(matches) => matches
.iter()
.filter_map(|m| m.value)
.collect::<Vec<_>>()
.into_iter(),
QueryResult::None => vec![].into_iter(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryMatch<'s> {
pub path: SmolStr,
pub value: Option<&'s Data<'s>>,
}
#[derive(Debug, Clone, PartialEq)]
enum QuerySegment {
Field(SmolStr),
Wildcard,
ScopedRecursion(SmolStr),
GlobalRecursion(SmolStr),
}
fn parse_query_pattern(pattern: &str) -> Vec<QuerySegment> {
let mut segments = Vec::new();
let mut remaining = pattern;
if remaining.starts_with('.') && !remaining.starts_with("..") {
remaining = &remaining[1..];
}
while !remaining.is_empty() {
if remaining.starts_with("...") {
let rest = &remaining[3..];
let end = rest.find(&['.', '['][..]).unwrap_or(rest.len());
let field = SmolStr::new(&rest[..end]);
segments.push(QuerySegment::GlobalRecursion(field));
remaining = &rest[end..];
if remaining.starts_with('.') && !remaining.starts_with("..") {
remaining = &remaining[1..];
}
} else if remaining.starts_with("..") {
let rest = &remaining[2..];
let end = rest.find(&['.', '['][..]).unwrap_or(rest.len());
let field = SmolStr::new(&rest[..end]);
segments.push(QuerySegment::ScopedRecursion(field));
remaining = &rest[end..];
if remaining.starts_with('.') && !remaining.starts_with("..") {
remaining = &remaining[1..];
}
} else if remaining.starts_with("[..]") {
segments.push(QuerySegment::Wildcard);
remaining = &remaining[4..];
if remaining.starts_with('.') && !remaining.starts_with("..") {
remaining = &remaining[1..];
}
} else {
let end = remaining.find(&['.', '['][..]).unwrap_or(remaining.len());
let field = &remaining[..end];
if !field.is_empty() {
segments.push(QuerySegment::Field(SmolStr::new(field)));
}
remaining = &remaining[end..];
if remaining.starts_with('.') && !remaining.starts_with("..") {
remaining = &remaining[1..];
}
}
}
segments
}
fn query_data<'s>(data: &'s Data<'s>, pattern: &str) -> QueryResult<'s> {
let segments = parse_query_pattern(pattern);
if segments.is_empty() {
return QueryResult::None;
}
let mut results = vec![QueryMatch {
path: SmolStr::new_static(""),
value: Some(data),
}];
let has_wildcard = segments.iter().any(|s| matches!(s, QuerySegment::Wildcard));
let has_global = segments
.iter()
.any(|s| matches!(s, QuerySegment::GlobalRecursion(_)));
for segment in segments {
results = execute_segment(&results, &segment);
if results.is_empty() {
return QueryResult::None;
}
}
if has_wildcard || has_global || results.len() > 1 {
QueryResult::Multiple(results)
} else if results.len() == 1 {
if let Some(value) = results[0].value {
QueryResult::Single(value)
} else {
QueryResult::None
}
} else {
QueryResult::None
}
}
fn execute_segment<'s>(current: &[QueryMatch<'s>], segment: &QuerySegment) -> Vec<QueryMatch<'s>> {
let mut next = Vec::new();
for qm in current {
let Some(data) = qm.value else { continue };
match segment {
QuerySegment::Field(field) => {
if let Some(obj) = data.as_object() {
if let Some(value) = obj.get(field.as_str()) {
let new_path = append_path(&qm.path, field.as_str());
next.push(QueryMatch {
path: new_path,
value: Some(value),
});
}
}
}
QuerySegment::Wildcard => match data {
Data::Array(arr) => {
for (idx, item) in arr.iter().enumerate() {
let new_path = append_path(&qm.path, &format!("[{}]", idx));
next.push(QueryMatch {
path: new_path,
value: Some(item),
});
}
}
Data::Object(obj) => {
for (key, value) in obj.iter() {
let new_path = append_path(&qm.path, key.as_str());
next.push(QueryMatch {
path: new_path,
value: Some(value),
});
}
}
_ => {}
},
QuerySegment::ScopedRecursion(field) => {
if let Some(found) = find_field_recursive(data, field.as_str(), &qm.path) {
next.push(found);
}
}
QuerySegment::GlobalRecursion(field) => {
find_all_fields_recursive(data, field.as_str(), &qm.path, &mut next);
}
}
}
next
}
fn find_field_recursive<'s>(
data: &'s Data<'s>,
field: &str,
base_path: &SmolStr,
) -> Option<QueryMatch<'s>> {
match data {
Data::Object(obj) => {
if let Some(value) = obj.get(field) {
let new_path = append_path(base_path, field);
return Some(QueryMatch {
path: new_path,
value: Some(value),
});
}
for (key, value) in obj.iter() {
let new_path = append_path(base_path, key.as_str());
if let Some(found) = find_field_recursive(value, field, &new_path) {
return Some(found);
}
}
}
Data::Array(arr) => {
for (idx, item) in arr.iter().enumerate() {
let new_path = append_path(base_path, &format!("[{}]", idx));
if let Some(found) = find_field_recursive(item, field, &new_path) {
return Some(found);
}
}
}
_ => {}
}
None
}
fn find_all_fields_recursive<'s>(
data: &'s Data<'s>,
field: &str,
base_path: &SmolStr,
results: &mut Vec<QueryMatch<'s>>,
) {
match data {
Data::Object(obj) => {
if let Some(value) = obj.get(field) {
let new_path = append_path(base_path, field);
results.push(QueryMatch {
path: new_path,
value: Some(value),
});
}
for (key, value) in obj.iter() {
let new_path = append_path(base_path, key.as_str());
find_all_fields_recursive(value, field, &new_path, results);
}
}
Data::Array(arr) => {
for (idx, item) in arr.iter().enumerate() {
let new_path = append_path(base_path, &format!("[{}]", idx));
find_all_fields_recursive(item, field, &new_path, results);
}
}
_ => {}
}
}
fn append_path(base: &SmolStr, segment: &str) -> SmolStr {
if base.is_empty() {
SmolStr::new(segment)
} else if segment.starts_with('[') {
SmolStr::new(format!("{}{}", base, segment))
} else {
SmolStr::new(format!("{}.{}", base, segment))
}
}