Analyzed /Users/lifcc/Desktop/code/AI/agent/sage/crates/sage-core/src/error.rs
- Total public functions: 23
- Lines of code: 635 lines
- Error variants: 13 variants
Simple constructors that only take a message parameter:
pub fn xxx(message: impl Into<String>) -> Self {
Self::Xxx {
message: message.into(),
context: None,
// variant-specific fields set to None/default
}
}Functions following this pattern:
config(message)→ Config variantllm(message)→ Llm variantagent(message)→ Agent variantcache(message)→ Cache variantinvalid_input(message)→ InvalidInput variantstorage(message)→ Storage variantnot_found(message)→ NotFound variantexecution(message)→ Agent variant (alias)io(message)→ Io variantjson(message)→ Json varianthttp(message)→ Http variantother(message)→ Other variant
Code repetition: ~120 lines (10 lines × 12 functions)
Constructors that take message and context parameters:
pub fn xxx_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
Self::Xxx {
message: message.into(),
context: Some(context.into()),
// variant-specific fields
}
}Functions following this pattern:
config_with_context(message, context)agent_with_context(message, context)tool_with_context(tool_name, message, context)(also has tool_name)
Code repetition: ~45 lines (9 lines × 5 functions)
Constructors that take message plus one variant-specific field:
pub fn xxx_with_yyy(message: impl Into<String>, yyy: YyyType) -> Self {
Self::Xxx {
message: message.into(),
yyy: Some(yyy.into()),
context: None,
}
}Functions following this pattern:
llm_with_provider(message, provider)invalid_input_field(message, field)not_found_resource(message, resource_type)io_with_path(message, path)http_with_status(message, status_code)
Code repetition: ~50 lines (10 lines × 5 functions)
tool(tool_name, message)- Requires 2 parameters, no simpler versiontimeout(seconds)- Only takesseconds, no message parameterwith_context(self, context)- Chainable method
| Pattern Type | Count | Lines per Function | Total Repetition |
|---|---|---|---|
| Basic constructors | 12 | ~10 | ~120 lines |
| With-context constructors | 5 | ~9 | ~45 lines |
| With-field constructors | 5 | ~10 | ~50 lines |
| Total | 22 | - | ~215 lines |
Percentage of file dedicated to constructors: ~34% (215/635)
- High Boilerplate: Every error variant requires 1-3 constructor functions
- Maintenance Burden: Adding a new error variant requires writing multiple similar functions
- Inconsistent API: Some variants have
_with_xxxvariants, others don't - No Type Safety: Context and other optional fields can't be enforced at compile time
Pros:
- Single implementation for all error types
- Compile-time safety for required fields
- Extensible without adding new functions
- ~200 lines reduction
Example:
// Instead of 3 functions:
SageError::config("msg")
SageError::config_with_context("msg", "ctx")
SageError::Config { message, source, context }
// Use builder:
SageError::builder(ErrorKind::Config, "msg")
.context("ctx")
.source(err)
.build()Implementation:
pub struct SageErrorBuilder {
kind: ErrorKind,
message: String,
context: Option<String>,
// variant-specific fields
provider: Option<String>,
tool_name: Option<String>,
// ...
}
impl SageErrorBuilder {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self { ... }
pub fn context(mut self, ctx: impl Into<String>) -> Self { ... }
pub fn provider(mut self, p: impl Into<String>) -> Self { ... }
pub fn build(self) -> SageError { ... }
}
impl SageError {
pub fn builder(kind: ErrorKind, msg: impl Into<String>) -> SageErrorBuilder {
SageErrorBuilder::new(kind, msg)
}
}Pros:
- Keeps existing API
- Reduces code duplication
- No breaking changes
Cons:
- Macros harder to debug
- Still verbose at call sites
Example:
macro_rules! impl_error_constructor {
($name:ident, $variant:ident, $($field:ident: $ty:ty),*) => {
pub fn $name(message: impl Into<String> $(, $field: $ty)*) -> Self {
Self::$variant {
message: message.into(),
$($field: Some($field.into()),)*
context: None,
}
}
};
}Pros:
- Backward compatible
- Gradual migration
- Provides both simple and advanced APIs
Cons:
- More API surface area
- Duplicated functionality
Implement Option 1 (Builder Pattern) for the following reasons:
- Reduces ~200 lines of boilerplate (34% file size reduction)
- More flexible - can add new optional fields without new functions
- Better ergonomics - method chaining is intuitive
- Type-safe - builder can enforce required fields at compile time
- Future-proof - easy to extend without API changes
- Add
ErrorKindenum (if not exists) - Implement
SageErrorBuilder - Keep existing constructors for backward compatibility
- Gradually migrate codebase to use builder
- Mark old constructors as
#[deprecated]after migration (following project rules, these would be removed after version bump)
Line 196: config(message)
Line 205: config_with_context(message, context)
Line 214: llm(message)
Line 223: llm_with_provider(message, provider)
Line 232: tool(tool_name, message)
Line 241: tool_with_context(tool_name, message, context)
Line 254: agent(message)
Line 262: agent_with_context(message, context)
Line 270: cache(message)
Line 278: invalid_input(message)
Line 287: invalid_input_field(message, field)
Line 296: timeout(seconds)
Line 304: storage(message)
Line 312: not_found(message)
Line 321: not_found_resource(message, resource_type)
Line 333: execution(message)
Line 341: io(message)
Line 350: io_with_path(message, path)
Line 359: json(message)
Line 367: http(message)
Line 377: http_with_status(message, status_code)
Line 387: other(message)
Line 395: with_context(self, context)