syntaxis/docs/core/api-server-guideline.md

431 lines
14 KiB
Markdown
Raw Permalink Normal View History

# 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