Extension Attributes

Extension attributes allow third-party crates to attach custom metadata to types, fields, variants, and more. This enables crates like facet-kdl, facet-args, or your own custom crate to define domain-specific attributes without conflicting with facet's built-in attributes.

Using Extension Attributes

Extension attributes use a namespaced syntax:

#[derive(Facet)]
struct Config {
    #[facet(kdl::child)]
    server: Server,

    #[facet(kdl::property)]
    name: String,
}

The namespace (kdl) is typically the name you import the crate as:

use facet_kdl as kdl;  // Now kdl:: attributes work

Available Namespaces

Different crates provide different namespaces:

CrateNamespaceExample
facet-kdlkdl#[facet(kdl::child)]
facet-argsargs#[facet(args::short = 'v')]

Attribute Arguments

Extension attributes can have arguments:

// Simple marker (no arguments)
#[facet(kdl::child)]

// With parenthesized arguments
#[facet(orm::index(unique, name = "idx_email"))]

// With equals-style arguments
#[facet(args::short = 'v')]

KDL Extension Attributes

The facet-kdl crate provides these attributes for KDL serialization/deserialization:

kdl::argument

Marks a field as a KDL positional argument (the unnamed values after a node name):

#[derive(Facet)]
struct Server {
    #[facet(kdl::argument)]
    name: String,  // Comes from: server "my-server" ...
    host: String,
    port: u16,
}

KDL input:

server "my-server" host="localhost" port=8080

kdl::arguments

Marks a Vec field to collect all positional arguments:

#[derive(Facet)]
struct Matrix {
    #[facet(kdl::arguments)]
    data: Vec<i32>,  // Collects all positional args
}

KDL input:

matrix 1 2 3 4 5

kdl::property

Marks a field as a KDL property (key=value pairs):

#[derive(Facet)]
struct Server {
    #[facet(kdl::property)]
    host: String,
    #[facet(kdl::property)]
    port: u16,
}

KDL input:

server host="localhost" port=8080

Note: Fields without any KDL attribute default to being properties.

kdl::child

Marks a field as a KDL child node:

#[derive(Facet)]
struct Config {
    #[facet(kdl::child)]
    database: Database,
}

#[derive(Facet)]
struct Database {
    host: String,
    port: u16,
}

KDL input:

database host="localhost" port=5432

kdl::children

Marks a Vec field to collect repeated child nodes:

#[derive(Facet)]
struct Config {
    #[facet(kdl::children)]
    servers: Vec<Server>,
}

KDL input:

server host="web1" port=8080
server host="web2" port=8081

kdl::node_name

Captures the KDL node name into a field:

#[derive(Facet)]
struct Dependency {
    #[facet(kdl::node_name)]
    name: String,
    version: String,
}

KDL input:

serde version="1.0"
tokio version="1.28"

Here name will be "serde" or "tokio".

Creating Extension Attributes (For Crate Authors)

If you're building a crate that needs custom attributes, here's how to define and query them.

Querying Extension Attributes at Runtime

Extension attributes are stored in the type's shape and can be queried at runtime:

use facet_core::{Field, FieldAttribute};

fn process_field(field: &Field) {
    // Check if an attribute exists
    if field.has_extension_attr("kdl", "child") {
        // This field is a KDL child node
    }

    // Get the attribute with its arguments
    if let Some(ext) = field.get_extension_attr("kdl", "property") {
        // Access raw token arguments
        let args = ext.args;  // &[Token]

        // Or parse into structured form
        let parsed = ext.parse_args().unwrap();
        for (name, value) in parsed.named {
            // Handle named arguments like `rename = "foo"`
        }
    }
}

The ExtensionAttr Structure

pub struct ExtensionAttr {
    /// The namespace (e.g., "kdl" in `#[facet(kdl::child)]`)
    pub ns: &'static str,

    /// The key (e.g., "child" in `#[facet(kdl::child)]`)
    pub key: &'static str,

    /// Raw token arguments from the attribute
    pub args: &'static [Token],

    /// Function to get typed data (for advanced use)
    pub get: fn() -> &'static (dyn Any + Send + Sync),
}

Parsing Arguments

For attributes with arguments like #[facet(orm::index(unique, name = "idx"))]:

let ext = field.get_extension_attr("orm", "index").unwrap();
let args = ext.parse_args().unwrap();

// Positional arguments (like `unique`)
for arg in &args.positional {
    match arg {
        ParsedValue::Ident(name) => println!("Flag: {}", name),
        ParsedValue::Literal(lit) => println!("Value: {}", lit),
    }
}

// Named arguments (like `name = "idx"`)
for (key, value) in &args.named {
    println!("{} = {:?}", key, value);
}

Where Extension Attributes Can Appear

Extension attributes can be attached to:

Example checking shape-level attributes:

use facet_core::{Shape, ShapeAttribute};

fn check_shape(shape: &Shape) {
    for attr in shape.attributes {
        if let ShapeAttribute::Extension(ext) = attr {
            if ext.ns == "orm" && ext.key == "table" {
                // This type has #[facet(orm::table)]
            }
        }
    }
}

Current Limitations

Extension attributes are validated at runtime, not compile time. This means:

We're working on compile-time validation with helpful error messages.