pub mod access;
pub use access::*;
mod error;
pub use error::*;
mod parse;
pub use parse::ParseError;
use parse::PathParser;
use crate::{PartialReflect, Reflect};
use alloc::vec::Vec;
use core::fmt;
use derive_more::derive::From;
use thiserror::Error;
type PathResult<'a, T> = Result<T, ReflectPathError<'a>>;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ReflectPathError<'a> {
#[error(transparent)]
InvalidAccess(AccessError<'a>),
#[error("Can't downcast result of access to the given type")]
InvalidDowncast,
#[error("Encountered an error at offset {offset} while parsing `{path}`: {error}")]
ParseError {
offset: usize,
path: &'a str,
error: ParseError<'a>,
},
}
impl<'a> From<AccessError<'a>> for ReflectPathError<'a> {
fn from(value: AccessError<'a>) -> Self {
ReflectPathError::InvalidAccess(value)
}
}
pub trait ReflectPath<'a>: Sized {
fn reflect_element(self, root: &dyn PartialReflect) -> PathResult<'a, &dyn PartialReflect>;
fn reflect_element_mut(
self,
root: &mut dyn PartialReflect,
) -> PathResult<'a, &mut dyn PartialReflect>;
fn element<T: Reflect>(self, root: &dyn PartialReflect) -> PathResult<'a, &T> {
self.reflect_element(root).and_then(|p| {
p.try_downcast_ref::<T>()
.ok_or(ReflectPathError::InvalidDowncast)
})
}
fn element_mut<T: Reflect>(self, root: &mut dyn PartialReflect) -> PathResult<'a, &mut T> {
self.reflect_element_mut(root).and_then(|p| {
p.try_downcast_mut::<T>()
.ok_or(ReflectPathError::InvalidDowncast)
})
}
}
impl<'a> ReflectPath<'a> for &'a str {
fn reflect_element(self, mut root: &dyn PartialReflect) -> PathResult<'a, &dyn PartialReflect> {
for (access, offset) in PathParser::new(self) {
let a = access?;
root = a.element(root, Some(offset))?;
}
Ok(root)
}
fn reflect_element_mut(
self,
mut root: &mut dyn PartialReflect,
) -> PathResult<'a, &mut dyn PartialReflect> {
for (access, offset) in PathParser::new(self) {
root = access?.element_mut(root, Some(offset))?;
}
Ok(root)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `GetPath` so cannot be accessed by reflection path",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait GetPath: PartialReflect {
fn reflect_path<'p>(&self, path: impl ReflectPath<'p>) -> PathResult<'p, &dyn PartialReflect> {
path.reflect_element(self.as_partial_reflect())
}
fn reflect_path_mut<'p>(
&mut self,
path: impl ReflectPath<'p>,
) -> PathResult<'p, &mut dyn PartialReflect> {
path.reflect_element_mut(self.as_partial_reflect_mut())
}
fn path<'p, T: Reflect>(&self, path: impl ReflectPath<'p>) -> PathResult<'p, &T> {
path.element(self.as_partial_reflect())
}
fn path_mut<'p, T: Reflect>(&mut self, path: impl ReflectPath<'p>) -> PathResult<'p, &mut T> {
path.element_mut(self.as_partial_reflect_mut())
}
}
impl<T: Reflect + ?Sized> GetPath for T {}
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct OffsetAccess {
pub access: Access<'static>,
pub offset: Option<usize>,
}
impl From<Access<'static>> for OffsetAccess {
fn from(access: Access<'static>) -> Self {
OffsetAccess {
access,
offset: None,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, From)]
pub struct ParsedPath(
pub Vec<OffsetAccess>,
);
impl ParsedPath {
pub fn parse(string: &str) -> PathResult<'_, Self> {
let mut parts = Vec::new();
for (access, offset) in PathParser::new(string) {
parts.push(OffsetAccess {
access: access?.into_owned(),
offset: Some(offset),
});
}
Ok(Self(parts))
}
pub fn parse_static(string: &'static str) -> PathResult<'static, Self> {
let mut parts = Vec::new();
for (access, offset) in PathParser::new(string) {
parts.push(OffsetAccess {
access: access?,
offset: Some(offset),
});
}
Ok(Self(parts))
}
}
impl<'a> ReflectPath<'a> for &'a ParsedPath {
fn reflect_element(self, mut root: &dyn PartialReflect) -> PathResult<'a, &dyn PartialReflect> {
for OffsetAccess { access, offset } in &self.0 {
root = access.element(root, *offset)?;
}
Ok(root)
}
fn reflect_element_mut(
self,
mut root: &mut dyn PartialReflect,
) -> PathResult<'a, &mut dyn PartialReflect> {
for OffsetAccess { access, offset } in &self.0 {
root = access.element_mut(root, *offset)?;
}
Ok(root)
}
}
impl<const N: usize> From<[OffsetAccess; N]> for ParsedPath {
fn from(value: [OffsetAccess; N]) -> Self {
ParsedPath(value.to_vec())
}
}
impl From<Vec<Access<'static>>> for ParsedPath {
fn from(value: Vec<Access<'static>>) -> Self {
ParsedPath(
value
.into_iter()
.map(|access| OffsetAccess {
access,
offset: None,
})
.collect(),
)
}
}
impl<const N: usize> From<[Access<'static>; N]> for ParsedPath {
fn from(value: [Access<'static>; N]) -> Self {
value.to_vec().into()
}
}
impl<'a> TryFrom<&'a str> for ParsedPath {
type Error = ReflectPathError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
ParsedPath::parse(value)
}
}
impl fmt::Display for ParsedPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for OffsetAccess { access, .. } in &self.0 {
write!(f, "{access}")?;
}
Ok(())
}
}
impl core::ops::Index<usize> for ParsedPath {
type Output = OffsetAccess;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl core::ops::IndexMut<usize> for ParsedPath {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}
#[cfg(test)]
#[expect(
clippy::approx_constant,
reason = "We don't need the exact value of Pi here."
)]
mod tests {
use super::*;
use crate::*;
use alloc::vec;
#[derive(Reflect, PartialEq, Debug)]
struct A {
w: usize,
x: B,
y: Vec<C>,
z: D,
unit_variant: F,
tuple_variant: F,
struct_variant: F,
array: [i32; 3],
tuple: (bool, f32),
}
#[derive(Reflect, PartialEq, Debug)]
struct B {
foo: usize,
łørđ: C,
}
#[derive(Reflect, PartialEq, Debug)]
struct C {
mосква: f32,
}
#[derive(Reflect, PartialEq, Debug)]
struct D(E);
#[derive(Reflect, PartialEq, Debug)]
struct E(f32, usize);
#[derive(Reflect, PartialEq, Debug)]
enum F {
Unit,
Tuple(u32, u32),
Şķràźÿ { 東京: char },
}
fn a_sample() -> A {
A {
w: 1,
x: B {
foo: 10,
łørđ: C { mосква: 3.14 },
},
y: vec![C { mосква: 1.0 }, C { mосква: 2.0 }],
z: D(E(10.0, 42)),
unit_variant: F::Unit,
tuple_variant: F::Tuple(123, 321),
struct_variant: F::Şķràźÿ { 東京: 'm' },
array: [86, 75, 309],
tuple: (true, 1.23),
}
}
fn offset(access: Access<'static>, offset: usize) -> OffsetAccess {
OffsetAccess {
access,
offset: Some(offset),
}
}
fn access_field(field: &'static str) -> Access<'static> {
Access::Field(field.into())
}
type StaticError = ReflectPathError<'static>;
fn invalid_access(
offset: usize,
actual: ReflectKind,
expected: ReflectKind,
access: &'static str,
) -> StaticError {
ReflectPathError::InvalidAccess(AccessError {
kind: AccessErrorKind::IncompatibleTypes { actual, expected },
access: ParsedPath::parse_static(access).unwrap()[1].access.clone(),
offset: Some(offset),
})
}
#[test]
fn try_from() {
assert_eq!(
ParsedPath::try_from("w").unwrap().0,
&[offset(access_field("w"), 1)]
);
let r = ParsedPath::try_from("w[");
let matches = matches!(r, Err(ReflectPathError::ParseError { .. }));
assert!(
matches,
"ParsedPath::try_from did not return a ParseError for \"w[\""
);
}
#[test]
fn parsed_path_parse() {
assert_eq!(
ParsedPath::parse("w").unwrap().0,
&[offset(access_field("w"), 1)]
);
assert_eq!(
ParsedPath::parse("x.foo").unwrap().0,
&[offset(access_field("x"), 1), offset(access_field("foo"), 2)]
);
assert_eq!(
ParsedPath::parse("x.łørđ.mосква").unwrap().0,
&[
offset(access_field("x"), 1),
offset(access_field("łørđ"), 2),
offset(access_field("mосква"), 10)
]
);
assert_eq!(
ParsedPath::parse("y[1].mосква").unwrap().0,
&[
offset(access_field("y"), 1),
offset(Access::ListIndex(1), 2),
offset(access_field("mосква"), 5)
]
);
assert_eq!(
ParsedPath::parse("z.0.1").unwrap().0,
&[
offset(access_field("z"), 1),
offset(Access::TupleIndex(0), 2),
offset(Access::TupleIndex(1), 4),
]
);
assert_eq!(
ParsedPath::parse("x#0").unwrap().0,
&[
offset(access_field("x"), 1),
offset(Access::FieldIndex(0), 2)
]
);
assert_eq!(
ParsedPath::parse("x#0#1").unwrap().0,
&[
offset(access_field("x"), 1),
offset(Access::FieldIndex(0), 2),
offset(Access::FieldIndex(1), 4)
]
);
}
#[test]
fn parsed_path_get_field() {
let a = a_sample();
let b = ParsedPath::parse("w").unwrap();
let c = ParsedPath::parse("x.foo").unwrap();
let d = ParsedPath::parse("x.łørđ.mосква").unwrap();
let e = ParsedPath::parse("y[1].mосква").unwrap();
let f = ParsedPath::parse("z.0.1").unwrap();
let g = ParsedPath::parse("x#0").unwrap();
let h = ParsedPath::parse("x#1#0").unwrap();
let i = ParsedPath::parse("unit_variant").unwrap();
let j = ParsedPath::parse("tuple_variant.1").unwrap();
let k = ParsedPath::parse("struct_variant.東京").unwrap();
let l = ParsedPath::parse("struct_variant#0").unwrap();
let m = ParsedPath::parse("array[2]").unwrap();
let n = ParsedPath::parse("tuple.1").unwrap();
for _ in 0..30 {
assert_eq!(*b.element::<usize>(&a).unwrap(), 1);
assert_eq!(*c.element::<usize>(&a).unwrap(), 10);
assert_eq!(*d.element::<f32>(&a).unwrap(), 3.14);
assert_eq!(*e.element::<f32>(&a).unwrap(), 2.0);
assert_eq!(*f.element::<usize>(&a).unwrap(), 42);
assert_eq!(*g.element::<usize>(&a).unwrap(), 10);
assert_eq!(*h.element::<f32>(&a).unwrap(), 3.14);
assert_eq!(*i.element::<F>(&a).unwrap(), F::Unit);
assert_eq!(*j.element::<u32>(&a).unwrap(), 321);
assert_eq!(*k.element::<char>(&a).unwrap(), 'm');
assert_eq!(*l.element::<char>(&a).unwrap(), 'm');
assert_eq!(*m.element::<i32>(&a).unwrap(), 309);
assert_eq!(*n.element::<f32>(&a).unwrap(), 1.23);
}
}
#[test]
fn reflect_array_behaves_like_list() {
#[derive(Reflect)]
struct A {
list: Vec<u8>,
array: [u8; 10],
}
let a = A {
list: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
};
assert_eq!(*a.path::<u8>("list[5]").unwrap(), 5);
assert_eq!(*a.path::<u8>("array[5]").unwrap(), 5);
assert_eq!(*a.path::<u8>("list[0]").unwrap(), 0);
assert_eq!(*a.path::<u8>("array[0]").unwrap(), 0);
}
#[test]
fn reflect_array_behaves_like_list_mut() {
#[derive(Reflect)]
struct A {
list: Vec<u8>,
array: [u8; 10],
}
let mut a = A {
list: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
};
assert_eq!(*a.path_mut::<u8>("list[5]").unwrap(), 5);
assert_eq!(*a.path_mut::<u8>("array[5]").unwrap(), 5);
*a.path_mut::<u8>("list[5]").unwrap() = 10;
*a.path_mut::<u8>("array[5]").unwrap() = 10;
assert_eq!(*a.path_mut::<u8>("list[5]").unwrap(), 10);
assert_eq!(*a.path_mut::<u8>("array[5]").unwrap(), 10);
}
#[test]
fn reflect_path() {
let mut a = a_sample();
assert_eq!(*a.path::<A>("").unwrap(), a);
assert_eq!(*a.path::<usize>("w").unwrap(), 1);
assert_eq!(*a.path::<usize>("x.foo").unwrap(), 10);
assert_eq!(*a.path::<f32>("x.łørđ.mосква").unwrap(), 3.14);
assert_eq!(*a.path::<f32>("y[1].mосква").unwrap(), 2.0);
assert_eq!(*a.path::<usize>("z.0.1").unwrap(), 42);
assert_eq!(*a.path::<usize>("x#0").unwrap(), 10);
assert_eq!(*a.path::<f32>("x#1#0").unwrap(), 3.14);
assert_eq!(*a.path::<F>("unit_variant").unwrap(), F::Unit);
assert_eq!(*a.path::<u32>("tuple_variant.1").unwrap(), 321);
assert_eq!(*a.path::<char>("struct_variant.東京").unwrap(), 'm');
assert_eq!(*a.path::<char>("struct_variant#0").unwrap(), 'm');
assert_eq!(*a.path::<i32>("array[2]").unwrap(), 309);
assert_eq!(*a.path::<f32>("tuple.1").unwrap(), 1.23);
*a.path_mut::<f32>("tuple.1").unwrap() = 3.21;
assert_eq!(*a.path::<f32>("tuple.1").unwrap(), 3.21);
*a.path_mut::<f32>("y[1].mосква").unwrap() = 3.0;
assert_eq!(a.y[1].mосква, 3.0);
*a.path_mut::<u32>("tuple_variant.0").unwrap() = 1337;
assert_eq!(a.tuple_variant, F::Tuple(1337, 321));
assert_eq!(
a.reflect_path("x.notreal").err().unwrap(),
ReflectPathError::InvalidAccess(AccessError {
kind: AccessErrorKind::MissingField(ReflectKind::Struct),
access: access_field("notreal"),
offset: Some(2),
})
);
assert_eq!(
a.reflect_path("unit_variant.0").err().unwrap(),
ReflectPathError::InvalidAccess(AccessError {
kind: AccessErrorKind::IncompatibleEnumVariantTypes {
actual: VariantType::Unit,
expected: VariantType::Tuple,
},
access: ParsedPath::parse_static("unit_variant.0").unwrap()[1]
.access
.clone(),
offset: Some(13),
})
);
assert_eq!(
a.reflect_path("x[0]").err().unwrap(),
invalid_access(2, ReflectKind::Struct, ReflectKind::List, "x[0]")
);
assert_eq!(
a.reflect_path("y.x").err().unwrap(),
invalid_access(2, ReflectKind::List, ReflectKind::Struct, "y.x")
);
}
#[test]
fn accept_leading_tokens() {
assert_eq!(
ParsedPath::parse(".w").unwrap().0,
&[offset(access_field("w"), 1)]
);
assert_eq!(
ParsedPath::parse("#0.foo").unwrap().0,
&[
offset(Access::FieldIndex(0), 1),
offset(access_field("foo"), 3)
]
);
assert_eq!(
ParsedPath::parse(".5").unwrap().0,
&[offset(Access::TupleIndex(5), 1)]
);
assert_eq!(
ParsedPath::parse("[0].łørđ").unwrap().0,
&[
offset(Access::ListIndex(0), 1),
offset(access_field("łørđ"), 4)
]
);
}
}