| name | api-design |
| description | Rust library API design patterns including builder pattern, error handling, trait design, type safety, and CLI design with clap. |
API Design Patterns (Rust Library & CLI)
Code samples below are generic Rust patterns -- placeholder types and
simplified enum shapes for illustration. Do not treat them as a snapshot
of the current libmagic-rs API. Actual AST variants (OffsetSpec,
TypeKind, Operator, Value), config fields, and error variants live
in src/parser/ast.rs, src/config.rs, and src/error.rs respectively.
See AGENTS.md and GOTCHAS.md for
the authoritative architecture and policy.
When to Activate
- Designing or modifying public library API in
lib.rs
- Adding new public types, traits, or functions
- Reviewing API ergonomics and consistency
- Designing CLI arguments and output formats
- Planning breaking vs non-breaking changes
Library API Design
Builder Pattern
Use for configuration types that have many optional fields. libmagic-rs
currently uses chainable with_* setters on EvaluationConfig (see
src/config.rs); a dedicated builder type (MagicDatabase::builder())
is planned for v0.6.0 (see AGENTS.md "Development Phases"). General
shape:
pub struct EvaluationConfig {
pub timeout_ms: Option<u64>,
pub max_recursion_depth: u32,
pub stop_at_first_match: bool,
}
impl EvaluationConfig {
#[must_use]
pub fn with_timeout_ms(mut self, timeout_ms: Option<u64>) -> Self {
self.timeout_ms = timeout_ms;
self
}
}
When validation is required, a separate Builder with a build() -> Result<T, ConfigError> step makes sense; for libmagic-rs today, validation
runs at construction time inside EvaluationConfig::new and the chainable
setters.
Error Design
Three-Tier Error Hierarchy
pub enum LibmagicError {
Parse(ParseError),
Evaluation(EvaluationError),
Config(ConfigError),
Io(std::io::Error),
}
pub enum ParseError {
InvalidSyntax { line: usize, message: String },
IoError(String),
}
Use thiserror::Error to derive Display and Error.
Error Guidelines
- Use
thiserror for deriving Error implementations
- Errors should be actionable (include line numbers, offsets, context)
- Never expose internal paths or system details in public errors
- Implement
From conversions for ergonomic ? usage where the source
error doesn't need additional context
Type Safety
Newtype Pattern
pub struct Offset(i64);
pub struct Score(u32);
fn evaluate_at(offset: Offset, buffer: &[u8]) -> Result<Score, EvaluationError> { todo!() }
Enum-Based Type Discrimination
pub enum OffsetSpec {
Absolute(i64),
Indirect { },
Relative(i64),
FromEnd(i64),
}
Public API Surface
Minimize Exposure
pub use crate::evaluator::EvaluationResult;
pub use crate::parser::MagicRule;
pub(crate) use crate::evaluator::EvaluationContext;
Document Everything Public
pub fn evaluate_buffer(&self, buffer: &[u8])
-> Result<Option<EvaluationResult>, EvaluationError> { todo!() }
Trait Design
pub trait SafeBufferAccess {
fn get_byte(&self, offset: usize) -> Option<u8>;
fn get_slice(&self, offset: usize, len: usize) -> Option<&[u8]>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool { self.len() == 0 }
}
CLI Design (clap)
Argument Structure
#[derive(clap::Parser)]
#[command(name = "rmagic", about = "Identify file types")]
struct Args {
#[arg(required = true)]
files: Vec<std::path::PathBuf>,
#[arg(long)]
json: bool,
#[arg(long, value_name = "FILE")]
magic_file: Option<std::path::PathBuf>,
}
CLI Conventions
- Follow GNU
file command conventions where possible
- Short flags for common options
- Long flags for all options
- Positional arguments for files
-- to separate flags from file arguments
- Exit code 0 for success, 1 for errors
Output Format Consistency
- Text output:
filename: description (matches GNU file)
- JSON output: structured with
filename, matches, metadata
- Errors to stderr, results to stdout
- Quiet mode suppresses non-essential output
API Evolution
Non-Breaking Changes (patch/minor version)
- Adding new enum variants when the enum is
#[non_exhaustive]
- Adding new optional fields to
#[non_exhaustive] structs
- Adding new methods to existing types
- Loosening input constraints
Breaking Changes (major version)
- Removing or renaming public types/functions
- Changing function signatures
- Adding required fields to non-
#[non_exhaustive] structs
- Tightening input constraints
- Changing error types
Defensive Techniques
#[non_exhaustive]
pub enum TypeKind {
Byte,
Short { },
Long { },
String { },
}
API Review Checklist
Before exposing new public API: