3 releases (breaking)
Uses new Rust 2024
new 0.3.0 | May 11, 2025 |
---|---|
0.2.0 | May 10, 2025 |
0.1.5 | May 9, 2025 |
0.1.0 |
|
#241 in Filesystem
574 downloads per month
74KB
1K
SLoC
File Structure Validator (fsvalidator)
A Rust library for validating real filesystem directories against a declarative, strongly-typed schema. Useful for enforcing project structure, data ingestion layout, or configuration rules.
Features
- Flexible Matching: Validate files and directories using both literal names and regex patterns
- Nested Hierarchies: Support for complex directory trees with arbitrary depth
- Template System: Reuse common structures via a template reference system
- Strictness Controls: Fine-grained control over validation strictness (required files, restricted directories)
- Format Options: Define validation rules in TOML or JSON formats
- Fluent Builder API: Chain methods to programmatically construct validation structures
- Categorized Errors: Validation errors are categorized for programmatic usage and visualization
- Colorful Display: Eye-catching, symbol-rich display with helpful visual indicators
- Comprehensive Validation: Reports all validation errors with clear, hierarchical organization
Installation
Add to your Cargo.toml
:
[dependencies]
fsvalidator = { version = "0.3.0", features = ["toml"] } # or "json" feature
Quick Start
Using TOML Configuration
use anyhow::Result;
use fsvalidator::from_toml;
use fsvalidator::display::{format_validation_result, COLORS, SYMBOLS};
fn main() -> Result<()> {
// Print a colorful header
println!("{}{}Filesystem Validator{}", COLORS.bold, COLORS.blue, COLORS.reset);
// Load structure definition from TOML
let root = from_toml("path/to/fsvalidator.toml")?;
// Display the parsed structure (optional)
println!("Structure Definition:\n{root}");
let path = "./path/to/validate";
// Validate with categorized errors
let result = root.validate(path);
// Display results with colorful, symbolic output
println!("{}", format_validation_result(&result, path));
// Programmatically check results
if let Err(errors) = &result {
// You can check error categories for different handling
let has_missing = errors.children.iter()
.any(|err| matches!(err.category, fsvalidator::ErrorCategory::Missing));
if has_missing {
println!("{}{}Missing required files!{}",
COLORS.yellow, SYMBOLS.warning, COLORS.reset);
}
}
Ok(())
}
Using Builder API
use anyhow::Result;
use fsvalidator::ModelBuilder;
use fsvalidator::display::format_validation_result;
fn main() -> Result<()> {
// Create a project structure using the builder API
let project = ModelBuilder::new_dir("project")
.required(true)
.allow_defined_only(true)
.add_file("README.md", true)
.add_dir("src", true)
.add_file_pattern(".*\\.rs", true)
.up()
.add_dir("tests", false)
.add_file_pattern("test_.*\\.rs", false)
.build();
// Validate a directory against the defined structure
let path = "./path/to/validate";
let validation_result = project.validate(path);
// Display results with colorful, symbolic output
println!("{}", format_validation_result(&validation_result, path));
Ok(())
}
Definition Format
TOML Example
# Root directory definition
[root]
type = "dir"
name = "project"
required = true
allow_defined_only = true
excluded = ["fsvalidator\\..*"]
# Define children of the root directory
[[root.children]]
type = "file"
name = "README.md"
required = true
[[root.children]]
type = "dir"
name = "src"
required = true
# Define children of the src directory
[[root.children.children]]
type = "file"
pattern = ".*\.rs"
required = true
# Reference to a template
[[root.children]]
type = "ref"
ref = "test_directory"
# Template definition
[template.test_directory]
type = "dir"
name = "tests"
[[template.test_directory.children]]
type = "file"
pattern = "test_.*\.rs"
# Global settings (applied to all nodes unless overridden)
[global]
required = false
allow_defined_only = false
excluded = ["^\\..*", "\\.DS_Store"]
JSON Example
{
"root": {
"type": "dir",
"name": "project",
"required": true,
"allow_defined_only": true,
"children": [
{
"type": "file",
"name": "README.md",
"required": true
},
{
"type": "dir",
"name": "src",
"required": true,
"children": [
{
"type": "file",
"pattern": ".*\.rs",
"required": true
}
]
},
{
"type": "ref",
"ref": "test_directory"
}
]
},
"template": {
"test_directory": {
"type": "dir",
"name": "tests",
"required": false,
"children": [
{
"type": "file",
"pattern": "test_.*\.rs",
"required": false
}
]
}
},
"global": {
"required": false,
"allow_defined_only": false
}
}
Definition Structure
Node Types
dir
: A directory nodefile
: A file noderef
: A reference to a template
Node Properties
name
: Literal name of the file or directorypattern
: Regex pattern to match against file or directory name (use eithername
orpattern
, not both)required
: Whether the node must exist (default: false)allow_defined_only
: For directories, whether only defined children are allowed (default: false)excluded
: Regex pattern to exclude files/dirs when validating child nodeschildren
: List of child nodes (only valid fordir
type)ref
: Template reference name (only valid forref
type)
Special Sections
root
: The root node of the validation treetemplate
: Dictionary of reusable node templatesglobal
: Global settings applied to all nodes unless overriddenconfig
: Configuration options for the validator
Model Structure
// Main node types
enum Node {
Dir(Rc<RefCell<DirNode>>),
File(Rc<RefCell<FileNode>>),
}
// Directory node with children
struct DirNode {
name: NodeName,
children: Vec<Node>,
required: bool,
allow_defined_only: bool,
}
// File node
struct FileNode {
name: NodeName,
required: bool,
}
// Node name (literal or pattern)
enum NodeName {
Literal(String),
Pattern(String), // Regex pattern
}
Use Cases
- Enforcing consistent project layouts
- Validating data pipeline inputs/outputs
- Ensuring configuration directories are correctly structured
- Verifying deployment artifacts
- Testing file-based APIs
Advanced Usage
Programmatic Structure Creation
Using Direct Constructor Methods
You can create validation structures using the constructors directly:
use fsvalidator::model::{DirNode, FileNode, Node, NodeName};
// Create a file node
let readme = FileNode::new(NodeName::Literal("README.md".to_string()), true);
// Create a directory with pattern-matched files
let src_files = FileNode::new(NodeName::Pattern(".*\.rs".to_string()), true);
let src_dir = DirNode::new(
NodeName::Literal("src".to_string()),
vec![src_files],
true,
false,
vec!["excluded_pattern"],
);
// Create the project root
let project = DirNode::new(
NodeName::Literal("project".to_string()),
vec![readme, src_dir],
true,
true,
vec!["excluded_pattern"],
);
// Validate with colorful display
let path = "path/to/project";
let result = project.validate(path);
println!("{}", format_validation_result(&result, path));
// Process errors programmatically by category
if let Err(errors) = &result {
// Count errors by category
let missing_count = count_errors_by_category(errors, fsvalidator::ErrorCategory::Missing);
let unexpected_count = count_errors_by_category(errors, fsvalidator::ErrorCategory::Unexpected);
println!("Found {} missing files and {} unexpected entries", missing_count, unexpected_count);
}
Using Builder API (Recommended)
The builder API provides a more readable and chainable way to construct validation structures:
use fsvalidator::ModelBuilder;
// Create the project structure with a fluent builder API
let project = ModelBuilder::new_dir("project")
.required(true)
.allow_defined_only(true)
.exclude_patterns(vec!["^\\..*", "\\.DS_Store"])
.add_file("README.md", true)
.add_dir("src", true)
.add_file_pattern(".*\\.rs", true)
.up()
.add_dir("tests", false)
.add_file_pattern("test_.*\\.rs", false)
.build();
// Validate with colorful display
let path = "path/to/project";
let result = project.validate(path);
println!("{}", format_validation_result(&result, path));
Error Categories
Validation errors are categorized for programmatic handling:
- Missing: Required files or directories that don't exist
- WrongType: Path exists but is the wrong type (e.g., file instead of directory)
- NameMismatch: File/directory name doesn't match the expected pattern
- Unexpected: Unexpected entry in a directory with
allow_defined_only=true
- InvalidPattern: Error in a regex pattern
- IoError: Filesystem access errors
- Other: Miscellaneous errors
License
MIT OR Apache-2.0
Dependencies
~2.6–4.5MB
~81K SLoC