syntaxis/docs/core/api-server-guideline.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
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)
2025-12-26 18:36:23 +00:00

14 KiB

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:

[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:

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:

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));
}
  • 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:

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:

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:

#!/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

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:

[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

# <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
  • 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