ferronconf
A Rust library for parsing ferron.conf configuration files — a domain-specific language for custom web server configurations.
Overview
This crate provides a reference implementation of the ferron.conf format, including:
- Lexer — Tokenizes configuration files with support for comments, strings, numbers, booleans, and interpolation
- Parser — Builds an AST from tokens with full error reporting
- AST — Type-safe representation of configuration structures
For the complete format specification, see SPECIFICATION.md.
Installation
Add to your Cargo.toml:
[]
= "0.1.0"
Quick start
use Config;
use FromStr;
let input = r#"
example.com {
root /var/www/example
tls {
provider "acme"
challenge http-01
contact "admin@example.com"
}
}
api.example.com:8080 {
proxy http://localhost:3000
}
"#;
let config = from_str?;
Configuration format
The ferron.conf format supports five statement types:
1. Directives
Key-value pairs with optional nested blocks:
server_name example.com
max_connections 1000
enabled true
cert "{{env.TLS_CERT}}"
2. Host blocks
Configuration scoped to specific hosts (top-level only):
# Simple hostname
example.com {
root /var/www/example
}
# Wildcard subdomains
*.example.com {
tls {
provider "acme"
challenge http-01
contact "admin@example.com"
}
}
# With protocol and port
http api.example.com:8080 {
proxy http://localhost:3000
}
# IPv6
[2001:db8::1]:8080 {
root /ipv6-only
}
# Multiple hosts (comma-separated)
example.com, www.example.com {
root /var/www/shared
}
3. Global blocks
Global configuration applied to all hosts (top-level only):
{
runtime {
io_uring true
}
tcp {
listen "::"
send_buf 65536
}
default_http_port 8080
default_https_port 8443
}
4. Snippet blocks
Reusable configuration fragments:
snippet tls_defaults {
tls {
provider "acme"
challenge http-01
contact "admin@example.com"
}
http {
protocols h1 h2
}
}
5. Match blocks
Conditional logic based on request attributes:
match api_request {
request.uri.path ~ "/api"
request.method in "GET,POST"
}
match curl_client {
request.header.user_agent ~ "curl"
}
Supported operators: ==, !=, ~ (regex), !~ (negated regex), in
Comments
# This is a comment
server_name example.com # inline comment
Note: Comments are not allowed inside match blocks.
Data types
| Type | Example | Description |
|---|---|---|
| String (quoted) | "hello world" |
Supports escape sequences (\n, \t, \\, \") |
| String (bare) | example.com |
Unquoted alphanumeric with _-.:/+* |
| Number | 80, 3.14, -10 |
Integer or decimal |
| Boolean | true, false |
Case-sensitive literals |
| Interpolation | {{env.TLS_CERT}} |
Variable reference with dotted path |
Library API
Parsing
use Config;
use FromStr;
let config = from_str?;
AST navigation
use Statement;
// Find all directives with a given name
let roots = config.find_directives;
// Find all host blocks
let hosts = config.find_host_blocks;
// Find all match blocks
let matchers = config.find_match_blocks;
// Navigate statements
for stmt in &config.statements
Block helpers
// Find directive inside a block
if let Some = directive.block
Value extraction
use Value;
// From directive arguments
if let Some = directive.get_string_arg
if let Some = directive.get_integer_arg
if let Some = directive.get_boolean_arg
// From Value directly
match &value
Host pattern matching
use HostLabels;
for host_block in config.find_host_blocks
Match block inspection
for match_block in config.find_match_blocks
Error handling
Parse errors include line and column information:
use ;
use FromStr;
match from_str
Syntax highlighting
A TextMate grammar is provided in ferron.tmLanguage.json for editor syntax highlighting. Copy it to your editor's grammar directory or use it with tools like bat or syntect.
Example configuration
# Global defaults
{
runtime {
io_uring true
}
tcp {
listen "::"
}
default_http_port 80
default_https_port 443
admin {
listen 127.0.0.1:8081
health true
status true
}
}
# Reusable TLS configuration
snippet tls_acme {
tls {
provider "acme"
challenge http-01
contact "admin@example.com"
}
}
# Reusable HTTP settings
snippet common_http {
http {
protocols h1 h2
}
}
# Main site with static file serving
example.com:443 {
use tls_acme
use common_http
root /var/www/example
index index.html index.htm
directory_listing
compressed
log "access" {
format "combined"
}
}
# Wildcard subdomains with ACME TLS
*.example.com {
tls {
provider "acme"
challenge dns-01
contact "admin@example.com"
dns "cloudflare" {
api_key "EXAMPLE_API_KEY"
}
}
root /var/www/multi-tenant
}
# API reverse proxy
api.example.com {
proxy http://localhost:3000 http://localhost:3001 {
lb_algorithm two_random
keepalive true
http2 true
request_header +X-Real-IP "{{remote_address}}"
request_header X-Forwarded-Proto "{{scheme}}"
}
rate_limit {
rate 100
burst 50
key remote_address
}
cors {
origins "https://app.example.com"
methods GET POST PUT DELETE
headers "Content-Type" "Authorization"
credentials true
}
}
# Conditional routing
match api_request {
request.uri.path ~ "/api"
request.method in "GET,POST"
}
match curl_client {
request.header.user_agent ~ "curl"
}
# Location-based configuration
example.com {
root /var/www/example
location /static {
file_cache_control "public, max-age=31536000"
}
location /admin {
if curl_client {
status 403 {
body "Forbidden"
}
}
}
}
# Protocol-specific configuration
http * {
header X-Powered-By "Ferron"
}
# TCP service
tcp *:5432 {
proxy localhost:5432
}
Limitations
- Bare strings after identifiers may be ambiguous with host blocks at the top level
- Comments are not supported inside
matchblocks - IPv4 octets are validated to be in range 0–255
License
MIT