Extension Dependencies
Declare load order with dependencies() returning extension IDs, validated at startup with cycle detection, or check typed Deps at compile time via the builder.
On this page
Extensions can declare dependencies on other extensions, ensuring correct loading order and availability.
Declaring Dependencies
Use the dependencies() method:
impl Extension for MyExtension {
fn dependencies(&self) -> Vec<&'static str> {
vec!["users", "oauth"]
}
// ...
}
The runtime validates that all declared dependencies exist before proceeding.
Loading Order
Extensions load in priority order, but dependencies are validated regardless of priority:
- Extensions sorted by
priority()(lower first) - For each extension, verify all dependencies are registered
- If a dependency is missing, fail with
LoaderError::MissingDependency
Priority vs Dependencies
Priority controls execution order within the same tier:
fn priority(&self) -> u32 {
50 // Lower = loads first
}
Dependencies ensure availability:
fn dependencies(&self) -> Vec<&'static str> {
vec!["users"] // Must exist, regardless of priority
}
Best practice: Set priority lower than dependencies:
// users extension: priority 10
// oauth extension: priority 20, depends on users
// my extension: priority 50, depends on users and oauth
Cycle Detection
The runtime detects circular dependencies:
// This will fail:
// a depends on b
// b depends on c
// c depends on a
Error:
LoaderError::DependencyCycle {
chain: "a -> b -> c -> a"
}
Accessing Dependencies
Access other extensions via ExtensionContext:
fn router(&self, ctx: &dyn ExtensionContext) -> Option<ExtensionRouter> {
// Check if dependency exists
if !ctx.has_extension("users") {
return None;
}
// Get extension
let users_ext = ctx.get_extension("users")?;
// Use extension capabilities...
}
Typed Dependencies
For compile-time dependency checking, use the typed extension system:
use systemprompt::extension::prelude::*;
// Define extension type
#[derive(Default)]
pub struct MyExtension;
impl ExtensionType for MyExtension {
const ID: &'static str = "my-extension";
const NAME: &'static str = "My Extension";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
}
// Declare typed dependencies (nested-tuple list, terminated with ())
impl Dependencies for MyExtension {
type Deps = (UsersExtension, (OAuthExtension, ()));
}
The ExtensionBuilder validates these at compile time:
let registry = ExtensionBuilder::new()
.extension(UsersExtension::default())
.extension(OAuthExtension::default())
.extension(MyExtension::default()) // Compiles only if deps registered first
.build()?;
DependencyList Trait
pub trait DependencyList: 'static {
fn dependency_ids() -> Vec<&'static str>;
}
Implemented for the unit type and nested tuples (a type-level list):
impl DependencyList for () {
fn dependency_ids() -> Vec<&'static str> {
vec![]
}
}
impl<H: ExtensionType, T: DependencyList> DependencyList for (H, T) {
fn dependency_ids() -> Vec<&'static str> {
let mut ids = vec![H::ID];
ids.extend(T::dependency_ids());
ids
}
}
No Dependencies
For extensions without dependencies:
impl Dependencies for MyExtension {
type Deps = ();
}
// Or use the marker trait
impl NoDependencies for MyExtension {}
Common Patterns
Core Dependencies
Most extensions depend on infrastructure:
fn dependencies(&self) -> Vec<&'static str> {
vec!["database", "users"]
}
Feature Dependencies
Feature extensions depend on domain:
fn dependencies(&self) -> Vec<&'static str> {
vec!["users", "content", "analytics"]
}
Optional Dependencies
Check at runtime for optional features:
fn router(&self, ctx: &dyn ExtensionContext) -> Option<ExtensionRouter> {
let has_analytics = ctx.has_extension("analytics");
// Conditionally add analytics routes
if has_analytics {
router = router.nest("/analytics", analytics_routes());
}
}