elicitation 0.10.0

Conversational elicitation of strongly-typed Rust values via MCP
Documentation
//! Global registry of type specs via inventory.
//!
//! Each type that implements [`ElicitSpec`](crate::ElicitSpec) submits a
//! [`TypeSpecInventoryKey`] at compile time. The [`lookup_type_spec`] function
//! searches the registry at runtime by type name; [`lookup_type_spec_by_id`]
//! searches by [`TypeId`] for use in composed specs generated by `#[derive(Elicit)]`.

use std::any::TypeId;

use crate::TypeSpec;

/// Registration key for the global type spec inventory.
///
/// Submit one of these for each type that implements [`ElicitSpec`](crate::ElicitSpec).
/// The `type_name` must be a string literal (`&'static str`) because
/// `inventory::submit!` requires const-constructible values.
///
/// ```rust,ignore
/// inventory::submit!(TypeSpecInventoryKey::new(
///     "I32Positive",
///     <I32Positive as ElicitSpec>::type_spec,
///     TypeId::of::<I32Positive>,
/// ));
/// ```
pub struct TypeSpecInventoryKey {
    type_name: &'static str,
    builder: fn() -> TypeSpec,
    /// Returns the [`TypeId`] of the type, for lookup by composed specs.
    type_id: fn() -> TypeId,
}

impl TypeSpecInventoryKey {
    /// Creates a new registry key.
    ///
    /// # Arguments
    ///
    /// * `type_name` - The name agents use to look up this type (e.g., `"I32Positive"`)
    /// * `builder` - Function that constructs the [`TypeSpec`] on demand
    /// * `type_id` - Function returning the [`TypeId`] of the registered type
    pub const fn new(
        type_name: &'static str,
        builder: fn() -> TypeSpec,
        type_id: fn() -> TypeId,
    ) -> Self {
        Self {
            type_name,
            builder,
            type_id,
        }
    }

    /// The type name this key is registered under.
    pub fn type_name(&self) -> &str {
        self.type_name
    }

    /// Builds the [`TypeSpec`] for this type.
    pub fn build(&self) -> TypeSpec {
        (self.builder)()
    }

    /// The [`TypeId`] of the registered type.
    pub fn type_id(&self) -> TypeId {
        (self.type_id)()
    }
}

inventory::collect!(TypeSpecInventoryKey);

/// Look up the spec for a type by name.
///
/// Returns `None` if no [`TypeSpecInventoryKey`] has been submitted for that name.
///
/// # Example
///
/// ```rust,ignore
/// if let Some(spec) = lookup_type_spec("I32Positive") {
///     println!("{}: {}", spec.type_name(), spec.summary());
/// }
/// ```
pub fn lookup_type_spec(name: &str) -> Option<TypeSpec> {
    inventory::iter::<TypeSpecInventoryKey>()
        .find(|k| k.type_name() == name)
        .map(|k| k.build())
}

/// Look up the spec for a type by its [`TypeId`].
///
/// This is the mechanism used by composed specs (generated by `#[derive(Elicit)]`)
/// to harvest field-type specs at runtime without requiring `specialization`.
/// Returns `None` if the type has no registered [`ElicitSpec`].
///
/// # Example
///
/// ```rust,ignore
/// // Composed spec harvests field specs at runtime
/// if let Some(field_spec) = lookup_type_spec_by_id(TypeId::of::<I32Positive>()) {
///     // include field_spec entries under "fields.count"
/// }
/// ```
pub fn lookup_type_spec_by_id(id: TypeId) -> Option<TypeSpec> {
    inventory::iter::<TypeSpecInventoryKey>()
        .find(|k| k.type_id() == id)
        .map(|k| k.build())
}