Code Style
libmagic-rs follows strict code style guidelines to ensure consistency, readability, and maintainability across the codebase.
Formatting
Rustfmt Configuration
The project uses rustfmt
with default settings. All code must be formatted before committing:
# Format all code
cargo fmt
# Check formatting without changing files
cargo fmt -- --check
Key Formatting Rules
- Line length: 100 characters (rustfmt default)
- Indentation: 4 spaces (no tabs)
- Trailing commas: Required in multi-line constructs
- Import organization: Automatic grouping and sorting
#![allow(unused)] fn main() { // Good: Proper formatting use std::collections::HashMap; use std::path::Path; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::parser::ast::MagicRule; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EvaluationResult { pub description: String, pub mime_type: Option<String>, pub confidence: f64, } }
Naming Conventions
Types and Structs
Use PascalCase
for types, structs, enums, and traits:
#![allow(unused)] fn main() { // Good pub struct MagicDatabase {} pub enum OffsetSpec {} pub trait BinaryRegex {} // Bad pub struct magic_database {} pub enum offset_spec {} }
Functions and Variables
Use snake_case
for functions, methods, and variables:
#![allow(unused)] fn main() { // Good pub fn parse_magic_file(path: &Path) -> Result<Vec<MagicRule>> { } let magic_rules = vec![]; let file_buffer = FileBuffer::new(path)?; // Bad pub fn ParseMagicFile(path: &Path) -> Result<Vec<MagicRule>> { } let magicRules = vec![]; }
Constants
Use SCREAMING_SNAKE_CASE
for constants:
#![allow(unused)] fn main() { // Good const DEFAULT_BUFFER_SIZE: usize = 8192; const MAX_RECURSION_DEPTH: u32 = 50; // Bad const default_buffer_size: usize = 8192; const maxRecursionDepth: u32 = 50; }
Modules
Use snake_case
for module names:
#![allow(unused)] fn main() { // Good mod file_evaluator; mod magic_parser; mod output_formatter; // Bad mod MagicParser; mod fileEvaluator; }
Documentation Standards
Public API Documentation
All public items must have rustdoc comments with examples:
#![allow(unused)] fn main() { /// Parses a magic file into a vector of magic rules /// /// This function reads a magic file from the specified path and parses it into /// a collection of `MagicRule` structures that can be used for file type detection. /// /// # Arguments /// /// * `path` - Path to the magic file to parse /// /// # Returns /// /// Returns `Ok(Vec<MagicRule>)` on success, or `Err(LibmagicError)` if parsing fails. /// /// # Errors /// /// This function will return an error if: /// - The file cannot be read due to permissions or missing file /// - The magic file contains invalid syntax /// - Memory allocation fails during parsing /// /// # Examples /// /// ```rust,no_run /// use libmagic_rs::parser::parse_magic_file; /// /// let rules = parse_magic_file("magic.db")?; /// println!("Loaded {} magic rules", rules.len()); /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pub fn parse_magic_file<P: AsRef<Path>>(path: P) -> Result<Vec<MagicRule>> { // Implementation } }
Module Documentation
Each module should have comprehensive documentation:
#![allow(unused)] fn main() { //! Magic file parser module //! //! This module handles parsing of magic files into an Abstract Syntax Tree (AST) //! that can be evaluated against file buffers for type identification. //! //! The parser uses nom combinators for robust, efficient parsing with good //! error reporting. It supports the standard magic file format with extensions //! for modern file types. //! //! # Examples //! //! ```rust,no_run //! use libmagic_rs::parser::parse_magic_file; //! //! let rules = parse_magic_file("magic.db")?; //! for rule in &rules { //! println!("Rule: {}", rule.message); //! } //! # Ok::<(), Box<dyn std::error::Error>>(()) //! ``` }
Inline Comments
Use inline comments sparingly, focusing on why rather than what:
#![allow(unused)] fn main() { // Good: Explains reasoning // Use indirect offset to handle relocatable executables let actual_offset = resolve_indirect_offset(base_offset, buffer)?; // Bad: States the obvious // Set the offset to the resolved value let actual_offset = resolved_offset; }
Error Handling Style
Use Result Types
Always use Result
for fallible operations:
#![allow(unused)] fn main() { // Good pub fn parse_offset(input: &str) -> Result<OffsetSpec> { // Implementation that can fail } // Bad: Using Option for errors pub fn parse_offset(input: &str) -> Option<OffsetSpec> { // Loses error information } // Bad: Using panics pub fn parse_offset(input: &str) -> OffsetSpec { // Implementation that panics on error input.parse().unwrap() } }
Descriptive Error Messages
Provide context in error messages:
#![allow(unused)] fn main() { // Good: Specific, actionable error return Err(LibmagicError::ParseError { line: line_number, message: format!("Invalid offset '{}': expected number or hex value", input), }); // Bad: Generic error return Err(LibmagicError::ParseError { line: line_number, message: "parse error".to_string(), }); }
Error Propagation
Use the ?
operator for error propagation:
#![allow(unused)] fn main() { // Good pub fn load_and_parse(path: &Path) -> Result<Vec<MagicRule>> { let content = std::fs::read_to_string(path)?; let rules = parse_magic_string(&content)?; Ok(rules) } // Avoid: Manual error handling when ? works pub fn load_and_parse(path: &Path) -> Result<Vec<MagicRule>> { let content = match std::fs::read_to_string(path) { Ok(content) => content, Err(e) => return Err(LibmagicError::IoError(e)), }; // ... } }
Code Organization
Import Organization
Group imports in this order:
- Standard library
- External crates
- Internal crates/modules
#![allow(unused)] fn main() { // Standard library use std::collections::HashMap; use std::path::Path; // External crates use nom::{IResult, bytes::complete::tag}; use serde::{Deserialize, Serialize}; use thiserror::Error; // Internal modules use crate::evaluator::EvaluationContext; use crate::parser::ast::{MagicRule, OffsetSpec}; }
Function Organization
Organize functions logically within modules:
#![allow(unused)] fn main() { impl MagicRule { // Constructors first pub fn new(/* ... */) -> Self {} // Public methods pub fn evaluate(&self, buffer: &[u8]) -> Result<bool> {} pub fn message(&self) -> &str {} // Private helpers last fn validate_offset(&self) -> bool {} } }
File Organization
Keep files focused and reasonably sized (< 500-600 lines):
#![allow(unused)] fn main() { // Good: Focused module // src/parser/offset.rs - Only offset parsing logic // Bad: Everything in one file // src/parser/mod.rs - All parsing logic (thousands of lines) }
Testing Style
Test Organization
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; // Group related tests mod offset_parsing { use super::*; #[test] fn test_absolute_offset() { // Test implementation } #[test] fn test_indirect_offset() { // Test implementation } } mod error_handling { use super::*; #[test] fn test_invalid_syntax_error() { // Test implementation } } } }
Test Naming
Use descriptive test names that explain the scenario:
#![allow(unused)] fn main() { // Good: Descriptive names #[test] fn test_parse_absolute_offset_with_hex_value() {} #[test] fn test_parse_offset_returns_error_for_invalid_syntax() {} // Bad: Generic names #[test] fn test_parse_offset() {} #[test] fn test_error() {} }
Assertion Style
Use specific assertions with helpful messages:
#![allow(unused)] fn main() { // Good: Specific assertion with context assert_eq!( result.unwrap().message, "ELF executable", "Magic rule should identify ELF files correctly" ); // Good: Pattern matching for complex types match result { Ok(OffsetSpec::Absolute(offset)) => assert_eq!(offset, 42), _ => panic!("Expected absolute offset with value 42"), } // Avoid: Generic assertions assert!(result.is_ok()); }
Performance Considerations
Prefer Borrowing
Use references instead of owned values when possible:
#![allow(unused)] fn main() { // Good: Borrowing pub fn evaluate_rule(rule: &MagicRule, buffer: &[u8]) -> Result<bool> {} // Avoid: Unnecessary ownership pub fn evaluate_rule(rule: MagicRule, buffer: Vec<u8>) -> Result<bool> {} }
Avoid Unnecessary Allocations
#![allow(unused)] fn main() { // Good: String slice pub fn parse_message(input: &str) -> &str { input.trim() } // Avoid: Unnecessary allocation pub fn parse_message(input: &str) -> String { input.trim().to_string() } }
Use Appropriate Data Structures
#![allow(unused)] fn main() { // Good: Vec for ordered data let rules: Vec<MagicRule> = parse_rules(input)?; // Good: HashMap for key-value lookups let mime_types: HashMap<String, String> = load_mime_mappings()?; // Consider: BTreeMap for sorted keys let sorted_rules: BTreeMap<u32, MagicRule> = rules_by_priority(); }
This style guide ensures consistent, readable, and maintainable code across the libmagic-rs project. All contributors should follow these guidelines, and automated tools enforce many of these rules during CI.