Skip to content

Instantly share code, notes, and snippets.

@spikespaz
Created June 5, 2025 23:28
Show Gist options
  • Save spikespaz/a316c9045cb75b67212c779df27cfb87 to your computer and use it in GitHub Desktop.
Save spikespaz/a316c9045cb75b67212c779df27cfb87 to your computer and use it in GitHub Desktop.
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