use std::num::ParseIntError; use crate::{Reflect, ReflectMut, ReflectRef}; use thiserror::Error; #[derive(Debug, PartialEq, Eq, Error)] pub enum ReflectPathError<'a> { #[error("expected an identifier at the given index")] ExpectedIdent { index: usize }, #[error("the current struct doesn't have a field with the given name")] InvalidField { index: usize, field: &'a str }, #[error("the current tuple struct doesn't have a field with the given index")] InvalidTupleStructIndex { index: usize, tuple_struct_index: usize, }, #[error("the current list doesn't have a value at the given index")] InvalidListIndex { index: usize, list_index: usize }, #[error("encountered an unexpected token")] UnexpectedToken { index: usize, token: &'a str }, #[error("expected a 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, } pub trait GetPath { fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>; fn path_mut<'r, 'p>( &'r mut self, path: &'p str, ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>; 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) }) } 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) => { let list_index = value.parse::()?; let list_item = reflect_list.get(list_index).ok_or( ReflectPathError::InvalidListIndex { index: current_index, list_index, }, )?; current = list_item; } _ => { 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) => { let list_index = value.parse::()?; let list_item = reflect_list.get_mut(list_index).ok_or( ReflectPathError::InvalidListIndex { index: current_index, list_index, }, )?; current = list_item; } _ => { 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_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, }, )?) } _ => 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, }, )?) } _ => 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::*; #[test] fn reflect_path() { #[derive(Reflect)] struct A { w: usize, x: B, y: Vec, z: D, } #[derive(Reflect)] struct B { foo: usize, bar: C, } #[derive(Reflect)] struct C { baz: f32, } #[derive(Reflect)] struct D(E); #[derive(Reflect)] struct E(f32, usize); 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)), }; 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); *a.get_path_mut::("y[1].baz").unwrap() = 3.0; assert_eq!(a.y[1].baz, 3.0); assert_eq!( a.path("x.notreal").err().unwrap(), ReflectPathError::InvalidField { index: 2, field: "notreal" } ); 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(_)) )); } }