nginx Config Parser

bext includes a complete nginx configuration parser written in Rust. It tokenizes, parses, and builds an abstract syntax tree (AST) from any valid nginx.conf.

Grammar

The nginx config format is a simple recursive structure:

config    ::= directive*
directive ::= word+ (';' | '{' directive* '}')

A directive is one or more words followed by either a semicolon (simple directive) or a brace-delimited block containing child directives.

Lexer

The hand-written tokenizer produces five token types:

Token Description
Word Unquoted or quoted string value
OpenBrace {
CloseBrace }
Semicolon ;
Eof End of input

Quoting Rules

- Double quotes — escape sequences supported: \", \\

- Single quotes — literal content, no escaping

- Unquoted — delimited by whitespace, braces, or semicolons

- Comments# to end of line, with line number tracking

# Both of these are equivalent:
server_name "my-site.example.com";
server_name my-site.example.com;

# Quotes needed for special characters:
rewrite "^/old/(.*)$" "/new/$1" permanent;

Include Expansion

The parser resolves include directives inline during parsing:

http {
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Behavior:

- Relative paths are resolved against the including file's directory

- Glob patterns (*.conf, sites-enabled/*) are expanded

- Max depth — 16 levels of nesting to prevent circular includes

- No-match includes — non-fatal; logged as a warning

- Missing files — return an IoError unless the include uses a glob pattern

AST Structure

The parser produces a tree of Directive nodes:

pub struct Directive {
    pub name: String,           // e.g., "server", "listen", "proxy_pass"
    pub args: Vec,      // e.g., ["443", "ssl", "http2"]
    pub children: Vec, // block contents (empty for simple directives)
    pub file: String,           // source file path
    pub line: usize,            // line number in source
    pub column: usize,          // column number
}

The top-level NginxConfig wrapper provides convenience methods:

config.http_block()      // -> Option<&Directive>  — the http {} block
config.server_blocks()   // -> Vec<&Directive>     — all server {} blocks
config.upstream_blocks() // -> Vec<&Directive>     — all upstream {} blocks
config.get_directive("worker_processes") // -> Option<&Directive>

Error Handling

The parser reports six error types with file and line information:

Error Description
UnexpectedToken Syntax error — unexpected token in stream
UnterminatedString Quote opened but never closed
EmptyDirective Block or simple directive with no name
IncludeDepthExceeded More than 16 levels of include nesting
MissingIncludePath include with no path argument
IoError File read failure

Usage

use bext_nginx_compat::{parse_and_convert, parse_and_convert_string};

// Parse from a file path (resolves includes)
let config = parse_and_convert("/etc/nginx/nginx.conf")?;

// Parse from a string (no include resolution)
let config = parse_and_convert_string(raw_config)?;

// Auto-detect nginx installation and parse
let config = bext_nginx_compat::detect_and_convert()?;

Detection

bext automatically searches for nginx configs at:

1. /etc/nginx/nginx.conf (Linux default) 2. /usr/local/etc/nginx/nginx.conf (FreeBSD, Homebrew Intel) 3. /opt/homebrew/etc/nginx/nginx.conf (Homebrew Apple Silicon) 4. $NGINX_CONF environment variable (custom path)

It also detects whether nginx is currently running by checking PID files at /run/nginx.pid and /var/run/nginx.pid.