# Architecture
This document explains the architectural decisions in `bare-script`, designed to be understandable by both humans and AI systems.
## Overview
`bare-script` is a **type-safe shell command execution library** built on three pillars:
1. **Zero-cost abstractions**: Type-safe API with minimal runtime overhead
2. **Parse, don't validate**: Validate once at boundaries, trust types thereafter
3. **Explicit over implicit**: Clear contracts, no hidden behavior
## Design Philosophy Deep Dive
### "Parse, Don't Validate"
This concept means:
```
❌ DON'T: Validate everywhere
CommandBuilder::new("ls")
.arg(input); // No validation, could be invalid
✅ DO: Parse once, use types
let args = Args::new()
.flag("-v", "--verbose")
.option("-o", "--output")
.value("file.txt")
.validate()?; // Validation happens here
```
**Why This Matters:**
- **For Humans**: Reduces cognitive load - once validated, you can trust the arguments
- **For AI**: Creates clear boundaries where validation logic belongs
### Builder Pattern
All command building uses the Builder pattern:
```rust
CommandBuilder::new("ls")
.arg("-l")
.arg("-h")
.env("DEBUG", "true")
.current_dir("/tmp")
.capture_output()
.execute()?;
```
**Benefits:**
- Fluency: Chain method calls for readable code
- Immutability: Each method returns a new builder (no mutability)
- Type safety: Compile-time guarantee of valid command structure
## Module Architecture
```
bare-script/
├── lib.rs # Crate root, public exports
├── args.rs # Type-safe argument builder
├── config.rs # Command configuration
├── error.rs # Error types
├── output.rs # Command output types
├── logging.rs # Optional logging (feature = "logging")
├── shell.rs # Shell integration helpers
├── sync/ # Synchronous execution
│ ├── mod.rs
│ ├── builder.rs # CommandBuilder for sync
│ └── pipeline.rs # Pipeline for sync
└── proc/ # Asynchronous execution (feature = "tokio-rt")
├── mod.rs
├── builder.rs # CommandBuilder for async
└── pipeline.rs # Pipeline for async
```
### Feature-Gated Modules
Modules are conditionally compiled based on features:
```rust
#[cfg(feature = "tokio-rt")]
pub mod proc;
#[cfg(feature = "logging")]
pub mod logging;
```
**Design Decision**: Async support is behind a feature gate to:
- Minimize compile times for users who don't need async
- Reduce binary size for simple sync use cases
- Allow gradual adoption
## Type Hierarchy
### Core Traits
#### `CommandBuilder`
The main entry point for building commands:
```rust
pub struct CommandBuilder {
cmd: Command, // std::process::Command or tokio::process::Command
}
```
#### `Args`
Type-safe argument builder:
```rust
pub struct Args {
args: Vec<Arg>,
positional: Vec<Arg>,
}
pub enum ArgType {
Flag, // e.g., -v, --verbose
Option, // e.g., -o file, --output=file
Positional, // e.g., filename
}
```
#### `Output`
Captured command output:
```rust
pub struct Output {
stdout: Vec<u8>,
stderr: Vec<u8>,
status: ExitStatus,
}
```
### Error Types
All errors use `thiserror` for rich error messages:
```rust
pub enum ScriptError {
CommandNotFound(String),
PermissionDenied(String),
InvalidArgument(String),
InvalidWorkingDirectory(String),
ExecutionFailed { code: i32, stderr: String },
Timeout(Duration),
IoError(std::io::Error),
// ... more variants
}
```
## Synchronous vs Asynchronous
### Sync API
Uses `std::process::Command`:
```rust
use bare_script::sync::CommandBuilder;
let output = CommandBuilder::new("ls")
.arg("-l")
.execute()?;
```
### Async API
Uses `tokio::process::Command`:
```rust
use bare_script::proc::CommandBuilder;
let output = CommandBuilder::new("ls")
.arg("-l")
.execute()
.await?;
```
## Pipeline Support
Both sync and async APIs support command pipelines:
```rust
use bare_script::sync::Pipeline;
let output = Pipeline::new("echo")
.arg("hello world")
.pipe("grep")
.arg("hello")
.capture_output()
.execute()?;
```
## Cross-Platform Design
### Windows
- Default shell: `cmd`
- Shell flag: `/C`
- Uses `creation_flags` for process creation
### Unix
- Default shell: `sh`
- Shell flag: `-c`
- Uses `process_group`, `setsid`, `uid`, `gid` for process control
## Security Considerations
1. **No shell injection**: Arguments are passed directly, not through shell
2. **Environment isolation**: Can clear or remove environment variables
3. **Working directory control**: Explicit control over execution directory
4. **Timeout support**: Prevents hanging processes
## Performance Characteristics
- **Zero-cost abstractions**: After validation, command execution has minimal overhead
- **Process spawn overhead**: Actual command execution includes OS process creation (~5-10ms)
- **Memory**: Small footprint, no internal buffering
## Extension Points
### Custom Arguments
Use the `Args` builder for complex argument patterns:
```rust
let args = Args::new()
.flag("-v", "--verbose")
.option("-o", "--output")
.value("file.txt")
.positional()
.validate()?;
```
### Custom Configuration
Use `Config` for reusable configurations:
```rust
let config = Config::new()
.env("DEBUG", "true")
.cwd("/tmp");
```