How config adapters transform various configuration formats into Caddy JSON
Config adapters are modules that convert non-JSON configuration formats into Caddy’s native JSON structure. This allows you to write configurations in more user-friendly formats like Caddyfile while benefiting from Caddy’s JSON-based architecture.
The adapter parses the input format into an internal representation.For Caddyfile, this involves:
Lexical analysis (tokenization)
Syntax parsing (server blocks, directives)
Placeholder replacement
Import resolution
2
Validation
The adapter validates the parsed configuration:
configadapters.go:30-36
type Warning struct { File string `json:"file,omitempty"` Line int `json:"line,omitempty"` Directive string `json:"directive,omitempty"` Message string `json:"message,omitempty"`}
Warnings are collected but don’t stop the adaptation process.
3
Transformation
The adapter transforms the configuration into Caddy’s JSON structure.Helper functions simplify JSON generation:
configadapters.go:46-60
// JSON encodes val as JSON, returning it as a json.RawMessage.// Any marshaling errors are converted to warnings.func JSON(val any, warnings *[]Warning) json.RawMessage { b, err := json.Marshal(val) if err != nil { if warnings != nil { *warnings = append(*warnings, Warning{Message: err.Error()}) } return nil } return b}
4
Module Object Creation
For module values, adapters use special helpers:
configadapters.go:62-106
// JSONModuleObject marshals val into a JSON object with an added// key named fieldName with the value fieldVal.func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage { // Encode to JSON object first enc, err := json.Marshal(val) if err != nil { if warnings != nil { *warnings = append(*warnings, Warning{Message: err.Error()}) } return nil } // Decode the object var tmp map[string]any err = json.Unmarshal(enc, &tmp) if err != nil { // ... error handling ... return nil } // Add the module's field with its appointed value tmp[fieldName] = fieldVal // Re-marshal as JSON result, err := json.Marshal(tmp) // ... return result ...}
This allows the adapter to specify module names inline with configuration.
Caddyfile configurations are organized into server blocks:
example.com { root * /var/www/html file_server}
The adapter processes these through several phases:
Phase 1: Parsing
Server blocks are parsed into an internal structure:
httptype.go:66-80
originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks))for _, sblock := range inputServerBlocks { for j, k := range sblock.Keys { if j == 0 && strings.HasPrefix(k.Text, "@") { return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block", k.File, k.Line) } if _, ok := registeredDirectives[k.Text]; ok { return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive", k.File, k.Line, k.Text) } } originalServerBlocks = append(originalServerBlocks, serverBlock{ block: sblock, pile: make(map[string][]ConfigValue), })}
Phase 2: Directive Processing
Each directive is processed by its registered handler:
httptype.go:120-159
for _, segment := range sb.block.Segments { dir := segment.Directive() if strings.HasPrefix(dir, matcherPrefix) { // matcher definitions were pre-processed continue } dirFunc, ok := registeredDirectives[dir] if !ok { tkn := segment[0] message := "%s:%d: unrecognized directive: %s" if !sb.block.HasBraces { message += "\nDid you mean to define a second site? If so, you must use curly braces around each site to separate their configurations." } return nil, warnings, fmt.Errorf(message, tkn.File, tkn.Line, dir) } h := Helper{ Dispenser: caddyfile.NewDispenser(segment), options: options, warnings: &warnings, matcherDefs: matcherDefs, parentBlock: sb.block, groupCounter: gc, State: state, } results, err := dirFunc(h) if err != nil { return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err) } dir = normalizeDirectiveName(dir) for _, result := range results { result.directive = dir sb.pile[result.Class] = append(sb.pile[result.Class], result) }}
Phase 3: Server Construction
Server blocks are consolidated and HTTP servers are created: