use core::{fmt::Debug, mem::offset_of};
use facet::{Facet, SequenceType, Shape, StructKind, StructType, Type, UserType};
#[test]
fn unit_struct() {
#[derive(Debug, Facet)]
struct UnitStruct;
let shape = UnitStruct::SHAPE;
assert_eq!(format!("{shape}"), "UnitStruct");
let layout = shape.layout.sized_layout().unwrap();
assert_eq!(layout.size(), 0);
assert_eq!(layout.align(), 1);
if let Type::User(UserType::Struct(StructType { kind, fields, .. })) = shape.ty {
assert_eq!(kind, StructKind::Unit);
assert_eq!(fields.len(), 0);
} else {
panic!("Expected Struct innards");
}
}
#[test]
fn simple_struct() {
#[derive(Debug, Facet)]
struct Blah {
foo: u32,
bar: String,
}
if !cfg!(miri) {
let shape = Blah::SHAPE;
assert_eq!(format!("{shape}"), "Blah");
let layout = shape.layout.sized_layout().unwrap();
assert_eq!(layout.size(), 32);
assert_eq!(layout.align(), 8);
if let Type::User(UserType::Struct(StructType { kind, fields, .. })) = shape.ty {
assert_eq!(kind, StructKind::Struct);
assert_eq!(fields.len(), 2);
let foo_field = &fields[0];
assert_eq!(foo_field.name, "foo");
let foo_layout = foo_field.shape().layout.sized_layout().unwrap();
assert_eq!(foo_layout.size(), 4);
assert_eq!(foo_layout.align(), 4);
assert_eq!(foo_field.offset, offset_of!(Blah, foo));
let bar_field = &fields[1];
assert_eq!(bar_field.name, "bar");
let bar_layout = bar_field.shape().layout.sized_layout().unwrap();
assert_eq!(bar_layout.size(), 24);
assert_eq!(bar_layout.align(), 8);
assert_eq!(bar_field.offset, offset_of!(Blah, bar));
} else {
panic!("Expected Struct innards");
}
}
}
#[test]
fn struct_with_sensitive_field() {
#[derive(Debug, Facet)]
struct Blah {
foo: u32,
#[facet(sensitive)]
bar: String,
}
if !cfg!(miri) {
let shape = Blah::SHAPE;
if let Type::User(UserType::Struct(StructType { fields, .. })) = shape.ty {
let bar_field = &fields[1];
assert_eq!(bar_field.name, "bar");
match shape.ty {
Type::User(UserType::Struct(struct_kind)) => {
assert!(!struct_kind.fields[0].is_sensitive());
assert!(struct_kind.fields[1].is_sensitive());
}
_ => panic!("Expected struct"),
}
} else {
panic!("Expected Struct innards");
}
}
}
#[test]
fn struct_repr_c() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(C)]
struct Blah {
foo: u32,
bar: String,
}
}
#[test]
#[cfg(feature = "doc")]
fn struct_doc_comment() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {}
assert_eq!(Foo::SHAPE.doc, &[" yes"]);
}
#[test]
#[cfg(feature = "doc")]
fn struct_doc_comment2() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {}
assert_eq!(Foo::SHAPE.doc, &[" yes", " no"]);
}
#[test]
#[cfg(feature = "doc")]
fn struct_doc_comment3() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {}
assert_eq!(Foo::SHAPE.doc, &[" yes 😄", " no"]);
}
#[test]
#[cfg(feature = "doc")]
fn struct_doc_comment4() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {}
assert_eq!(Foo::SHAPE.doc, &[r#" what about "quotes""#]);
}
#[test]
#[cfg(feature = "doc")]
fn struct_field_doc_comment() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {
bar: u32,
}
if let Type::User(UserType::Struct(StructType { fields, .. })) = Foo::SHAPE.ty {
assert_eq!(fields[0].doc, &[" This field has a doc comment"]);
} else {
panic!("Expected Struct innards");
}
}
#[test]
#[cfg(feature = "doc")]
fn tuple_struct_field_doc_comment_test() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct MyTupleStruct(
u32,
String,
);
let shape = MyTupleStruct::SHAPE;
if let Type::User(UserType::Struct(StructType { fields, kind, .. })) = shape.ty {
assert_eq!(kind, StructKind::TupleStruct);
assert_eq!(fields[0].doc, &[" This is a documented field"]);
assert_eq!(fields[1].doc, &[" This is another documented field"]);
} else {
panic!("Expected Struct innards");
}
}
#[test]
#[cfg(feature = "doc")]
fn enum_variants_with_comments() {
#[derive(Clone, Hash, PartialEq, Eq, Facet)]
#[repr(u8)]
enum CommentedEnum {
#[allow(dead_code)]
A,
#[allow(dead_code)]
B(u32),
#[allow(dead_code)]
C {
x: u32,
y: String,
},
}
let shape = CommentedEnum::SHAPE;
if let Type::User(UserType::Enum(enum_kind)) = shape.ty {
assert_eq!(enum_kind.variants.len(), 3);
let variant_a = &enum_kind.variants[0];
assert_eq!(variant_a.name, "A");
assert_eq!(variant_a.doc, &[" This is variant A"]);
let variant_b = &enum_kind.variants[1];
assert_eq!(variant_b.name, "B");
assert_eq!(
variant_b.doc,
&[" This is variant B", " with multiple lines"]
);
let variant_c = &enum_kind.variants[2];
assert_eq!(variant_c.name, "C");
assert_eq!(
variant_c.doc,
&[" This is variant C", " which has named fields"]
);
let fields = variant_c.data.fields;
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, "x");
assert_eq!(fields[0].doc, &[" This is field x"]);
assert_eq!(fields[1].name, "y");
assert_eq!(fields[1].doc, &[" This is field y"]);
} else {
panic!("Expected Enum definition");
}
}
#[test]
fn struct_with_pub_field() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Foo {
pub bar: u32,
}
}
#[test]
fn tuple_struct_repr_transparent() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(transparent)]
struct Blah(u32);
}
#[test]
#[cfg(feature = "doc")]
fn tuple_struct_doc_comment() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(transparent)]
struct Blah(u32);
assert_eq!(Blah::SHAPE.doc, &[" This is a struct for sure"]);
}
#[test]
fn tuple_struct_field_doc_comment() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(transparent)]
struct Blah(
u32,
);
}
#[test]
fn record_struct_generic() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Blah<'a, T, const C: usize = 3>
where
T: core::hash::Hash,
{
field: core::marker::PhantomData<&'a T>,
}
}
#[test]
fn tuple_struct_generic() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(transparent)]
struct Blah<'a, T, const C: usize = 3>(T, core::marker::PhantomData<&'a ()>)
where
T: core::hash::Hash;
}
#[test]
fn unit_struct_generic() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Blah<const C: usize = 3>
where
(): core::hash::Hash;
}
#[test]
fn enum_generic() {
#[allow(dead_code)]
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(u8)]
enum E<'a, T, const C: usize = 3>
where
T: core::hash::Hash,
{
Unit,
Tuple(T, core::marker::PhantomData<&'a ()>),
Record {
field: T,
phantom: core::marker::PhantomData<&'a ()>,
},
}
}
#[test]
fn enum_generic_partial() {
#[allow(dead_code)]
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
#[repr(u8)]
enum E<'a, T, const C: usize = 3>
where
T: core::hash::Hash,
{
Unit,
Tuple(i32),
Record {
field: T,
phantom: core::marker::PhantomData<&'a ()>,
},
}
}
#[test]
fn tuple_struct_with_pub_field() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct Blah(
pub u32,
pub(crate) u32,
);
}
#[test]
fn cfg_attrs() {
#[derive(Facet)]
#[cfg_attr(feature = "testfeat", derive(Debug))]
#[cfg_attr(feature = "testfeat", facet(deny_unknown_fields))]
pub struct CubConfig {}
}
#[test]
fn struct_with_std_string() {
#[derive(Clone, Hash, PartialEq, Eq, ::facet::Facet)]
struct FileInfo {
path: std::string::String,
size: u64,
}
}
#[test]
fn macroed_type() {
fn validate_shape(shape: &Shape) {
match shape.ty {
Type::User(UserType::Struct(sk)) => {
assert_eq!(sk.fields.len(), 1);
let field = sk.fields[0];
let shape_name = format!("{}", field.shape());
assert_eq!(shape_name, "u32");
eprintln!("Shape {shape} looks correct");
}
_ => unreachable!(),
}
}
#[derive(Debug, Facet, PartialEq)]
struct Manual {
value: u32,
}
validate_shape(Manual::SHAPE);
macro_rules! declare_struct {
($type:ty) => {
#[derive(Debug, Facet, PartialEq)]
struct Macroed {
value: $type,
}
};
}
declare_struct!(u32);
validate_shape(Macroed::SHAPE);
}
#[test]
#[allow(dead_code)]
fn array_field() {
#[derive(Facet)]
#[repr(u8)]
pub enum Packet {
Header([u8; 4]),
}
let shape = Packet::SHAPE;
match shape.ty {
Type::User(UserType::Enum(e)) => {
let variant = &e.variants[0];
let fields = &variant.data.fields;
let field = &fields[0];
match field.shape().ty {
Type::Sequence(SequenceType::Array(ak)) => {
assert_eq!(ak.n, 4);
eprintln!("Shape {shape} looks correct");
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
#[test]
fn struct_impls_drop() {
#[derive(Facet)]
struct BarFoo {
bar: u32,
foo: String,
}
impl Drop for BarFoo {
fn drop(&mut self) {
eprintln!("Dropping BarFoo");
}
}
}
#[test]
fn opaque_arc() {
#[allow(dead_code)]
pub struct NotDerivingFacet(u64);
#[derive(Facet)]
pub struct Handle(#[facet(opaque)] std::sync::Arc<NotDerivingFacet>);
let shape = Handle::SHAPE;
match shape.ty {
Type::User(UserType::Struct(sk)) => {
assert_eq!(sk.fields.len(), 1);
let field = sk.fields[0];
let shape_name = format!("{}", field.shape());
assert_eq!(shape_name, "Opaque");
eprintln!("Shape {shape} looks correct");
}
_ => unreachable!(),
}
}
#[allow(dead_code)]
struct OpaqueBorrowedNotFacet;
#[derive(Facet)]
struct OpaqueBorrowed<'a> {
#[facet(opaque)]
inner: &'a OpaqueBorrowedNotFacet,
}
#[test]
fn opaque_borrowed_reference() {
let value = OpaqueBorrowedNotFacet;
let wrapped = OpaqueBorrowed { inner: &value };
let shape = OpaqueBorrowed::SHAPE;
match shape.ty {
Type::User(UserType::Struct(sk)) => {
assert_eq!(sk.fields.len(), 1);
assert_eq!(format!("{}", sk.fields[0].shape()), "Opaque");
}
_ => unreachable!(),
}
assert!(core::ptr::eq(wrapped.inner, &value));
}
#[test]
fn enum_rename_all_snake_case() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(rename_all = "snake_case")]
#[allow(dead_code)]
enum MaybeFontStyle {
Regular,
Italic,
Bold,
}
let shape = MaybeFontStyle::SHAPE;
assert_eq!(format!("{shape}"), "MaybeFontStyle");
if let Type::User(UserType::Enum(enum_kind)) = shape.ty {
assert_eq!(enum_kind.variants.len(), 3);
assert_eq!(enum_kind.variants[0].effective_name(), "regular");
assert_eq!(enum_kind.variants[1].effective_name(), "italic");
assert_eq!(enum_kind.variants[2].effective_name(), "bold");
for variant in enum_kind.variants {
assert_eq!(variant.data.fields.len(), 0);
}
} else {
panic!("Expected Enum definition");
}
}
#[test]
fn core_ops_range() {
let shape = core::ops::Range::<usize>::SHAPE;
let Type::User(UserType::Struct(struct_type)) = shape.ty else {
panic!("expected struct type");
};
assert_eq!(shape.type_params.len(), 1);
assert_eq!(shape.type_params[0].name, "Idx");
assert_eq!(shape.type_params[0].shape(), usize::SHAPE);
assert_eq!(struct_type.fields.len(), 2);
assert_eq!(struct_type.fields[0].name, "start");
assert_eq!(struct_type.fields[0].shape(), usize::SHAPE);
assert_eq!(
struct_type.fields[0].offset,
offset_of!(core::ops::Range::<usize>, start)
);
assert_eq!(struct_type.fields[1].name, "end");
assert_eq!(struct_type.fields[1].shape(), usize::SHAPE);
assert_eq!(
struct_type.fields[1].offset,
offset_of!(core::ops::Range::<usize>, end)
);
}
#[test]
fn struct_with_default_field_that_has_lifetime() {
#[derive(Facet)]
struct Foo<'a> {
#[facet(default)]
name: Option<std::borrow::Cow<'a, str>>,
}
}
#[test]
fn plain_tuple() {
let _value = (42, "hello", true);
let shape = <(i32, &str, bool) as Facet>::SHAPE;
match shape.ty {
Type::User(UserType::Struct(s)) => {
assert_eq!(s.kind, StructKind::Tuple);
assert_eq!(s.fields.len(), 3);
assert_eq!(s.fields[0].name, "0");
assert_eq!(s.fields[1].name, "1");
assert_eq!(s.fields[2].name, "2");
}
_ => panic!("Expected tuple to be a UserType::Struct"),
}
}
#[test]
fn test_macro_u16() {
macro_rules! test_macro_u16 {
() => {
242u16
};
}
const CONST_VALUE_U16: u16 = 142;
#[repr(u16)]
#[derive(Facet)]
#[allow(dead_code)]
enum TestEnum {
Value1 = 42u16,
Value2 = CONST_VALUE_U16,
Value3 = test_macro_u16!(),
}
}
#[test]
fn tests_enum_empty_tuple() {
#[repr(u16)]
#[derive(Facet)]
#[allow(dead_code)]
enum TestEnum {
EmptyTuple(),
}
}
#[test]
fn test_skip_field_in_derive() {
#[allow(dead_code)]
struct NotFacet {
foo: i32,
bar: String,
}
#[derive(Facet)]
struct Foo {
#[facet(opaque)]
name: NotFacet,
}
}
#[test]
fn test_transparent_newtype() {
#[derive(Facet)]
#[facet(transparent)]
struct UserId(u32);
let shape = UserId::SHAPE;
assert_eq!(format!("{shape}"), "UserId");
let layout = shape.layout.sized_layout().unwrap();
assert_eq!(layout.size(), 4);
assert_eq!(layout.align(), 4);
if let Type::User(UserType::Struct(StructType { kind, fields, .. })) = shape.ty {
assert_eq!(kind, StructKind::TupleStruct);
assert_eq!(fields.len(), 1);
let field = &fields[0];
assert_eq!(field.name, "0");
let field_layout = field.shape().layout.sized_layout().unwrap();
assert_eq!(field_layout.size(), 4);
assert_eq!(field_layout.align(), 4);
assert_eq!(field.offset, offset_of!(UserId, 0));
} else {
panic!("Expected Struct innards");
}
}
#[test]
fn enum_untagged() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(untagged)]
#[allow(dead_code)]
enum StringOrInt {
Int(i64),
String(String),
}
let shape = StringOrInt::SHAPE;
assert!(shape.is_untagged());
assert!(shape.get_tag_attr().is_none());
assert!(shape.get_content_attr().is_none());
}
#[test]
fn enum_internally_tagged() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(tag = "type")]
#[allow(dead_code)]
enum Message {
Request { id: String },
Response { id: String },
}
let shape = Message::SHAPE;
assert!(!shape.is_untagged());
assert_eq!(shape.get_tag_attr(), Some("type"));
assert!(shape.get_content_attr().is_none());
}
#[test]
fn enum_adjacently_tagged() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(tag = "t", content = "c")]
#[allow(dead_code)]
enum Block {
Para(String),
Code(String),
}
let shape = Block::SHAPE;
assert!(!shape.is_untagged());
assert_eq!(shape.get_tag_attr(), Some("t"));
assert_eq!(shape.get_content_attr(), Some("c"));
}
#[test]
fn enum_externally_tagged_default() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[allow(dead_code)]
enum Status {
Active,
Inactive,
}
let shape = Status::SHAPE;
assert!(!shape.is_untagged());
assert!(shape.get_tag_attr().is_none());
assert!(shape.get_content_attr().is_none());
}
#[test]
fn struct_with_custom_crate_path() {
#[derive(Debug, Facet)]
#[facet(crate = ::facet)]
struct CustomPathStruct {
foo: u32,
bar: String,
}
let shape = CustomPathStruct::SHAPE;
assert_eq!(format!("{shape}"), "CustomPathStruct");
if let Type::User(UserType::Struct(StructType { kind, fields, .. })) = shape.ty {
assert_eq!(kind, StructKind::Struct);
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, "foo");
assert_eq!(fields[1].name, "bar");
} else {
panic!("Expected Struct innards");
}
}
#[test]
fn enum_with_custom_crate_path() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(crate = ::facet)]
#[allow(dead_code)]
enum CustomPathEnum {
VariantA,
VariantB(u32),
VariantC { x: String },
}
let shape = CustomPathEnum::SHAPE;
assert_eq!(format!("{shape}"), "CustomPathEnum");
if let Type::User(UserType::Enum(enum_kind)) = shape.ty {
assert_eq!(enum_kind.variants.len(), 3);
assert_eq!(enum_kind.variants[0].name, "VariantA");
assert_eq!(enum_kind.variants[1].name, "VariantB");
assert_eq!(enum_kind.variants[2].name, "VariantC");
} else {
panic!("Expected Enum definition");
}
}
#[test]
fn tuple_struct_with_custom_crate_path() {
#[derive(Debug, Facet)]
#[facet(crate = ::facet)]
struct CustomPathTuple(u32, String);
let shape = CustomPathTuple::SHAPE;
assert_eq!(format!("{shape}"), "CustomPathTuple");
if let Type::User(UserType::Struct(StructType { kind, fields, .. })) = shape.ty {
assert_eq!(kind, StructKind::TupleStruct);
assert_eq!(fields.len(), 2);
} else {
panic!("Expected Struct innards");
}
}
#[test]
fn generic_struct_with_custom_crate_path() {
#[derive(Debug, Facet)]
#[facet(crate = ::facet)]
struct GenericCustomPath<T: Debug> {
value: T,
}
let shape = GenericCustomPath::<u32>::SHAPE;
assert_eq!(format!("{shape}"), "GenericCustomPath<u32>");
}
#[test]
fn metadata_field_attribute() {
#[derive(Debug, Facet)]
struct Timestamped<T: Debug> {
value: T,
#[facet(metadata = timestamp)]
created_at: u64,
}
let shape = Timestamped::<i32>::SHAPE;
let Type::User(UserType::Struct(struct_type)) = shape.ty else {
panic!("Expected struct type");
};
let created_at_field = struct_type
.fields
.iter()
.find(|f| f.name == "created_at")
.expect("Should have created_at field");
assert!(
created_at_field.is_metadata(),
"created_at field should be marked as metadata"
);
assert_eq!(
created_at_field.metadata_kind(),
Some("timestamp"),
"created_at field should have metadata kind 'timestamp'"
);
let value_field = struct_type
.fields
.iter()
.find(|f| f.name == "value")
.expect("Should have value field");
assert!(
!value_field.is_metadata(),
"value field should not be marked as metadata"
);
}
#[test]
fn metadata_field_structural_hash() {
use core::hash::Hasher;
use facet_reflect::Peek;
use std::hash::DefaultHasher;
#[derive(Debug, Facet)]
struct WithTimestamp {
data: i32,
#[facet(metadata = timestamp)]
ts: u64,
}
let a = WithTimestamp { data: 42, ts: 1000 };
let b = WithTimestamp { data: 42, ts: 9999 };
let mut hasher_a = DefaultHasher::new();
Peek::new(&a).structural_hash(&mut hasher_a);
let hash_a = hasher_a.finish();
let mut hasher_b = DefaultHasher::new();
Peek::new(&b).structural_hash(&mut hasher_b);
let hash_b = hasher_b.finish();
assert_eq!(
hash_a, hash_b,
"Values with same data but different metadata should have same structural hash"
);
let c = WithTimestamp { data: 99, ts: 1000 };
let mut hasher_c = DefaultHasher::new();
Peek::new(&c).structural_hash(&mut hasher_c);
let hash_c = hasher_c.finish();
assert_ne!(
hash_a, hash_c,
"Values with different data should have different structural hash"
);
}
#[test]
fn pod_struct_basic() {
#[derive(Debug, Facet)]
#[facet(pod)]
struct Point {
x: i32,
y: i32,
}
let shape = Point::SHAPE;
assert!(shape.is_pod(), "Point should be marked as POD");
}
#[test]
fn pod_tuple_struct() {
#[derive(Debug, Facet)]
#[facet(pod)]
struct Pair(i32, i32);
let shape = Pair::SHAPE;
assert!(shape.is_pod(), "Pair should be marked as POD");
}
#[test]
fn pod_enum() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[facet(pod)]
#[allow(dead_code)]
enum Color {
Red,
Green,
Blue,
}
let shape = Color::SHAPE;
assert!(shape.is_pod(), "Color enum should be marked as POD");
}
#[test]
fn non_pod_struct() {
#[derive(Debug, Facet)]
struct NotPod {
x: i32,
}
let shape = NotPod::SHAPE;
assert!(!shape.is_pod(), "NotPod should not be marked as POD");
}
#[test]
fn primitives_are_pod() {
assert!(u8::SHAPE.is_pod(), "u8 should be POD");
assert!(u16::SHAPE.is_pod(), "u16 should be POD");
assert!(u32::SHAPE.is_pod(), "u32 should be POD");
assert!(u64::SHAPE.is_pod(), "u64 should be POD");
assert!(i8::SHAPE.is_pod(), "i8 should be POD");
assert!(i16::SHAPE.is_pod(), "i16 should be POD");
assert!(i32::SHAPE.is_pod(), "i32 should be POD");
assert!(i64::SHAPE.is_pod(), "i64 should be POD");
assert!(f32::SHAPE.is_pod(), "f32 should be POD");
assert!(f64::SHAPE.is_pod(), "f64 should be POD");
assert!(bool::SHAPE.is_pod(), "bool should be POD");
assert!(char::SHAPE.is_pod(), "char should be POD");
}
#[test]
fn transparent_generic_tuple_struct() {
#[derive(Debug, Clone, PartialEq, Facet)]
#[facet(transparent)]
pub struct Document<T: 'static>(Vec<T>);
let shape = Document::<i32>::SHAPE;
assert_eq!(format!("{shape}"), "Document<i32>");
assert!(shape.inner.is_some(), "Should have inner shape");
let inner = shape.inner.unwrap();
assert_eq!(format!("{inner}"), "Vec<i32>");
}
#[test]
fn shape_module_path_and_source_location() {
#[derive(Debug, Facet)]
struct TestModulePath {
x: i32,
}
let shape = TestModulePath::SHAPE;
assert!(
shape.module_path.is_some(),
"module_path should be Some for derived types"
);
let module_path = shape.module_path.unwrap();
assert!(
module_path.contains("derive"),
"module_path should contain 'derive', got: {}",
module_path
);
assert!(
shape.source_file.is_some(),
"source_file should be Some for derived types"
);
let source_file = shape.source_file.unwrap();
assert!(
source_file.contains("derive.rs"),
"source_file should contain 'derive.rs', got: {}",
source_file
);
assert!(
shape.source_line.is_some(),
"source_line should be Some for derived types"
);
assert!(
shape.source_column.is_some(),
"source_column should be Some for derived types"
);
let i32_shape = i32::SHAPE;
assert!(
i32_shape.module_path.is_none(),
"module_path should be None for primitives"
);
assert!(
i32_shape.source_file.is_none(),
"source_file should be None for primitives"
);
}
#[test]
fn enum_module_path_and_source_location() {
#[derive(Debug, Facet)]
#[repr(u8)]
#[allow(dead_code)]
enum TestEnumPath {
A,
B,
}
let shape = TestEnumPath::SHAPE;
assert!(
shape.module_path.is_some(),
"module_path should be Some for derived enums"
);
let module_path = shape.module_path.unwrap();
assert!(
module_path.contains("derive"),
"module_path should contain 'derive', got: {}",
module_path
);
assert!(
shape.source_file.is_some(),
"source_file should be Some for derived enums"
);
}
#[test]
fn foreign_type_module_paths() {
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
let vec_shape = Vec::<i32>::SHAPE;
assert_eq!(
vec_shape.module_path,
Some("alloc::vec"),
"Vec should have module_path 'alloc::vec'"
);
let string_shape = String::SHAPE;
assert_eq!(
string_shape.module_path,
Some("alloc::string"),
"String should have module_path 'alloc::string'"
);
let hashmap_shape = HashMap::<String, i32>::SHAPE;
assert_eq!(
hashmap_shape.module_path,
Some("std::collections::hash_map"),
"HashMap should have module_path 'std::collections::hash_map'"
);
let hashset_shape = HashSet::<String>::SHAPE;
assert_eq!(
hashset_shape.module_path,
Some("std::collections::hash_set"),
"HashSet should have module_path 'std::collections::hash_set'"
);
let btreemap_shape = BTreeMap::<String, i32>::SHAPE;
assert_eq!(
btreemap_shape.module_path,
Some("alloc::collections::btree_map"),
"BTreeMap should have module_path 'alloc::collections::btree_map'"
);
let btreeset_shape = BTreeSet::<String>::SHAPE;
assert_eq!(
btreeset_shape.module_path,
Some("alloc::collections::btree_set"),
"BTreeSet should have module_path 'alloc::collections::btree_set'"
);
let option_shape = Option::<i32>::SHAPE;
assert_eq!(
option_shape.module_path,
Some("core::option"),
"Option should have module_path 'core::option'"
);
let result_shape = Result::<i32, String>::SHAPE;
assert_eq!(
result_shape.module_path,
Some("core::result"),
"Result should have module_path 'core::result'"
);
let box_shape = Box::<i32>::SHAPE;
assert_eq!(
box_shape.module_path,
Some("alloc::boxed"),
"Box should have module_path 'alloc::boxed'"
);
let arc_shape = std::sync::Arc::<i32>::SHAPE;
assert_eq!(
arc_shape.module_path,
Some("alloc::sync"),
"Arc should have module_path 'alloc::sync'"
);
let rc_shape = std::rc::Rc::<i32>::SHAPE;
assert_eq!(
rc_shape.module_path,
Some("alloc::rc"),
"Rc should have module_path 'alloc::rc'"
);
let pathbuf_shape = std::path::PathBuf::SHAPE;
assert_eq!(
pathbuf_shape.module_path,
Some("std::path"),
"PathBuf should have module_path 'std::path'"
);
let cow_shape = std::borrow::Cow::<str>::SHAPE;
assert_eq!(
cow_shape.module_path,
Some("alloc::borrow"),
"Cow should have module_path 'alloc::borrow'"
);
assert!(
vec_shape.source_file.is_none(),
"Foreign types should not have source_file"
);
assert!(
string_shape.source_line.is_none(),
"Foreign types should not have source_line"
);
}
#[test]
fn test_decl_id() {
use facet::Facet;
#[derive(Facet)]
struct Wrapper<T> {
inner: T,
}
let wrapper_u32 = <Wrapper<u32>>::SHAPE;
let wrapper_string = <Wrapper<String>>::SHAPE;
assert_ne!(
wrapper_u32.id, wrapper_string.id,
"Different instantiations should have different type ids"
);
assert_eq!(
wrapper_u32.decl_id, wrapper_string.decl_id,
"Same generic type with different params should have same decl_id"
);
#[derive(Facet)]
struct OtherWrapper<T> {
value: T,
}
let other_u32 = <OtherWrapper<u32>>::SHAPE;
assert_ne!(
wrapper_u32.decl_id, other_u32.decl_id,
"Different type declarations should have different decl_id"
);
macro_rules! make_two {
() => {
#[derive(Facet)]
struct MacroFoo<T> {
inner: T,
}
#[derive(Facet)]
struct MacroBar<T> {
inner: T,
}
};
}
make_two!();
let macro_foo = <MacroFoo<u32>>::SHAPE;
let macro_bar = <MacroBar<u32>>::SHAPE;
assert_ne!(
macro_foo.decl_id, macro_bar.decl_id,
"Types from same macro invocation should have different decl_id (type name differs)"
);
macro_rules! make_wrapper {
($name:ident) => {
#[derive(Facet)]
struct $name<T> {
inner: T,
}
};
}
make_wrapper!(WrapperA);
make_wrapper!(WrapperB);
let wrapper_a = <WrapperA<u32>>::SHAPE;
let wrapper_b = <WrapperB<u32>>::SHAPE;
assert_ne!(
wrapper_a.decl_id, wrapper_b.decl_id,
"Different macro invocations should have different decl_id"
);
}
#[test]
fn from_ref_infallible() {
use std::borrow::Cow;
#[derive(Debug, PartialEq, Facet)]
#[facet(from_ref = BorrowedData::from_ref)]
struct BorrowedData<'a> {
text: Cow<'a, str>,
}
impl<'a> BorrowedData<'a> {
fn from_ref(s: &'a str) -> Self {
BorrowedData {
text: Cow::Borrowed(s),
}
}
}
let shape = BorrowedData::SHAPE;
assert!(
shape.vtable.has_try_from(),
"BorrowedData should have try_from in VTable"
);
let src_shape = <&str>::SHAPE;
let src_str: &str = "hello world";
let src_ptr = facet::PtrConst::new(src_str);
let mut uninit = core::mem::MaybeUninit::<BorrowedData<'_>>::uninit();
let dst_ptr = facet::PtrUninit::from_maybe_uninit(&mut uninit);
let outcome = unsafe { shape.call_try_from(src_shape, src_ptr, dst_ptr) };
assert!(
matches!(outcome, Some(facet::TryFromOutcome::Converted)),
"try_from should succeed with matching source shape"
);
let result = unsafe { uninit.assume_init() };
assert_eq!(result.text, "hello world");
}
#[test]
fn try_from_ref_fallible() {
#[derive(Debug, PartialEq, Facet)]
#[facet(try_from_ref = AsStr::try_from_ref)]
struct AsStr<'a> {
value: &'a str,
}
impl<'a> AsStr<'a> {
fn try_from_ref(s: &'a [u8]) -> Result<Self, std::str::Utf8Error> {
Ok(AsStr {
value: std::str::from_utf8(s)?,
})
}
}
let shape = AsStr::SHAPE;
assert!(
shape.vtable.has_try_from(),
"ParsedInt should have try_from in VTable"
);
let src_shape = <&[u8]>::SHAPE;
let src_str: &[u8] = b"buh";
let src_ptr = facet::PtrConst::new(src_str);
let mut uninit = core::mem::MaybeUninit::<AsStr>::uninit();
let dst_ptr = facet::PtrUninit::from_maybe_uninit(&mut uninit);
let outcome = unsafe { shape.call_try_from(src_shape, src_ptr, dst_ptr) };
assert!(
matches!(outcome, Some(facet::TryFromOutcome::Converted)),
"try_from should succeed with valid UTF-8 string"
);
let result = unsafe { uninit.assume_init() };
assert_eq!(result.value, "buh");
let invalid_str: &[u8] = b"\xFF";
let invalid_ptr = facet::PtrConst::new(invalid_str);
let mut uninit2 = core::mem::MaybeUninit::<AsStr>::uninit();
let dst_ptr2 = facet::PtrUninit::from_maybe_uninit(&mut uninit2);
let outcome2 = unsafe { shape.call_try_from(src_shape, invalid_ptr, dst_ptr2) };
assert!(
matches!(outcome2, Some(facet::TryFromOutcome::Failed(_))),
"try_from should fail with invalid UTF-8 string"
);
}
#[test]
fn from_ref_wrong_source_type() {
use std::borrow::Cow;
#[derive(Debug, PartialEq, Facet)]
#[facet(from_ref = TextData::from_ref)]
struct TextData<'a> {
text: Cow<'a, str>,
}
impl<'a> TextData<'a> {
fn from_ref(s: &'a str) -> Self {
TextData {
text: Cow::Borrowed(s),
}
}
}
let shape = TextData::SHAPE;
let wrong_shape = i32::SHAPE;
let wrong_value: i32 = 42;
let wrong_ptr = facet::PtrConst::new(&wrong_value as *const i32);
let mut uninit = core::mem::MaybeUninit::<TextData<'_>>::uninit();
let dst_ptr = facet::PtrUninit::from_maybe_uninit(&mut uninit);
let outcome = unsafe { shape.call_try_from(wrong_shape, wrong_ptr, dst_ptr) };
assert!(
matches!(outcome, Some(facet::TryFromOutcome::Unsupported)),
"try_from should return Unsupported for wrong source type"
);
}
#[test]
fn from_ref_enum() {
use std::borrow::Cow;
#[derive(Debug, PartialEq, Facet)]
#[facet(from_ref = Status::from_ref)]
#[repr(u8)]
enum Status<'a> {
Active(Cow<'a, str>),
Inactive,
}
impl<'a> Status<'a> {
fn from_ref(s: &'a str) -> Self {
if s == "inactive" {
Status::Inactive
} else {
Status::Active(Cow::Borrowed(s))
}
}
}
let shape = Status::SHAPE;
assert!(
shape.vtable.has_try_from(),
"Status enum should have try_from in VTable"
);
let src_shape = <&str>::SHAPE;
let src_str: &str = "hello";
let src_ptr = facet::PtrConst::new(src_str);
let mut uninit = core::mem::MaybeUninit::<Status<'_>>::uninit();
let dst_ptr = facet::PtrUninit::from_maybe_uninit(&mut uninit);
let outcome = unsafe { shape.call_try_from(src_shape, src_ptr, dst_ptr) };
assert!(
matches!(outcome, Some(facet::TryFromOutcome::Converted)),
"try_from should succeed with matching source shape"
);
let result = unsafe { uninit.assume_init() };
assert_eq!(result, Status::Active(Cow::Borrowed("hello")));
let src_str2: &str = "inactive";
let src_ptr2 = facet::PtrConst::new(src_str2);
let mut uninit2 = core::mem::MaybeUninit::<Status<'_>>::uninit();
let dst_ptr2 = facet::PtrUninit::from_maybe_uninit(&mut uninit2);
let outcome2 = unsafe { shape.call_try_from(src_shape, src_ptr2, dst_ptr2) };
assert!(
matches!(outcome2, Some(facet::TryFromOutcome::Converted)),
"try_from should succeed for inactive status"
);
let result2 = unsafe { uninit2.assume_init() };
assert_eq!(result2, Status::Inactive);
}
#[cfg(feature = "yoke")]
mod yoke {
use super::*;
#[test]
fn test_yoke() {
#[derive(Debug, PartialEq, Facet)]
#[facet(try_from_ref = AsStr::try_from_ref)]
struct AsStr<'a> {
value: &'a str,
}
impl<'a> AsStr<'a> {
fn try_from_ref(s: &'a [u8]) -> Result<Self, std::str::Utf8Error> {
Ok(AsStr {
value: std::str::from_utf8(s)?,
})
}
}
}
}
#[test]
fn metadata_container_basic() {
#[derive(Facet)]
#[facet(metadata_container)]
struct Documented<T> {
value: T,
#[facet(metadata = "doc")]
doc: Option<Vec<String>>,
}
let shape = <Documented<String>>::SHAPE;
assert!(shape.is_metadata_container());
if let Type::User(UserType::Struct(StructType { fields, .. })) = shape.ty {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, "value");
assert_eq!(fields[1].name, "doc");
assert_eq!(fields[1].metadata_kind(), Some("doc"));
} else {
panic!("Expected struct type");
}
}
#[test]
fn metadata_container_with_span() {
#[derive(Facet, Clone, Copy)]
struct Span {
start: usize,
end: usize,
}
#[derive(Facet)]
#[facet(metadata_container)]
struct Spanned<T> {
value: T,
#[facet(metadata = "span")]
span: Option<Span>,
}
let shape = <Spanned<u32>>::SHAPE;
assert!(shape.is_metadata_container());
if let Type::User(UserType::Struct(StructType { fields, .. })) = shape.ty {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, "value");
assert_eq!(fields[1].name, "span");
assert_eq!(fields[1].metadata_kind(), Some("span"));
} else {
panic!("Expected struct type");
}
}
#[test]
fn non_metadata_container_flag() {
#[derive(Facet)]
struct Regular {
foo: u32,
bar: String,
}
let shape = Regular::SHAPE;
assert!(!shape.is_metadata_container());
}
#[test]
fn macro_rules_visibility_fragment() {
macro_rules! with_vis {
($vis:vis struct $name:ident;) => {
#[derive(Facet)]
$vis struct $name;
};
}
with_vis!(
pub struct VisibleCat;
);
with_vis!(
struct PrivateCat;
);
let shape = VisibleCat::SHAPE;
assert_eq!(format!("{shape}"), "VisibleCat");
let shape = PrivateCat::SHAPE;
assert_eq!(format!("{shape}"), "PrivateCat");
}
#[test]
fn macro_rules_visibility_fragment_with_fields() {
macro_rules! with_vis_fields {
($vis:vis struct $name:ident { $field_vis:vis $field:ident: $ty:ty $(,)? }) => {
#[derive(Facet)]
$vis struct $name {
$field_vis $field: $ty,
}
};
}
with_vis_fields!(
pub struct VisibleDog {
pub name: String,
}
);
let shape = VisibleDog::SHAPE;
assert_eq!(format!("{shape}"), "VisibleDog");
if let Type::User(UserType::Struct(StructType { fields, .. })) = shape.ty {
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].name, "name");
} else {
panic!("Expected struct type");
}
}
#[test]
fn macro_rules_visibility_fragment_enum() {
macro_rules! with_vis_enum {
($vis:vis enum $name:ident { $($variant:ident),* $(,)? }) => {
#[derive(Facet)]
#[repr(u8)]
#[allow(dead_code)]
$vis enum $name {
$($variant),*
}
};
}
with_vis_enum!(
pub enum VisibleColor {
Red,
Green,
Blue,
}
);
let shape = VisibleColor::SHAPE;
assert_eq!(format!("{shape}"), "VisibleColor");
if let Type::User(UserType::Enum(enum_kind)) = shape.ty {
assert_eq!(enum_kind.variants.len(), 3);
assert_eq!(enum_kind.variants[0].name, "Red");
assert_eq!(enum_kind.variants[1].name, "Green");
assert_eq!(enum_kind.variants[2].name, "Blue");
} else {
panic!("Expected enum type");
}
}
#[test]
fn skip_unless_truthy_on_option_ref_with_lifetime_compiles() {
#[derive(Facet)]
struct CommandAttrs<'a> {
pub program: &'a str,
#[facet(skip_unless_truthy)]
pub cwd: Option<&'a str>,
}
if let Type::User(UserType::Struct(StructType { fields, .. })) = CommandAttrs::SHAPE.ty {
assert_eq!(fields.len(), 2);
assert!(fields[1].skip_serializing_if.is_some());
} else {
panic!("Expected struct type");
}
}