Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
431 lines
14 KiB
Markdown
431 lines
14 KiB
Markdown
# API Server Development Guideline
|
|
|
|
**Status**: Production Ready
|
|
**Version**: 1.0
|
|
**Last Updated**: 2025-11-13
|
|
**Applies To**: All REST API servers in the syntaxis ecosystem
|
|
|
|
## Overview
|
|
|
|
This guideline defines the standard pattern for creating and reviewing REST API servers that follow **Project Agnostic Practice (PAP)** principles. All API servers MUST adhere to these rules to ensure consistency, portability, and production-readiness.
|
|
|
|
## Core Principles
|
|
|
|
### 1. Configuration-Driven (No Hardcoded Paths)
|
|
- **MUST**: Use explicit `--config <PATH>` CLI argument
|
|
- **MUST**: Load configuration from TOML file
|
|
- **MUST NOT**: Use hardcoded paths or project-relative paths
|
|
- **MUST NOT**: Use environment variable overrides
|
|
- **MUST NOT**: Provide default values - all settings must be explicit
|
|
|
|
**Why**: Ensures server can run anywhere without modification, follows PAP.
|
|
|
|
### 2. Strict Configuration Enforcement
|
|
- **MUST**: Require all configuration fields in TOML
|
|
- **MUST**: Fail with clear error message if config file missing
|
|
- **MUST**: Fail with clear error message if required fields missing
|
|
- **MUST**: Show file path in error messages
|
|
|
|
**Why**: Forces users to understand configuration, prevents silent failures.
|
|
|
|
### 3. Configuration File Structure
|
|
- **MUST**: Use `[server]` table in TOML
|
|
- **MUST**: Extract `[server]` section explicitly for parsing
|
|
- **MUST**: Provide template file in project root: `<tool>-config.template.toml`
|
|
- **MUST**: Include comments explaining each field
|
|
|
|
**Why**: Clear, discoverable configuration with examples for users.
|
|
|
|
### 4. Required Configuration Fields
|
|
Every API server MUST support these core settings:
|
|
|
|
```toml
|
|
[server]
|
|
host = "127.0.0.1" # Bind address (string)
|
|
port = 3000 # Port number (integer)
|
|
database_path = "./db.db" # Database file path (string, absolute or relative)
|
|
public_files_path = "./public" # Static files directory (string, NEW!)
|
|
cors_enabled = true # CORS support (boolean)
|
|
log_level = "info" # Logging level (string: trace/debug/info/warn/error)
|
|
|
|
[server.tls] # Optional TLS/HTTPS
|
|
enabled = false
|
|
cert_path = "/path/to/cert.pem" # Required if enabled
|
|
key_path = "/path/to/key.pem" # Required if enabled
|
|
```
|
|
|
|
### 5. Logging Configuration
|
|
- **MUST**: Apply `log_level` from config to tracing_subscriber
|
|
- **MUST**: Support levels: trace, debug, info, warn, error
|
|
- **MUST**: Log all configuration values during startup (except secrets)
|
|
- **MUST**: Log TLS status (enabled/disabled/configured)
|
|
- **MUST**: Log static files directory status
|
|
|
|
**Example Output**:
|
|
```
|
|
INFO Server configuration: 127.0.0.1:3000, database: /tmp/db.db,
|
|
public_files: ./public, cors: true, log_level: info, tls: disabled
|
|
```
|
|
|
|
### 6. CORS Configuration
|
|
- **MUST**: Include `cors_enabled` boolean in config
|
|
- **MUST**: Conditionally apply `CorsLayer` based on config
|
|
- **MUST**: Use `CorsLayer::permissive()` when enabled
|
|
- **MUST**: Log CORS status during startup
|
|
|
|
**Code Pattern**:
|
|
```rust
|
|
if cfg.cors_enabled {
|
|
router = router.layer(CorsLayer::permissive());
|
|
tracing::debug!("CORS enabled");
|
|
} else {
|
|
tracing::debug!("CORS disabled");
|
|
}
|
|
```
|
|
|
|
### 7. Static Files (Public & Documentation)
|
|
- **MUST**: Make file path configurable via `public_files_path`
|
|
- **MUST**: Support both absolute and relative paths
|
|
- **MUST**: Check if path exists before serving
|
|
- **MUST**: Log WARNING if path doesn't exist
|
|
- **MUST**: Skip static file routes if path missing (graceful degradation)
|
|
- **MUST**: Serve both `/public` and `/docs` from same path
|
|
- **MUST NOT**: Hardcode `"public"` or `"docs"` directory names
|
|
|
|
**Code Pattern**:
|
|
```rust
|
|
let public_files_path = cfg.public_files_path_buf();
|
|
let public_files_exist = public_files_path.exists();
|
|
|
|
if public_files_exist {
|
|
tracing::info!("Static files directory: {}", public_files_path.display());
|
|
} else {
|
|
tracing::warn!(
|
|
"Static files directory does not exist: {}. Skipping endpoints.",
|
|
public_files_path.display()
|
|
);
|
|
}
|
|
|
|
if public_files_exist {
|
|
router = router
|
|
.nest_service("/public", ServeDir::new(public_files_path.clone()))
|
|
.nest_service("/docs", ServeDir::new(public_files_path));
|
|
}
|
|
```
|
|
|
|
### 8. TLS/HTTPS Support (Optional but Recommended)
|
|
- **MUST**: Include optional `[server.tls]` section
|
|
- **MUST**: Support `enabled`, `cert_path`, `key_path` fields
|
|
- **SHOULD**: Implement TLS configuration validation
|
|
- **MUST**: Log TLS status during startup
|
|
- **SHOULD**: Return clear error if TLS enabled but paths missing
|
|
|
|
**Validation Pattern**:
|
|
```rust
|
|
pub fn validate_tls(&self) -> Result<(), ConfigError> {
|
|
if let Some(tls) = &self.tls {
|
|
if tls.enabled {
|
|
if tls.cert_path.is_none() {
|
|
return Err(ConfigError::Custom(
|
|
"TLS enabled but cert_path not provided".to_string(),
|
|
));
|
|
}
|
|
if tls.key_path.is_none() {
|
|
return Err(ConfigError::Custom(
|
|
"TLS enabled but key_path not provided".to_string(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 9. Path Resolution
|
|
- **MUST**: Resolve relative paths from current working directory
|
|
- **MUST**: Support absolute paths without modification
|
|
- **MUST**: Handle missing parent directories (create if needed for database)
|
|
- **MUST**: Provide `*_path_buf()` methods that return `PathBuf`
|
|
|
|
**Code Pattern**:
|
|
```rust
|
|
pub fn database_path_buf(&self) -> PathBuf {
|
|
let path = PathBuf::from(&self.database_path);
|
|
if path.is_absolute() {
|
|
path
|
|
} else {
|
|
match std::env::current_dir() {
|
|
Ok(cwd) => cwd.join(&path),
|
|
Err(_) => path
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 10. Installation Script
|
|
- **MUST**: Provide bash script: `scripts/install-<tool>-api.sh`
|
|
- **MUST**: Ask user for config directory (default: `~/.config/<tool>-api`)
|
|
- **MUST**: Copy template config to user location
|
|
- **MUST**: Generate `run-<tool>-server.sh` wrapper script
|
|
- **MUST**: Provide clear installation instructions
|
|
- **MUST**: Check that binary exists before proceeding
|
|
|
|
**Generated Wrapper Script**:
|
|
```bash
|
|
#!/bin/bash
|
|
exec /path/to/binary --config /path/to/config.toml "$@"
|
|
```
|
|
|
|
## Implementation Checklist
|
|
|
|
### Configuration Module (`config.rs`)
|
|
- [ ] `ServerConfig` struct with all required fields
|
|
- [ ] `TlsConfig` struct (optional fields)
|
|
- [ ] `ConfigError` enum with descriptive messages
|
|
- [ ] `load()` method that reads TOML file
|
|
- [ ] Explicit `[server]` table extraction
|
|
- [ ] `socket_addr()` method
|
|
- [ ] `*_path_buf()` methods for path resolution
|
|
- [ ] `validate_tls()` method
|
|
- [ ] `log_config()` method showing all settings
|
|
- [ ] Unit tests for configuration loading
|
|
|
|
### Main Server (`main.rs`)
|
|
- [ ] CLI argument parsing with clap (`--config` flag)
|
|
- [ ] Configuration loading
|
|
- [ ] Configuration validation
|
|
- [ ] Tracing initialization from `log_level`
|
|
- [ ] Database initialization
|
|
- [ ] Router construction
|
|
- [ ] Conditional CORS layer application
|
|
- [ ] Conditional static file route registration
|
|
- [ ] TLS path existence check
|
|
- [ ] Server startup logging
|
|
|
|
### Documentation
|
|
- [ ] `README.md` with configuration guide
|
|
- [ ] `<tool>-config.template.toml` with comments
|
|
- [ ] Examples for dev/prod configurations
|
|
- [ ] Troubleshooting section
|
|
|
|
### Scripts
|
|
- [ ] `scripts/install-<tool>-api.sh` installation script
|
|
- [ ] Script handles `--config-dir` and `--binary` arguments
|
|
- [ ] Script creates wrapper script with correct paths
|
|
- [ ] Clear success/error messages
|
|
|
|
## Modular Configuration Architecture (Recommended)
|
|
|
|
For servers with multiple optional features, use a **two-tier configuration approach** to prevent config file bloat:
|
|
|
|
### Tier 1: Main Configuration
|
|
- Core server settings only (host, port, database, CORS, logging, TLS)
|
|
- Feature enablement flags (`[server.features]` section)
|
|
- Minimal and focused
|
|
|
|
### Tier 2: Feature Configurations
|
|
- Feature-specific settings in separate files
|
|
- Located in `configs/features/{feature}.toml` relative to main config
|
|
- Auto-loaded only if feature is enabled
|
|
- Reduces main config complexity
|
|
|
|
**Example Structure**:
|
|
```
|
|
project-root/
|
|
├── server-api-config.toml # Main config with feature flags
|
|
└── configs/
|
|
└── features/
|
|
├── database.toml # Loaded if database feature enabled
|
|
├── health.toml
|
|
├── metrics.toml
|
|
├── rate_limit.toml
|
|
├── auth.toml
|
|
├── cache.toml
|
|
└── multi_tenant.toml
|
|
```
|
|
|
|
**Main Config Example**:
|
|
```toml
|
|
[server]
|
|
host = "127.0.0.1"
|
|
port = 3000
|
|
database_path = "./data/database.db"
|
|
public_files_path = "./public"
|
|
cors_enabled = true
|
|
log_level = "info"
|
|
|
|
[server.features]
|
|
database.enabled = true # If true, loads configs/features/database.toml
|
|
health.enabled = true
|
|
metrics.enabled = false
|
|
rate_limit.enabled = false
|
|
auth.enabled = false
|
|
cache.enabled = false
|
|
multi_tenant.enabled = false
|
|
|
|
[server.tls]
|
|
enabled = false
|
|
# cert_path = "/path/to/cert.pem"
|
|
# key_path = "/path/to/key.pem"
|
|
```
|
|
|
|
**Implementation Requirements**:
|
|
- [ ] Add `FeaturesConfig` struct to hold feature flags
|
|
- [ ] Add `FeatureFlag { enabled: bool }` for each feature
|
|
- [ ] Implement `validate_feature_configs()` to check enabled features have config files
|
|
- [ ] Document feature-specific config file format in template files
|
|
- [ ] Log which features are enabled at startup
|
|
|
|
## Configuration Template Structure
|
|
|
|
```toml
|
|
# <Tool> API Server Configuration Template
|
|
#
|
|
# This is a template configuration file for the <tool>-server binary.
|
|
# Copy this file to your desired location and update the values below.
|
|
#
|
|
# Usage:
|
|
# <tool>-server --config /path/to/<tool>-api-config.toml
|
|
|
|
[server]
|
|
# Server host to bind to
|
|
# Examples: 127.0.0.1 (localhost only), 0.0.0.0 (all interfaces)
|
|
host = "127.0.0.1"
|
|
|
|
# Server port number
|
|
port = 3000
|
|
|
|
# Database file path
|
|
# Can be absolute (/var/lib/data.db) or relative (./data/data.db)
|
|
# Parent directory will be created automatically if needed
|
|
database_path = "./data/database.db"
|
|
|
|
# Path to directory containing static files (public folder and docs)
|
|
# Can be absolute (/var/www/public) or relative (./public)
|
|
public_files_path = "./public"
|
|
|
|
# Enable Cross-Origin Resource Sharing (CORS)
|
|
# Set to true to allow requests from different origins
|
|
cors_enabled = true
|
|
|
|
# Logging level: trace, debug, info, warn, error
|
|
log_level = "info"
|
|
|
|
# TLS/HTTPS Configuration (optional)
|
|
[server.tls]
|
|
enabled = false
|
|
# cert_path = "/path/to/cert.pem"
|
|
# key_path = "/path/to/key.pem"
|
|
```
|
|
|
|
## Error Handling Standards
|
|
|
|
### Configuration Loading Errors
|
|
```
|
|
❌ Missing config file:
|
|
Error: Custom("Failed to read config file \"/path/file.toml\": No such file or directory")
|
|
|
|
❌ Invalid TOML:
|
|
Error: Custom("Failed to parse TOML config from \"/path/file.toml\": missing field `host`")
|
|
|
|
❌ Missing [server] section:
|
|
Error: Custom("Missing [server] section in config file")
|
|
|
|
❌ Invalid TLS config:
|
|
Error: Custom("TLS enabled but cert_path is not provided")
|
|
```
|
|
|
|
### Runtime Warnings
|
|
```
|
|
⚠️ Static files directory missing:
|
|
WARN Static files directory does not exist: /path/to/public.
|
|
Skipping /public and /docs endpoints.
|
|
|
|
✓ Successful startup:
|
|
INFO Server configuration: 127.0.0.1:3000, database: ./db.db,
|
|
public_files: ./public, cors: true, log_level: info, tls: disabled
|
|
INFO Server listening on 127.0.0.1:3000
|
|
```
|
|
|
|
## Review Checklist for Existing API Servers
|
|
|
|
Use this checklist when reviewing existing API servers for compliance:
|
|
|
|
### Configuration System
|
|
- [ ] Uses `--config` CLI argument (clap)
|
|
- [ ] Loads TOML from file path
|
|
- [ ] Extracts `[server]` table explicitly
|
|
- [ ] Has no hardcoded paths
|
|
- [ ] Has no environment variable overrides
|
|
- [ ] Has no default values (all fields required)
|
|
- [ ] Clear error messages with file paths
|
|
|
|
### Configuration Fields
|
|
- [ ] `host` - String for bind address
|
|
- [ ] `port` - Integer for port number
|
|
- [ ] `database_path` - String for database file
|
|
- [ ] `public_files_path` - String for static files (NEW!)
|
|
- [ ] `cors_enabled` - Boolean for CORS
|
|
- [ ] `log_level` - String for logging level
|
|
- [ ] `[server.tls]` - Optional TLS section
|
|
|
|
### Functionality
|
|
- [ ] Log level applied to tracing_subscriber
|
|
- [ ] CORS conditionally applied based on config
|
|
- [ ] Static files conditionally served (with existence check)
|
|
- [ ] TLS validation if enabled
|
|
- [ ] Configuration logged during startup
|
|
|
|
### Documentation
|
|
- [ ] Template config file in project root
|
|
- [ ] Installation script provided
|
|
- [ ] README explains configuration
|
|
- [ ] Examples for different environments
|
|
|
|
### Code Quality
|
|
- [ ] No unsafe code (`#![forbid(unsafe_code)]`)
|
|
- [ ] No unwrap() - uses Result<T>
|
|
- [ ] Comprehensive tests
|
|
- [ ] Proper error handling
|
|
- [ ] Clear logging
|
|
|
|
## Template Locations
|
|
|
|
- **Config struct**: `src/config.rs`
|
|
- **Main server**: `src/main.rs`
|
|
- **Template file**: `<tool>-api-config.template.toml`
|
|
- **Installation script**: `scripts/install-<tool>-api.sh`
|
|
- **Documentation**: `README.md`, `API_SERVER_GUIDELINE.md`
|
|
|
|
## Working Example
|
|
|
|
**Reference Implementation**: `crates/syntaxis-api/` in syntaxis
|
|
|
|
This crate demonstrates all guidelines:
|
|
- ✅ Strict configuration in `src/config.rs`
|
|
- ✅ CLI argument parsing in `src/main.rs`
|
|
- ✅ CORS conditional application
|
|
- ✅ Static files with graceful degradation
|
|
- ✅ TLS support with validation
|
|
- ✅ Template and installation script
|
|
- ✅ Comprehensive logging
|
|
|
|
## Version History
|
|
|
|
### v1.0 (2025-11-13)
|
|
- Initial comprehensive guideline
|
|
- Based on syntaxis-api implementation
|
|
- Includes all core principles and checklist
|
|
- Production-ready pattern established
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements for future versions:
|
|
- [ ] Database migration configuration
|
|
- [ ] Health check endpoint standardization
|
|
- [ ] Metrics/monitoring endpoints
|
|
- [ ] Rate limiting configuration
|
|
- [ ] Authentication/authorization settings
|
|
- [ ] Cache configuration options
|
|
- [ ] Multi-tenant configuration
|