Config Include Resolver (Go)
Topics: graphs, DFS, cycle detection, gateway configuration
Problem
Gateway/proxy configs (nginx, Envoy, …) are split across files that include one another. Booting
the gateway means flattening the include graph into one ordered directive list — while refusing
configs that loop forever or nest pathologically deep.
const IncludePrefix = "include "
var ErrMissingFile, ErrMaxDepth error
type CycleError struct { Chain []string }
func Resolve(files map[string][]string, root string, maxDepth int) ([]string, error)
files maps a filename to its lines. A line include <name> pulls in that file's directives in
place; every other line is a literal directive. Resolution starts at root and is depth-first.
- Expand includes in order, in place — a file's directives appear exactly where its
include
line sat.
- The same file may be included on different branches and expands each time — that is not a
cycle. A cycle is revisiting a file already on the current path; return a
*CycleError whose
Chain is the path that closed the loop (e.g. ["a","b","a"]).
root is depth 0; each include is one level deeper. Visiting a file at depth > maxDepth returns
ErrMaxDepth.
- A missing
root, or an include of an absent file, returns ErrMissingFile.
main:[listen 80, include common, gzip on], common:[timeout 30, include security], security:[tls on]
→ [listen 80, timeout 30, tls on, gzip on]
main:[include a, include b], a:[include shared, x], b:[include shared, y], shared:[s]
→ [s, x, s, y] # shared expands on both branches — not a cycle
a:[include b], b:[include a] → *CycleError{Chain: [a, b, a]}
A DFS that tracks the set of files on the current path (not a global visited set) makes the
cycle-vs-diamond distinction fall out; carry the recursion depth to enforce maxDepth.
Run
go test -v ./challenges/config-includes/go/
Sign in to submit your solution.