facet 0.44.4

Reflection for Rust: introspect types at compile time with metadata for serialization, pretty-printing, CLIs, and more
Documentation
//! Exhaustive runtime decoding matrix for extension attributes.
//!
//! Run with:
//! `cargo run -p facet --example extension_attr_runtime_matrix`

use facet::{Facet, StructType, Type, UserType};
use facet_testattrs as testattrs;

#[derive(Facet)]
struct Demo {
    #[facet(testattrs::named)]
    #[facet(testattrs::env_prefix = "APP_")]
    #[facet(testattrs::min = -7)]
    #[facet(testattrs::max_len = 42)]
    #[facet(testattrs::short = 'v')]
    #[facet(testattrs::generic_name = "primary")]
    #[facet(rename = "demo_value")]
    #[facet(sensitive)]
    value: String,
}

fn demo_field() -> &'static facet::Field {
    let Type::User(UserType::Struct(StructType { fields, .. })) = Demo::SHAPE.ty else {
        panic!("expected Demo to be a struct");
    };
    &fields[0]
}

fn assert_extension_decoding(field: &facet::Field) {
    let named = field
        .get_attr(Some("testattrs"), "named")
        .expect("named extension attribute must exist");
    assert!(named.get_as::<testattrs::Attr>().is_none());
    assert!(named.get_as::<()>().is_some());

    let env_prefix = field
        .get_attr(Some("testattrs"), "env_prefix")
        .expect("env_prefix extension attribute must exist");
    assert!(env_prefix.get_as::<testattrs::Attr>().is_none());
    assert_eq!(env_prefix.get_as::<&'static str>().copied(), Some("APP_"));

    let min = field
        .get_attr(Some("testattrs"), "min")
        .expect("min extension attribute must exist");
    assert!(min.get_as::<testattrs::Attr>().is_none());
    assert_eq!(min.get_as::<i64>().copied(), Some(-7));

    let max_len = field
        .get_attr(Some("testattrs"), "max_len")
        .expect("max_len extension attribute must exist");
    assert!(max_len.get_as::<testattrs::Attr>().is_none());
    assert_eq!(max_len.get_as::<usize>().copied(), Some(42));

    let short = field
        .get_attr(Some("testattrs"), "short")
        .expect("short extension attribute must exist");
    assert!(short.get_as::<Option<char>>().is_none());
    let short_decoded = short
        .get_as::<testattrs::Attr>()
        .expect("short should decode through testattrs::Attr");
    assert!(matches!(short_decoded, testattrs::Attr::Short(Some('v'))));

    let maybe_name = field
        .get_attr(Some("testattrs"), "generic_name")
        .expect("generic_name extension attribute must exist");
    let maybe_name_decoded = maybe_name
        .get_as::<testattrs::Attr>()
        .expect("generic_name should decode through testattrs::Attr");
    assert!(matches!(
        maybe_name_decoded,
        testattrs::Attr::GenericName(Some("primary"))
    ));
}

fn assert_builtin_access(field: &facet::Field) {
    // Builtins have dedicated accessors; use those as the primary API.
    assert_eq!(field.rename, Some("demo_value"));
    assert!(field.is_sensitive());
}

fn main() {
    let field = demo_field();
    assert_extension_decoding(field);
    assert_builtin_access(field);
    println!("extension_attr_runtime_matrix: all runtime decoding assertions passed");
}