syntaxis/docs/skill_api-server-development.md

261 lines
6.6 KiB
Markdown
Raw Permalink Normal View History

# API Server Development Skill
**Purpose**: Provide guidance for creating and reviewing production-grade REST API servers that follow Project Agnostic Practice (PAP) principles.
**When to Use**:
- Creating a new REST API server
- Reviewing existing API servers for compliance
- Implementing configuration systems
- Adding CORS/TLS/static files support
## The Config-Driven API Server Pattern
This skill is based on the **syntaxis-api** implementation, which establishes the standard pattern for all API servers in this project.
### What Makes This Pattern Special
**Portable**: No hardcoded paths - works anywhere
**Explicit**: All configuration required, no defaults
**Safe**: Clear error messages, validation
**Flexible**: Config-driven CORS, logging, static files, TLS
**Graceful**: Missing optional features don't break server
## Quick Start for New API Servers
### 1. Configuration Module
Create `src/config.rs`:
```rust
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("TOML parsing error: {0}")]
TomlError(#[from] toml::de::Error),
#[error("Configuration error: {0}")]
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub database_path: String,
pub public_files_path: String,
pub cors_enabled: bool,
pub log_level: String,
#[serde(default)]
pub tls: Option<TlsConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
pub enabled: bool,
pub cert_path: Option<String>,
pub key_path: Option<String>,
}
impl ServerConfig {
pub fn load<P: AsRef<std::path::Path>>(config_path: P) -> Result<Self, ConfigError> {
// Extract [server] section explicitly
// Fail if any required field missing
// Return clear error messages
}
pub fn socket_addr(&self) -> Result<SocketAddr, ConfigError> { ... }
pub fn database_path_buf(&self) -> PathBuf { ... }
pub fn public_files_path_buf(&self) -> PathBuf { ... }
pub fn validate_tls(&self) -> Result<(), ConfigError> { ... }
pub fn log_config(&self) { ... }
}
```
### 2. CLI Argument Parsing
Add to `Cargo.toml`:
```toml
clap = { version = "4.4", features = ["derive"] }
```
In `src/main.rs`:
```rust
use clap::Parser;
#[derive(Parser)]
#[command(name = "tool-server")]
struct Args {
/// Path to configuration file (TOML format)
#[arg(short, long)]
config: PathBuf,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let cfg = ServerConfig::load(&args.config)?;
cfg.validate_tls()?;
// ... rest of initialization
}
```
### 3. Tracing from Config
```rust
// Initialize tracing with configured log level
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.or_else(|_| tracing_subscriber::EnvFilter::try_new(&cfg.log_level))?;
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.init();
cfg.log_config();
```
### 4. Conditional CORS
```rust
if cfg.cors_enabled {
router = router.layer(CorsLayer::permissive());
tracing::debug!("CORS enabled");
}
```
### 5. Config-Driven Static Files
```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 /public and /docs 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));
}
```
### 6. Template Configuration File
Create `<tool>-api-config.template.toml`:
```toml
# <Tool> API Server Configuration Template
#
# Usage: <tool>-server --config /path/to/config.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.tls]
enabled = false
# cert_path = "/path/to/cert.pem"
# key_path = "/path/to/key.pem"
```
### 7. Installation Script
Create `scripts/install-<tool>-api.sh`:
```bash
#!/bin/bash
set -e
CONFIG_DIR="$HOME/.config/<tool>-api"
BINARY="${PROJECT_ROOT}/target/release/<tool>-server"
mkdir -p "$CONFIG_DIR"
cp <tool>-api-config.template.toml "$CONFIG_DIR/<tool>-api-config.toml"
cat > "$CONFIG_DIR/run-<tool>-server.sh" << 'EOF'
#!/bin/bash
exec BINARY_PATH --config CONFIG_FILE_PATH "$@"
EOF
sed -i '' "s|BINARY_PATH|$BINARY|g" "$CONFIG_DIR/run-<tool>-server.sh"
sed -i '' "s|CONFIG_FILE_PATH|$CONFIG_DIR/<tool>-api-config.toml|g" "$CONFIG_DIR/run-<tool>-server.sh"
chmod +x "$CONFIG_DIR/run-<tool>-server.sh"
echo "✅ Installation complete!"
echo "Run: $CONFIG_DIR/run-<tool>-server.sh"
```
## Review Checklist
When reviewing an existing API server:
### Configuration System
- [ ] Has `--config` CLI argument (clap)
- [ ] Loads TOML from file
- [ ] Extracts `[server]` table explicitly
- [ ] No hardcoded paths
- [ ] No env var overrides
- [ ] All fields required (no defaults)
- [ ] Clear error messages
### Configuration Fields
- [ ] `host` (string)
- [ ] `port` (u16)
- [ ] `database_path` (string)
- [ ] `public_files_path` (string)
- [ ] `cors_enabled` (bool)
- [ ] `log_level` (string)
- [ ] `[server.tls]` (optional)
### Features
- [ ] Log level applied to tracing
- [ ] CORS conditional based on config
- [ ] Static files conditional (with existence check)
- [ ] TLS validation if enabled
- [ ] Configuration logged at startup
### Files
- [ ] `<tool>-api-config.template.toml` in project root
- [ ] `scripts/install-<tool>-api.sh`
- [ ] `README.md` with configuration examples
- [ ] Tests for config loading
## Reference Implementation
**Location**: `crates/syntaxis-api/` in syntaxis
Review this crate for a complete, production-ready example:
- Configuration: `src/config.rs`
- Server setup: `src/main.rs`
- Template: `syntaxis-api-config.template.toml`
- Installation: `scripts/install-syntaxis-api.sh`
## Full Documentation
See **API_SERVER_GUIDELINE.md** in project root for:
- Complete principles
- Detailed implementation checklist
- Error handling standards
- All required patterns
## Key Takeaways
1. **Configuration First**: `--config` is not optional
2. **Be Explicit**: No defaults, no magic
3. **Be Kind**: Clear error messages with file paths
4. **Be Flexible**: Conditional features (CORS, TLS, files)
5. **Be Portable**: No hardcoded paths anywhere
6. **Be Documented**: Template + comments + installation guide