Libmagic-rs api-design
Rust library API design patterns including builder pattern, error handling, trait design, type safety, and CLI design with clap.
install
source · Clone the upstream repo
git clone https://github.com/EvilBit-Labs/libmagic-rs
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/EvilBit-Labs/libmagic-rs "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/api-design" ~/.claude/skills/evilbit-labs-libmagic-rs-api-design && rm -rf "$T"
manifest:
.claude/skills/api-design/SKILL.mdsource content
API Design Patterns (Rust Library & CLI)
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
// For types with many optional configuration fields pub struct EvaluationConfig { timeout: Duration, max_rules: usize, follow_symlinks: bool, } impl EvaluationConfig { pub fn builder() -> EvaluationConfigBuilder { EvaluationConfigBuilder::default() } } pub struct EvaluationConfigBuilder { /* ... */ } impl EvaluationConfigBuilder { pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } pub fn build(self) -> Result<EvaluationConfig, ConfigError> { // Validate configuration Ok(EvaluationConfig { /* ... */ }) } }
Error Design
Three-Tier Error Hierarchy
// Top-level: user-facing errors pub enum LibmagicError { Parse(ParseError), Evaluation(EvaluationError), Config(ConfigError), Io(std::io::Error), } // Module-level: specific to subsystem pub enum ParseError { InvalidSyntax { line: usize, reason: String }, IoError(String), } // Always implement std::error::Error + Display impl std::fmt::Display for LibmagicError { /* ... */ } impl std::error::Error for LibmagicError { /* ... */ }
Error Guidelines
- Use
for deriving Error implementationsthiserror - Errors should be actionable (include line numbers, context)
- Never expose internal paths or system details in public errors
- Implement
conversions for ergonomicFrom
usage?
Type Safety
Newtype Pattern
// Wrap primitives to prevent misuse pub struct Offset(i64); pub struct Score(u32); pub struct Level(u32); // Prevents accidentally passing a score where an offset is expected fn evaluate_at(offset: Offset, buffer: &[u8]) -> Result<Score, EvaluationError>;
Enum-Based Type Discrimination
// Use enums to make invalid states unrepresentable pub enum OffsetSpec { Absolute(i64), Indirect { base: i64, pointer_type: TypeKind }, Relative(i64), FromEnd(i64), } // Cannot have both Absolute and Relative -- the type system prevents it
Public API Surface
Minimize Exposure
// Only expose what users need pub use crate::evaluator::EvaluationResult; pub use crate::parser::MagicRule; // Keep internals private pub(crate) use crate::evaluator::EvaluationContext;
Document Everything Public
/// Evaluate magic rules against a file buffer. /// /// # Arguments /// * `rules` - Parsed magic rules to evaluate /// * `buffer` - File contents to identify /// /// # Returns /// The best matching result, or `None` if no rules match. /// /// # Errors /// Returns `EvaluationError` if evaluation fails due to /// invalid offsets or corrupted rule definitions. /// /// # Examples /// ``` /// use libmagic_rs::MagicDatabase; /// /// let db = MagicDatabase::default(); /// let result = db.evaluate_buffer(&[0x7f, 0x45, 0x4c, 0x46])?; /// ``` pub fn evaluate_buffer(&self, buffer: &[u8]) -> Result<Option<EvaluationResult>, EvaluationError>;
Trait Design
// Small, focused traits 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; } // Implement for multiple types impl SafeBufferAccess for FileBuffer { /* ... */ } impl SafeBufferAccess for &[u8] { /* ... */ }
CLI Design (clap)
Argument Structure
#[derive(Parser)] #[command(name = "rmagic", about = "Identify file types")] struct Args { /// Files to identify #[arg(required = true)] files: Vec<PathBuf>, /// Output as JSON #[arg(long)] json: bool, /// Use custom magic file #[arg(long, value_name = "FILE")] magic_file: Option<PathBuf>, }
CLI Conventions
- Follow GNU
command conventions where possiblefile - Short flags for common options (
for JSON)-j - Long flags for all options (
)--json - Positional arguments for files
to separate flags from file arguments--- Exit code 0 for success, 1 for errors
Output Format Consistency
- Text output:
(matches GNUfilename: description
)file - JSON output: structured with
,filename
,matchesmetadata - Errors to stderr, results to stdout
- Quiet mode suppresses non-essential output
API Evolution
Non-Breaking Changes (patch/minor version)
- Adding new enum variants (if
)#[non_exhaustive] - Adding new optional fields to builders
- 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 structs
- Tightening input constraints
- Changing error types
Defensive Techniques
// Mark enums as non-exhaustive for future extension #[non_exhaustive] pub enum TypeKind { Byte, Short { endian: Endianness, signed: bool }, Long { endian: Endianness, signed: bool }, String { max_length: Option<usize> }, // Future: Quad, Float, Regex, etc. }
API Review Checklist
Before exposing new public API:
- All public items have rustdoc with examples
- Error types are descriptive and actionable
- Builder pattern used for types with >3 optional fields
- Types prevent invalid states at compile time
-
on enums that may grow#[non_exhaustive] - Consistent naming with existing API surface
-
/From
conversions for ergonomic useInto -
andDisplay
implemented for all public typesDebug