Created
June 5, 2025 23:28
-
-
Save spikespaz/a316c9045cb75b67212c779df27cfb87 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use facet::{ | |
EnumType, Field, PointerType, Shape, StructKind, StructType, Type, UnionType, UserType, | |
}; | |
pub trait RecursiveShape<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>>; | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>>; | |
} | |
impl<'shape> RecursiveShape<'shape> for Shape<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>> { | |
if path.is_empty() { | |
Some(self) | |
} else { | |
self.field_by_path(path).map(|field| field.shape) | |
} | |
} | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>> { | |
match &self.ty { | |
_ if path.is_empty() => None, | |
Type::Primitive(_) => None, | |
Type::Sequence(_) => None, | |
Type::User(user_type) => user_type.field_by_path(path), | |
Type::Pointer( | |
PointerType::Reference(pointer_value_type) | PointerType::Raw(pointer_value_type), | |
) => pointer_value_type.target().field_by_path(path), | |
// `PointerType` is exhaustive. | |
Type::Pointer(PointerType::Function(_)) => None, | |
// `Type` itself is marked `#[non_exhaustive]`. | |
_ => unimplemented!("`field_by_path` not implemented for new `Type` variant"), | |
} | |
} | |
} | |
impl<'shape> RecursiveShape<'shape> for UserType<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>> { | |
self.field_by_path(path).map(|field| field.shape) | |
} | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>> { | |
match self { | |
UserType::Struct(struct_type) => struct_type.field_by_path(path), | |
UserType::Enum(enum_type) => enum_type.field_by_path(path), | |
UserType::Union(union_type) => union_type.field_by_path(path), | |
UserType::Opaque => None, | |
} | |
} | |
} | |
impl<'shape> RecursiveShape<'shape> for StructType<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>> { | |
self.field_by_path(path).map(|field| field.shape) | |
} | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>> { | |
let (field_name, path) = path.split_first()?; | |
let field = match self.kind { | |
StructKind::Unit => None, | |
StructKind::TupleStruct | StructKind::Tuple => { | |
let field_index = field_name.as_ref().parse::<usize>().ok()?; | |
self.fields.get(field_index) | |
} | |
StructKind::Struct => self | |
.fields | |
.iter() | |
.find(|field| field.name == field_name.as_ref()), | |
_ => unimplemented!("`field_by_path` not implemented for new `StructKind` variant"), | |
}?; | |
if path.is_empty() { | |
Some(field) | |
} else { | |
field.shape.field_by_path(path) | |
} | |
} | |
} | |
impl<'shape> RecursiveShape<'shape> for EnumType<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>> { | |
self.variants | |
.iter() | |
.find_map(|variant| variant.data.shape_by_path(path)) | |
} | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>> { | |
self.variants | |
.iter() | |
.find_map(|variant| variant.data.field_by_path(path)) | |
} | |
} | |
impl<'shape> RecursiveShape<'shape> for UnionType<'shape> { | |
fn shape_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Shape<'shape>> { | |
self.field_by_path(path).map(|field| field.shape) | |
} | |
fn field_by_path(&self, path: &[impl AsRef<str>]) -> Option<&Field<'shape>> { | |
let (field_name, path) = path.split_first()?; | |
let field = self | |
.fields | |
.iter() | |
.find(|field| field.name == field_name.as_ref())?; | |
if path.is_empty() { | |
Some(field) | |
} else { | |
field.shape.field_by_path(path) | |
} | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use facet::Facet; | |
use crate::RecursiveShape; | |
#[derive(Facet)] | |
struct Struct { | |
field_a: u8, | |
field_b: u16, | |
field_c: Inner, | |
} | |
#[derive(Facet)] | |
struct Inner { | |
field_1: u32, | |
field_2: u64, | |
} | |
#[derive(Facet)] | |
struct TupleStruct(i8, i16); | |
type Tuple = (i32, i64); | |
#[test] | |
fn empty_path_is_outer_shape() { | |
let shape = Struct::SHAPE.shape_by_path(&[] as &[&str]); | |
assert_eq!(Struct::SHAPE, shape.unwrap()) | |
} | |
#[test] | |
fn missing_path_is_none() { | |
let shape = Struct::SHAPE.shape_by_path(&["missing"]); | |
assert!(shape.is_none()) | |
} | |
#[test] | |
fn field_shape_seq_1() { | |
let shape = Struct::SHAPE.shape_by_path(&["field_a"]); | |
assert_eq!(u8::SHAPE, shape.unwrap()); | |
let shape = Struct::SHAPE.shape_by_path(&["field_b"]); | |
assert_eq!(u16::SHAPE, shape.unwrap()); | |
} | |
#[test] | |
fn field_shape_seq_2() { | |
let shape = Struct::SHAPE.shape_by_path(&["field_c", "field_1"]); | |
assert_eq!(u32::SHAPE, shape.unwrap()); | |
let shape = Struct::SHAPE.shape_by_path(&["field_c", "field_2"]); | |
assert_eq!(u64::SHAPE, shape.unwrap()); | |
} | |
#[test] | |
fn indexed_field_shape() { | |
let shape = TupleStruct::SHAPE.shape_by_path(&["0"]); | |
assert_eq!(i8::SHAPE, shape.unwrap()); | |
let shape = TupleStruct::SHAPE.shape_by_path(&["1"]); | |
assert_eq!(i16::SHAPE, shape.unwrap()); | |
} | |
#[test] | |
fn tuple_field_shape() { | |
let shape = Tuple::SHAPE.shape_by_path(&["0"]); | |
assert_eq!(i32::SHAPE, shape.unwrap()); | |
let shape = Tuple::SHAPE.shape_by_path(&["1"]); | |
assert_eq!(i64::SHAPE, shape.unwrap()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment