Extension Builder
Type-safe extension registration with compile-time dependency checking.
On this page
ExtensionBuilder provides type-safe extension registration with compile-time dependency checking using Rust's type system.
Basic Usage
use systemprompt::extension::prelude::*;
let registry = ExtensionBuilder::new()
.extension(UsersExtension::default())
.extension(OAuthExtension::default())
.extension(MyExtension::default())
.build()?;
Type-Level Tracking
The builder tracks registered types at compile time:
pub struct ExtensionBuilder<Registered: TypeList = ()> {
extensions: Vec<Box<dyn AnyExtension>>,
_marker: PhantomData<Registered>,
}
Each extension() call extends the type list:
impl<R: TypeList> ExtensionBuilder<R> {
pub fn extension<E>(self, ext: E) -> ExtensionBuilder<(E, R)>
where
E: ExtensionType + Dependencies,
E::Deps: Subset<R>, // Compile-time check!
{
// ...
}
}
Dependency Validation
Dependencies are validated at compile time:
// UsersExtension has no dependencies
impl Dependencies for UsersExtension {
type Deps = ();
}
// OAuthExtension depends on UsersExtension
impl Dependencies for OAuthExtension {
type Deps = (UsersExtension,);
}
// This compiles:
ExtensionBuilder::new()
.extension(UsersExtension::default()) // Registered: (Users, ())
.extension(OAuthExtension::default()) // Deps (Users) subset of (Users, ()) ✓
.build()
// This fails at compile time:
ExtensionBuilder::new()
.extension(OAuthExtension::default()) // Deps (Users) NOT subset of () ✗
.build()
Specialized Builders
Schema Extensions
ExtensionBuilder::new()
.schema_extension(DatabaseExtension::default())
.schema_extension(UsersExtension::default())
.build()
API Extensions
ExtensionBuilder::new()
.api_extension(ContentApiExtension::default())
.api_extension(FilesApiExtension::default())
.build()
Mixed Extensions
ExtensionBuilder::new()
.extension(DatabaseExtension::default())
.schema_extension(UsersExtension::default())
.api_extension(ContentApiExtension::default())
.build()
TypeList
The type list tracks registered extensions:
pub trait TypeList: 'static {
fn contains_type<T: 'static>() -> bool;
fn type_ids() -> Vec<TypeId>;
fn len() -> usize;
}
// Empty list
impl TypeList for () {
fn contains_type<T: 'static>() -> bool { false }
fn type_ids() -> Vec<TypeId> { vec![] }
fn len() -> usize { 0 }
}
// Cons list
impl<Head: 'static, Tail: TypeList> TypeList for (Head, Tail) {
fn contains_type<T: 'static>() -> bool {
TypeId::of::<Head>() == TypeId::of::<T>() || Tail::contains_type::<T>()
}
// ...
}
Subset Trait
Validates dependency satisfaction:
pub trait Subset<B: TypeList>: TypeList {
fn is_subset_of() -> bool;
}
// Empty is subset of anything
impl<B: TypeList> Subset<B> for () {
fn is_subset_of() -> bool { true }
}
// (Head, Tail) is subset of B if Head is in B and Tail is subset of B
impl<Head: 'static, Tail: TypeList + Subset<B>, B: TypeList + Contains<Head>> Subset<B> for (Head, Tail) {
fn is_subset_of() -> bool {
B::contains_type::<Head>() && Tail::is_subset_of()
}
}
TypedExtensionRegistry
The builder produces a typed registry:
pub struct TypedExtensionRegistry {
extensions: Vec<Box<dyn AnyExtension>>,
by_id: HashMap<&'static str, usize>,
}
impl TypedExtensionRegistry {
pub fn get<E: ExtensionType>(&self) -> Option<&E>;
pub fn get_by_id(&self, id: &str) -> Option<&dyn AnyExtension>;
pub fn iter(&self) -> impl Iterator<Item = &dyn AnyExtension>;
}
Error Handling
Runtime validation can still fail:
pub fn build(self) -> Result<TypedExtensionRegistry, LoaderError> {
let mut registry = TypedExtensionRegistry::new();
for ext in self.extensions {
// Check for duplicates
if registry.by_id.contains_key(ext.id()) {
return Err(LoaderError::DuplicateExtension(ext.id().to_string()));
}
registry.add(ext);
}
// Validate API paths
registry.validate_api_paths()?;
Ok(registry)
}
Example: Complete Setup
use systemprompt::extension::prelude::*;
// Define extensions with dependencies
#[derive(Debug, Default)]
struct DatabaseExtension;
impl ExtensionType for DatabaseExtension {
const ID: &'static str = "database";
const NAME: &'static str = "Database";
const VERSION: &'static str = "0.1.0";
const PRIORITY: u32 = 1;
}
impl Dependencies for DatabaseExtension {
type Deps = ();
}
#[derive(Debug, Default)]
struct UsersExtension;
impl ExtensionType for UsersExtension {
const ID: &'static str = "users";
const NAME: &'static str = "Users";
const VERSION: &'static str = "0.1.0";
const PRIORITY: u32 = 10;
}
impl Dependencies for UsersExtension {
type Deps = (DatabaseExtension,);
}
// Build registry
fn create_registry() -> Result<TypedExtensionRegistry, LoaderError> {
ExtensionBuilder::new()
.extension(DatabaseExtension)
.extension(UsersExtension)
.build()
}
When to Use
Use ExtensionBuilder when:
- Building a fixed set of extensions
- Want compile-time dependency validation
- Need type-safe extension access
Use ExtensionRegistry::discover() when:
- Extensions register dynamically
- Using
register_extension!macro - Building plugin systems