use std::num::ParseIntError; use crate::{Array, Reflect, ReflectMut, ReflectRef, VariantType}; use thiserror::Error; /// An error returned from a failed path string query. #[derive(Debug, PartialEq, Eq, Error)] pub enum ReflectPathError<'a> { #[error("expected an identifier at index {index}")] ExpectedIdent { index: usize }, #[error("the current struct doesn't have a field with the name `{field}`")] InvalidField { index: usize, field: &'a str }, #[error("the current tuple struct doesn't have a field with the index {tuple_struct_index}")] InvalidTupleStructIndex { index: usize, tuple_struct_index: usize, }, #[error("the current list doesn't have a value at the index {list_index}")] InvalidListIndex { index: usize, list_index: usize }, #[error("encountered an unexpected token `{token}`")] UnexpectedToken { index: usize, token: &'a str }, #[error("expected token `{token}`, but it wasn't there.")] ExpectedToken { index: usize, token: &'a str }, #[error("expected a struct, but found a different reflect value")] ExpectedStruct { index: usize }, #[error("expected a list, but found a different reflect value")] ExpectedList { index: usize }, #[error("failed to parse a usize")] IndexParseError(#[from] ParseIntError), #[error("failed to downcast to the path result to the given type")] InvalidDowncast, #[error("expected either a struct variant or tuple variant, but found a unit variant")] InvalidVariantAccess { index: usize, accessor: &'a str }, } /// A trait which allows nested values to be retrieved with path strings. /// /// Path strings use Rust syntax: /// - [`Struct`] items are accessed with a dot and a field name: `.field_name` /// - [`TupleStruct`] and [`Tuple`] items are accessed with a dot and a number: `.0` /// - [`List`] items are accessed with brackets: `[0]` /// /// If the initial path element is a field of a struct, tuple struct, or tuple, /// the initial '.' may be omitted. /// /// For example, given a struct with a field `foo` which is a reflected list of /// 2-tuples (like a `Vec<(T, U)>`), the path string `foo[3].0` would access tuple /// element 0 of element 3 of `foo`. /// /// [`Struct`]: crate::Struct /// [`TupleStruct`]: crate::TupleStruct /// [`Tuple`]: crate::Tuple /// [`List`]: crate::List pub trait GetPath { /// Returns a reference to the value specified by `path`. /// /// To retrieve a statically typed reference, use /// [`get_path`][GetPath::get_path]. fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>; /// Returns a mutable reference to the value specified by `path`. /// /// To retrieve a statically typed mutable reference, use /// [`get_path_mut`][GetPath::get_path_mut]. fn path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>; /// Returns a statically typed reference to the value specified by `path`. fn get_path<'r, 'p, T: Reflect>( &'r self, path: &'p str, ) -> Result<&'r T, ReflectPathError<'p>> { self.path(path).and_then(|p| { p.downcast_ref::() .ok_or(ReflectPathError::InvalidDowncast) }) } /// Returns a statically typed mutable reference to the value specified by /// `path`. fn get_path_mut<'r, 'p, T: Reflect>( &'r mut self, path: &'p str, ) -> Result<&'r mut T, ReflectPathError<'p>> { self.path_mut(path).and_then(|p| { p.downcast_mut::() .ok_or(ReflectPathError::InvalidDowncast) }) } } impl GetPath for T { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { (self as &dyn Reflect).path(path) } fn path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { (self as &mut dyn Reflect).path_mut(path) } } impl GetPath for dyn Reflect { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { let mut index = 0; let mut current: &dyn Reflect = self; while let Some(token) = next_token(path, &mut index) { let current_index = index; match token { Token::Dot => { if let Some(Token::Ident(value)) = next_token(path, &mut index) { current = read_field(current, value, current_index)?; } else { return Err(ReflectPathError::ExpectedIdent { index: current_index, }); } } Token::OpenBracket => { if let Some(Token::Ident(value)) = next_token(path, &mut index) { match current.reflect_ref() { ReflectRef::List(reflect_list) => { current = read_array_entry(reflect_list, value, current_index)?; } ReflectRef::Array(reflect_arr) => { current = read_array_entry(reflect_arr, value, current_index)?; } _ => { return Err(ReflectPathError::ExpectedList { index: current_index, }) } } } else { return Err(ReflectPathError::ExpectedIdent { index: current_index, }); } if let Some(Token::CloseBracket) = next_token(path, &mut index) { } else { return Err(ReflectPathError::ExpectedToken { index: current_index, token: "]", }); } } Token::CloseBracket => { return Err(ReflectPathError::UnexpectedToken { index: current_index, token: "]", }) } Token::Ident(value) => { current = read_field(current, value, current_index)?; } } } Ok(current) } fn path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { let mut index = 0; let mut current: &mut dyn Reflect = self; while let Some(token) = next_token(path, &mut index) { let current_index = index; match token { Token::Dot => { if let Some(Token::Ident(value)) = next_token(path, &mut index) { current = read_field_mut(current, value, current_index)?; } else { return Err(ReflectPathError::ExpectedIdent { index: current_index, }); } } Token::OpenBracket => { if let Some(Token::Ident(value)) = next_token(path, &mut index) { match current.reflect_mut() { ReflectMut::List(reflect_list) => { current = read_array_entry_mut(reflect_list, value, current_index)?; } ReflectMut::Array(reflect_arr) => { current = read_array_entry_mut(reflect_arr, value, current_index)?; } _ => { return Err(ReflectPathError::ExpectedStruct { index: current_index, }) } } } else { return Err(ReflectPathError::ExpectedIdent { index: current_index, }); } if let Some(Token::CloseBracket) = next_token(path, &mut index) { } else { return Err(ReflectPathError::ExpectedToken { index: current_index, token: "]", }); } } Token::CloseBracket => { return Err(ReflectPathError::UnexpectedToken { index: current_index, token: "]", }) } Token::Ident(value) => { current = read_field_mut(current, value, current_index)?; } } } Ok(current) } } fn read_array_entry<'r, 'p, T>( list: &'r T, value: &'p str, current_index: usize, ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> where T: Array + ?Sized, { let list_index = value.parse::()?; list.get(list_index) .ok_or(ReflectPathError::InvalidListIndex { index: current_index, list_index, }) } fn read_array_entry_mut<'r, 'p, T>( list: &'r mut T, value: &'p str, current_index: usize, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> where T: Array + ?Sized, { let list_index = value.parse::()?; list.get_mut(list_index) .ok_or(ReflectPathError::InvalidListIndex { index: current_index, list_index, }) } fn read_field<'r, 'p>( current: &'r dyn Reflect, field: &'p str, current_index: usize, ) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { match current.reflect_ref() { ReflectRef::Struct(reflect_struct) => { Ok(reflect_struct .field(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, })?) } ReflectRef::TupleStruct(reflect_struct) => { let tuple_index = field.parse::()?; Ok(reflect_struct.field(tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { index: current_index, tuple_struct_index: tuple_index, }, )?) } ReflectRef::Enum(reflect_enum) => match reflect_enum.variant_type() { VariantType::Struct => { Ok(reflect_enum .field(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, })?) } VariantType::Tuple => { let tuple_index = field.parse::()?; Ok(reflect_enum .field_at(tuple_index) .ok_or(ReflectPathError::InvalidField { index: current_index, field, })?) } _ => Err(ReflectPathError::InvalidVariantAccess { index: current_index, accessor: field, }), }, _ => Err(ReflectPathError::ExpectedStruct { index: current_index, }), } } fn read_field_mut<'r, 'p>( current: &'r mut dyn Reflect, field: &'p str, current_index: usize, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { match current.reflect_mut() { ReflectMut::Struct(reflect_struct) => { Ok(reflect_struct .field_mut(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, })?) } ReflectMut::TupleStruct(reflect_struct) => { let tuple_index = field.parse::()?; Ok(reflect_struct.field_mut(tuple_index).ok_or( ReflectPathError::InvalidTupleStructIndex { index: current_index, tuple_struct_index: tuple_index, }, )?) } ReflectMut::Enum(reflect_enum) => match reflect_enum.variant_type() { VariantType::Struct => { Ok(reflect_enum .field_mut(field) .ok_or(ReflectPathError::InvalidField { index: current_index, field, })?) } VariantType::Tuple => { let tuple_index = field.parse::()?; Ok(reflect_enum.field_at_mut(tuple_index).ok_or( ReflectPathError::InvalidField { index: current_index, field, }, )?) } _ => Err(ReflectPathError::InvalidVariantAccess { index: current_index, accessor: field, }), }, _ => Err(ReflectPathError::ExpectedStruct { index: current_index, }), } } enum Token<'a> { Dot, OpenBracket, CloseBracket, Ident(&'a str), } fn next_token<'a>(path: &'a str, index: &mut usize) -> Option> { if *index >= path.len() { return None; } match path[*index..].chars().next().unwrap() { '.' => { *index += 1; return Some(Token::Dot); } '[' => { *index += 1; return Some(Token::OpenBracket); } ']' => { *index += 1; return Some(Token::CloseBracket); } _ => {} } // we can assume we are parsing an ident now for (char_index, character) in path[*index..].chars().enumerate() { match character { '.' | '[' | ']' => { let ident = Token::Ident(&path[*index..*index + char_index]); *index += char_index; return Some(ident); } _ => {} } } let ident = Token::Ident(&path[*index..]); *index = path.len(); Some(ident) } #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { use super::GetPath; use crate as bevy_reflect; use crate::*; #[test] fn reflect_array_behaves_like_list() { #[derive(Reflect)] struct A { list: Vec, 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.get_path::("list[5]").unwrap(), 5); assert_eq!(*a.get_path::("array[5]").unwrap(), 5); assert_eq!(*a.get_path::("list[0]").unwrap(), 0); assert_eq!(*a.get_path::("array[0]").unwrap(), 0); } #[test] fn reflect_array_behaves_like_list_mut() { #[derive(Reflect)] struct A { list: Vec, 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.get_path_mut::("list[5]").unwrap(), 5); assert_eq!(*a.get_path_mut::("array[5]").unwrap(), 5); *a.get_path_mut::("list[5]").unwrap() = 10; *a.get_path_mut::("array[5]").unwrap() = 10; assert_eq!(*a.get_path_mut::("list[5]").unwrap(), 10); assert_eq!(*a.get_path_mut::("array[5]").unwrap(), 10); } #[test] fn reflect_path() { #[derive(Reflect)] struct A { w: usize, x: B, y: Vec, z: D, unit_variant: F, tuple_variant: F, struct_variant: F, } #[derive(Reflect)] struct B { foo: usize, bar: C, } #[derive(Reflect, FromReflect)] struct C { baz: f32, } #[derive(Reflect)] struct D(E); #[derive(Reflect)] struct E(f32, usize); #[derive(Reflect, FromReflect, PartialEq, Debug)] enum F { Unit, Tuple(u32, u32), Struct { value: char }, } let mut a = A { w: 1, x: B { foo: 10, bar: C { baz: 3.14 }, }, y: vec![C { baz: 1.0 }, C { baz: 2.0 }], z: D(E(10.0, 42)), unit_variant: F::Unit, tuple_variant: F::Tuple(123, 321), struct_variant: F::Struct { value: 'm' }, }; assert_eq!(*a.get_path::("w").unwrap(), 1); assert_eq!(*a.get_path::("x.foo").unwrap(), 10); assert_eq!(*a.get_path::("x.bar.baz").unwrap(), 3.14); assert_eq!(*a.get_path::("y[1].baz").unwrap(), 2.0); assert_eq!(*a.get_path::("z.0.1").unwrap(), 42); assert_eq!(*a.get_path::("unit_variant").unwrap(), F::Unit); assert_eq!(*a.get_path::("tuple_variant.1").unwrap(), 321); assert_eq!(*a.get_path::("struct_variant.value").unwrap(), 'm'); *a.get_path_mut::("y[1].baz").unwrap() = 3.0; assert_eq!(a.y[1].baz, 3.0); *a.get_path_mut::("tuple_variant.0").unwrap() = 1337; assert_eq!(a.tuple_variant, F::Tuple(1337, 321)); assert_eq!( a.path("x.notreal").err().unwrap(), ReflectPathError::InvalidField { index: 2, field: "notreal" } ); assert_eq!( a.path("unit_variant.0").err().unwrap(), ReflectPathError::InvalidVariantAccess { index: 13, accessor: "0" } ); assert_eq!( a.path("x..").err().unwrap(), ReflectPathError::ExpectedIdent { index: 2 } ); assert_eq!( a.path("x[0]").err().unwrap(), ReflectPathError::ExpectedList { index: 2 } ); assert_eq!( a.path("y.x").err().unwrap(), ReflectPathError::ExpectedStruct { index: 2 } ); assert!(matches!( a.path("y[badindex]"), Err(ReflectPathError::IndexParseError(_)) )); } }