595 lines
13 KiB
Plaintext
595 lines
13 KiB
Plaintext
|
|
# {{TOOL_NAME}} Architecture
|
||
|
|
|
||
|
|
Complete system design and component documentation.
|
||
|
|
|
||
|
|
## High-Level Overview
|
||
|
|
|
||
|
|
{{TOOL_NAME}} is a production-grade tool implementing a **3-tier architecture**:
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────┐
|
||
|
|
│ Tier 3: CLI / User Interface │
|
||
|
|
│ crates/{{tool_name_kebab}}-cli/ │
|
||
|
|
│ - clap command parsing │
|
||
|
|
│ - Configuration discovery │
|
||
|
|
│ - User-friendly output │
|
||
|
|
└─────────────────────────────────────────────┘
|
||
|
|
↓
|
||
|
|
┌─────────────────────────────────────────────┐
|
||
|
|
│ Tier 2: REST API (optional) │
|
||
|
|
│ crates/{{tool_name_kebab}}-api/ │
|
||
|
|
│ - axum HTTP server │
|
||
|
|
│ - JSON request/response │
|
||
|
|
│ - RESTful endpoints │
|
||
|
|
└─────────────────────────────────────────────┘
|
||
|
|
↓
|
||
|
|
┌─────────────────────────────────────────────┐
|
||
|
|
│ Tier 1: Core Library │
|
||
|
|
│ crates/{{tool_name_kebab}}-core/ │
|
||
|
|
│ - Business logic │
|
||
|
|
│ - Domain types │
|
||
|
|
│ - Service layer │
|
||
|
|
│ - Error handling │
|
||
|
|
│ - Data persistence │
|
||
|
|
└─────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## Tier 1: Core Library
|
||
|
|
|
||
|
|
**Location**: `crates/{{tool_name_kebab}}-core/`
|
||
|
|
|
||
|
|
**Purpose**: Reusable, interface-agnostic business logic
|
||
|
|
|
||
|
|
### Components
|
||
|
|
|
||
|
|
#### Error Handling
|
||
|
|
|
||
|
|
**File**: `src/error.rs`
|
||
|
|
|
||
|
|
Canonical error struct pattern:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct {{ToolName}}Error {
|
||
|
|
pub kind: {{ToolName}}ErrorKind,
|
||
|
|
pub context: String,
|
||
|
|
pub source: Option<Box<dyn Error>>,
|
||
|
|
pub backtrace: Backtrace,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Error Types** (customize for your domain):
|
||
|
|
- `ConfigError` - Configuration issues
|
||
|
|
- `ValidationError` - Input validation
|
||
|
|
- `IoError` - File I/O failures
|
||
|
|
- `DatabaseError` - Database operations
|
||
|
|
- Custom domain errors as needed
|
||
|
|
|
||
|
|
**Usage**:
|
||
|
|
```rust
|
||
|
|
fn operation() -> Result<T> {
|
||
|
|
if condition {
|
||
|
|
return Err({{ToolName}}Error::new(
|
||
|
|
{{ToolName}}ErrorKind::ValidationError("message".into()),
|
||
|
|
"context about what failed",
|
||
|
|
));
|
||
|
|
}
|
||
|
|
Ok(value)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Domain Types
|
||
|
|
|
||
|
|
**File**: `src/types.rs`
|
||
|
|
|
||
|
|
Primary domain type and related structures:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct {{MainType}} {
|
||
|
|
pub id: String, // UUID v4
|
||
|
|
pub name: String, // Human-readable name
|
||
|
|
pub description: String, // Details
|
||
|
|
pub metadata: serde_json::Value, // Extensibility
|
||
|
|
pub created_at: DateTime<Utc>, // Creation timestamp
|
||
|
|
pub updated_at: DateTime<Utc>, // Last modification
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Additional Types**:
|
||
|
|
- Enums for status, priority, categories
|
||
|
|
- Configuration structures (serde + validation)
|
||
|
|
- Query filters and responses
|
||
|
|
- Transfer objects for APIs
|
||
|
|
|
||
|
|
#### Service Layer
|
||
|
|
|
||
|
|
**File**: `src/handlers/service.rs`
|
||
|
|
|
||
|
|
Business logic implementation:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct {{MainType}}Service {
|
||
|
|
items: Arc<Mutex<HashMap<String, {{MainType}}>>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl {{MainType}}Service {
|
||
|
|
pub async fn create(...) -> Result<{{MainType}}>
|
||
|
|
pub async fn get(&self, id: &str) -> Result<{{MainType}}>
|
||
|
|
pub async fn list(&self) -> Result<Vec<{{MainType}}>>
|
||
|
|
pub async fn update(...) -> Result<{{MainType}}>
|
||
|
|
pub async fn delete(&self, id: &str) -> Result<()>
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Storage Options**:
|
||
|
|
- **In-memory** (template default) - Dev/testing only
|
||
|
|
- **SQLite** - Single-user, embedded
|
||
|
|
- **PostgreSQL** - Production, concurrent access
|
||
|
|
- **Custom** - Implement `Storage` trait
|
||
|
|
|
||
|
|
**Pattern**: Async/await with tokio for all I/O operations
|
||
|
|
|
||
|
|
#### Module Organization
|
||
|
|
|
||
|
|
```
|
||
|
|
src/
|
||
|
|
├── lib.rs # Exports and documentation
|
||
|
|
├── error.rs # {{ToolName}}Error, {{ToolName}}ErrorKind
|
||
|
|
├── types.rs # {{MainType}} and related types
|
||
|
|
└── handlers/
|
||
|
|
├── mod.rs # Module organization
|
||
|
|
└── service.rs # {{MainType}}Service, business logic
|
||
|
|
```
|
||
|
|
|
||
|
|
### Testing
|
||
|
|
|
||
|
|
**Unit Tests** (in each module file):
|
||
|
|
```rust
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_create() { ... }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Integration Tests** (`tests/` directory):
|
||
|
|
```
|
||
|
|
tests/
|
||
|
|
├── common/
|
||
|
|
│ └── mod.rs # Shared test utilities
|
||
|
|
└── integration_test.rs
|
||
|
|
```
|
||
|
|
|
||
|
|
**Requirements**:
|
||
|
|
- Minimum 15 tests per module
|
||
|
|
- Test success paths
|
||
|
|
- Test error paths
|
||
|
|
- Test edge cases
|
||
|
|
- All tests must pass before commit
|
||
|
|
|
||
|
|
## Tier 2: REST API (Optional)
|
||
|
|
|
||
|
|
**Location**: `crates/{{tool_name_kebab}}-api/`
|
||
|
|
|
||
|
|
**Purpose**: HTTP interface to core library
|
||
|
|
|
||
|
|
### Components
|
||
|
|
|
||
|
|
#### Web Server
|
||
|
|
|
||
|
|
**Framework**: axum (Rust async web framework)
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<()> {
|
||
|
|
let app = Router::new()
|
||
|
|
.route("/health", get(health_check))
|
||
|
|
.route("/{{main_type_plural}}", get(list).post(create))
|
||
|
|
.route("/{{main_type_plural}}/:id", get(get_one).put(update).delete(delete))
|
||
|
|
.with_state(Arc::new(state));
|
||
|
|
|
||
|
|
axum::serve(listener, app).await?;
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Request/Response Types
|
||
|
|
|
||
|
|
**Request** (input from client):
|
||
|
|
```rust
|
||
|
|
#[derive(Deserialize)]
|
||
|
|
struct CreateRequest {
|
||
|
|
name: String,
|
||
|
|
description: String,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response** (output to client):
|
||
|
|
```rust
|
||
|
|
#[derive(Serialize)]
|
||
|
|
struct {{MainType}}Response {
|
||
|
|
id: String,
|
||
|
|
name: String,
|
||
|
|
created_at: DateTime<Utc>,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Endpoints
|
||
|
|
|
||
|
|
Standard CRUD with HTTP semantics:
|
||
|
|
|
||
|
|
| Method | Path | Description | Status |
|
||
|
|
|--------|------|-------------|--------|
|
||
|
|
| GET | `/health` | Health check | 200 OK |
|
||
|
|
| GET | `/{{main_type_plural}}` | List all | 200 OK |
|
||
|
|
| GET | `/{{main_type_plural}}/:id` | Get one | 200 OK or 404 |
|
||
|
|
| POST | `/{{main_type_plural}}` | Create | 201 Created |
|
||
|
|
| PUT | `/{{main_type_plural}}/:id` | Update | 200 OK or 404 |
|
||
|
|
| DELETE | `/{{main_type_plural}}/:id` | Delete | 204 No Content or 404 |
|
||
|
|
|
||
|
|
#### Error Handling
|
||
|
|
|
||
|
|
Structured error responses:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "Validation error",
|
||
|
|
"details": "Name is required",
|
||
|
|
"code": "VALIDATION_ERROR"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Deployment
|
||
|
|
|
||
|
|
**Local Development**:
|
||
|
|
```bash
|
||
|
|
cargo run -p {{tool_name_kebab}}-api
|
||
|
|
# Server on http://127.0.0.1:3000
|
||
|
|
```
|
||
|
|
|
||
|
|
**Production**:
|
||
|
|
```bash
|
||
|
|
cargo build -p {{tool_name_kebab}}-api --release
|
||
|
|
./target/release/{{tool_name_kebab}}-api
|
||
|
|
```
|
||
|
|
|
||
|
|
**Docker**:
|
||
|
|
```dockerfile
|
||
|
|
FROM rust:latest as builder
|
||
|
|
WORKDIR /app
|
||
|
|
COPY . .
|
||
|
|
RUN cargo build --release
|
||
|
|
|
||
|
|
FROM debian:bookworm-slim
|
||
|
|
COPY --from=builder /app/target/release/{{tool_name_kebab}}-api /usr/bin/
|
||
|
|
ENTRYPOINT ["{{tool_name_kebab}}-api"]
|
||
|
|
```
|
||
|
|
|
||
|
|
## Tier 3: CLI
|
||
|
|
|
||
|
|
**Location**: `crates/{{tool_name_kebab}}-cli/`
|
||
|
|
|
||
|
|
**Purpose**: User-friendly command-line interface
|
||
|
|
|
||
|
|
### Components
|
||
|
|
|
||
|
|
#### Command Parser
|
||
|
|
|
||
|
|
**Framework**: clap (derive macros for CLI parsing)
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[derive(Parser)]
|
||
|
|
#[command(name = "{{tool_name_kebab}}")]
|
||
|
|
#[command(about = "{{TOOL_NAME}} - {{SHORT_DESCRIPTION}}")]
|
||
|
|
struct Cli {
|
||
|
|
#[arg(short, long)]
|
||
|
|
config: Option<PathBuf>,
|
||
|
|
|
||
|
|
#[arg(short, long, global = true)]
|
||
|
|
verbose: bool,
|
||
|
|
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: Commands,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Configuration Discovery
|
||
|
|
|
||
|
|
**Implementation**: tools-shared library
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use tools_shared::find_config_path;
|
||
|
|
|
||
|
|
let config_path = find_config_path("{{tool_name_kebab}}.toml")
|
||
|
|
.unwrap_or_else(|| PathBuf::from(".project/{{tool_name_kebab}}.toml"));
|
||
|
|
```
|
||
|
|
|
||
|
|
**Search Order**:
|
||
|
|
1. `.project/{{tool_name_kebab}}.toml` - Tools ecosystem
|
||
|
|
2. `.vapora/{{tool_name_kebab}}.toml` - VAPORA projects
|
||
|
|
3. `.coder/{{tool_name_kebab}}.toml` - Doc tracking
|
||
|
|
4. `./{{tool_name_kebab}}.toml` - Current directory
|
||
|
|
|
||
|
|
**Help Text**:
|
||
|
|
```
|
||
|
|
CONFIGURATION SEARCH:
|
||
|
|
Looks for config in order (uses first found):
|
||
|
|
|
||
|
|
1. .project/{{tool_name_kebab}}.toml
|
||
|
|
2. .vapora/{{tool_name_kebab}}.toml
|
||
|
|
3. .coder/{{tool_name_kebab}}.toml
|
||
|
|
4. ./{{tool_name_kebab}}.toml
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Commands
|
||
|
|
|
||
|
|
Organized into subcommands:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[derive(Subcommand)]
|
||
|
|
enum Commands {
|
||
|
|
/// List items
|
||
|
|
List {
|
||
|
|
#[arg(short, long)]
|
||
|
|
filter: Option<String>,
|
||
|
|
|
||
|
|
#[arg(short, long, default_value = "20")]
|
||
|
|
limit: usize,
|
||
|
|
},
|
||
|
|
|
||
|
|
/// Show summary
|
||
|
|
Summary {
|
||
|
|
#[arg(short, long)]
|
||
|
|
group_by: Option<String>,
|
||
|
|
},
|
||
|
|
|
||
|
|
/// Create new item
|
||
|
|
Create {
|
||
|
|
name: String,
|
||
|
|
|
||
|
|
#[arg(short, long)]
|
||
|
|
description: Option<String>,
|
||
|
|
},
|
||
|
|
|
||
|
|
/// Show version
|
||
|
|
Version,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Logging
|
||
|
|
|
||
|
|
Structured logging with tracing:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Initialize
|
||
|
|
let log_level = if cli.verbose { "debug" } else { "info" };
|
||
|
|
tracing_subscriber::fmt()
|
||
|
|
.with_env_filter(log_level)
|
||
|
|
.init();
|
||
|
|
|
||
|
|
// Usage
|
||
|
|
tracing::info!("Starting operation");
|
||
|
|
tracing::debug!("Debug info");
|
||
|
|
tracing::warn!("Warning message");
|
||
|
|
tracing::error!("Error occurred");
|
||
|
|
```
|
||
|
|
|
||
|
|
### Data Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
User runs command
|
||
|
|
↓
|
||
|
|
clap parses arguments
|
||
|
|
↓
|
||
|
|
Config discovery (tools-shared)
|
||
|
|
↓
|
||
|
|
Load configuration (serde + TOML)
|
||
|
|
↓
|
||
|
|
Validate configuration
|
||
|
|
↓
|
||
|
|
Initialize service from core library
|
||
|
|
↓
|
||
|
|
Execute command (call service method)
|
||
|
|
↓
|
||
|
|
Format output (JSON, table, text)
|
||
|
|
↓
|
||
|
|
Display to user
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration System
|
||
|
|
|
||
|
|
### Configuration File (TOML)
|
||
|
|
|
||
|
|
**Structure**:
|
||
|
|
```toml
|
||
|
|
[{{tool_name_kebab}}]
|
||
|
|
# Main settings
|
||
|
|
setting1 = "value1"
|
||
|
|
setting2 = 42
|
||
|
|
enable_feature = true
|
||
|
|
|
||
|
|
[{{tool_name_kebab}}.advanced]
|
||
|
|
# Advanced options
|
||
|
|
max_items = 1000
|
||
|
|
timeout_seconds = 30
|
||
|
|
|
||
|
|
[database]
|
||
|
|
# If using database
|
||
|
|
type = "sqlite"
|
||
|
|
path = ".project/{{tool_name_kebab}}.db"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Configuration Validation
|
||
|
|
|
||
|
|
In CLI before execution:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
fn validate_config(config: &Config) -> Result<()> {
|
||
|
|
if config.name.is_empty() {
|
||
|
|
return Err(ConfigError::EmptyName);
|
||
|
|
}
|
||
|
|
if config.timeout < 0 {
|
||
|
|
return Err(ConfigError::InvalidTimeout);
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Environment Variable Overrides
|
||
|
|
|
||
|
|
Support ENV vars for key settings:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
export {{TOOL_NAME}}_SETTING1=value1
|
||
|
|
export {{TOOL_NAME}}_TIMEOUT=60
|
||
|
|
|
||
|
|
{{tool_name_kebab}} list
|
||
|
|
# Uses ENV values if set, otherwise config file
|
||
|
|
```
|
||
|
|
|
||
|
|
## Data Persistence
|
||
|
|
|
||
|
|
### Storage Interface
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub trait Storage {
|
||
|
|
async fn create(&mut self, item: {{MainType}}) -> Result<()>;
|
||
|
|
async fn get(&self, id: &str) -> Result<{{MainType}}>;
|
||
|
|
async fn list(&self) -> Result<Vec<{{MainType}}>
|
||
|
|
async fn update(&mut self, item: {{MainType}}) -> Result<()>;
|
||
|
|
async fn delete(&mut self, id: &str) -> Result<()>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Storage Implementations
|
||
|
|
|
||
|
|
**In-Memory** (template default):
|
||
|
|
- Fast for development
|
||
|
|
- Data lost on restart
|
||
|
|
- Thread-safe with Arc<Mutex<>>
|
||
|
|
|
||
|
|
**SQLite**:
|
||
|
|
- File-based, embedded
|
||
|
|
- Good for single-user tools
|
||
|
|
- Use sqlx with async
|
||
|
|
|
||
|
|
**PostgreSQL**:
|
||
|
|
- Server-based
|
||
|
|
- Multiple concurrent users
|
||
|
|
- Use sqlx with connection pooling
|
||
|
|
|
||
|
|
## Error Propagation
|
||
|
|
|
||
|
|
```
|
||
|
|
Tier 3 (CLI)
|
||
|
|
↓ catches
|
||
|
|
Tier 2 (API)
|
||
|
|
↓ catches
|
||
|
|
Tier 1 (Core)
|
||
|
|
↓ propagates
|
||
|
|
Result<T, Error>
|
||
|
|
↓ back up
|
||
|
|
Format for user/response
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing Strategy
|
||
|
|
|
||
|
|
### Unit Tests (Tier 1)
|
||
|
|
- Test each service method
|
||
|
|
- Mock storage
|
||
|
|
- Test error conditions
|
||
|
|
- Minimum 15 tests
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
- Test with real storage
|
||
|
|
- Test full workflows
|
||
|
|
- Test configuration loading
|
||
|
|
- Test CLI argument parsing
|
||
|
|
|
||
|
|
### End-to-End Tests
|
||
|
|
- Test complete user workflows
|
||
|
|
- Test all commands
|
||
|
|
- Test error scenarios
|
||
|
|
- Test configuration discovery
|
||
|
|
|
||
|
|
## Performance Considerations
|
||
|
|
|
||
|
|
### Optimization Points
|
||
|
|
- Configuration caching
|
||
|
|
- Lazy loading of items
|
||
|
|
- Database connection pooling
|
||
|
|
- Async/await for all I/O
|
||
|
|
|
||
|
|
### Bottlenecks to Avoid
|
||
|
|
- Synchronous blocking I/O
|
||
|
|
- Loading all items in memory
|
||
|
|
- Repeated configuration parsing
|
||
|
|
- Unnecessary cloning
|
||
|
|
|
||
|
|
### Monitoring
|
||
|
|
- Structured logging (tracing)
|
||
|
|
- Performance metrics
|
||
|
|
- Error tracking
|
||
|
|
- Resource usage
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
### Input Validation
|
||
|
|
- Validate all user input
|
||
|
|
- Sanitize configuration values
|
||
|
|
- Validate file paths
|
||
|
|
- Check for path traversal
|
||
|
|
|
||
|
|
### Error Handling
|
||
|
|
- Don't expose internal paths
|
||
|
|
- Don't leak sensitive data
|
||
|
|
- Provide helpful error messages
|
||
|
|
- Log with appropriate levels
|
||
|
|
|
||
|
|
### Dependencies
|
||
|
|
- Audit external crates (cargo audit)
|
||
|
|
- Keep dependencies updated
|
||
|
|
- Minimize external unsafe code
|
||
|
|
- Pin versions in production
|
||
|
|
|
||
|
|
## Extension Points
|
||
|
|
|
||
|
|
### Adding New Commands
|
||
|
|
1. Add variant to Commands enum
|
||
|
|
2. Implement handler
|
||
|
|
3. Add tests
|
||
|
|
4. Update help text
|
||
|
|
5. Update documentation
|
||
|
|
|
||
|
|
### Adding New Service Methods
|
||
|
|
1. Implement in {{MainType}}Service
|
||
|
|
2. Add error handling
|
||
|
|
3. Add tests (success and error paths)
|
||
|
|
4. Update documentation
|
||
|
|
5. Expose in CLI or API
|
||
|
|
|
||
|
|
### Adding New Storage Backend
|
||
|
|
1. Implement Storage trait
|
||
|
|
2. Add configuration option
|
||
|
|
3. Add feature flag
|
||
|
|
4. Add tests
|
||
|
|
5. Update documentation
|
||
|
|
|
||
|
|
## Deployment Checklist
|
||
|
|
|
||
|
|
- ✅ All tests pass (cargo test --workspace)
|
||
|
|
- ✅ No clippy warnings (cargo clippy)
|
||
|
|
- ✅ Code formatted (cargo fmt)
|
||
|
|
- ✅ No vulnerabilities (cargo audit)
|
||
|
|
- ✅ Documentation complete
|
||
|
|
- ✅ Help text clear and accurate
|
||
|
|
- ✅ Configuration discoverable
|
||
|
|
- ✅ Error messages helpful
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- **Complete README**: [README.md](README.md)
|
||
|
|
- **Quick Start**: [QUICKSTART.md](QUICKSTART.md)
|
||
|
|
- **Configuration Guide**: [configuration.md](configuration.md)
|
||
|
|
- **Code**: `crates/{{tool_name_kebab}}-*/src/`
|
||
|
|
|