diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go index a95e0f30d..b3282a2ab 100644 --- a/caddyconfig/caddyfile/parse.go +++ b/caddyconfig/caddyfile/parse.go @@ -173,8 +173,15 @@ func (p *parser) begin() error { if err != nil { return err } - tokens = append([]Token{nameToken}, tokens...) - p.block.Segments = []Segment{tokens} + + // expand any import directives inside the named route block + expandedTokens, err := p.expandImportsInBlock(tokens) + if err != nil { + return err + } + + expandedTokens = append([]Token{nameToken}, expandedTokens...) + p.block.Segments = []Segment{expandedTokens} return nil } @@ -583,6 +590,41 @@ func (p *parser) doImport(nesting int) error { return nil } +// expandImportsInBlock takes a slice of tokens (typically the contents of a +// named route block including its outer curly braces) and expands any import +// directives found at the beginning of a line. The expansion is done by +// creating a temporary parser that shares the same snippets and import graph, +// then looping through the tokens and calling doImport whenever an import +// directive is encountered. All other tokens are left untouched. +func (p *parser) expandImportsInBlock(tokens []Token) ([]Token, error) { + // Create a temporary parser that operates on the provided tokens. + // The Dispenser is initialized with the token slice; snippets and the + // import graph are shared so that imports and cycle detection work + // consistently across the whole Caddyfile. + tempParser := &parser{ + Dispenser: NewDispenser(tokens), + definedSnippets: p.definedSnippets, + importGraph: p.importGraph, + } + + // Loop through the tokens. We only care about import directives that + // appear at the start of a line (same logic as directives()). + for tempParser.Next() { + if tempParser.Val() == "import" && tempParser.isNewLine() { + if err := tempParser.doImport(1); err != nil { + return nil, err + } + // Roll back the cursor so the next iteration sees the first + // token of the imported content (or the next token after it). + tempParser.cursor-- + } + } + + // The temporary parser's token slice has been modified in place by + // doImport, so we return it directly. + return tempParser.tokens, nil +} + // doSingleImport lexes the individual file at importFile and returns // its tokens or an error, if any. func (p *parser) doSingleImport(importFile string) ([]Token, error) { diff --git a/caddytest/integration/caddyfile_adapt/import_inside_named_route.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_inside_named_route.caddyfiletest new file mode 100644 index 000000000..965c97d10 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_inside_named_route.caddyfiletest @@ -0,0 +1,62 @@ +(common) { + respond "from snippet" +} + +&(foo) { + import common +} + +localhost:9080 { + invoke foo +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "foo" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "named_routes": { + "foo": { + "handle": [ + { + "body": "from snippet", + "handler": "static_response" + } + ] + } + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_inside_named_route_args.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_inside_named_route_args.caddyfiletest new file mode 100644 index 000000000..bcd28d594 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_inside_named_route_args.caddyfiletest @@ -0,0 +1,62 @@ +(common) { + respond "{args.0}" +} + +&(foo) { + import common "hello" +} + +localhost:9080 { + invoke foo +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "foo" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "named_routes": { + "foo": { + "handle": [ + { + "body": "hello", + "handler": "static_response" + } + ] + } + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_inside_named_route_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_inside_named_route_block.caddyfiletest new file mode 100644 index 000000000..5244c6553 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_inside_named_route_block.caddyfiletest @@ -0,0 +1,89 @@ +(common) { + route /api/* { + respond "api" + } +} + +&(foo) { + import common +} + +localhost:9080 { + invoke foo +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "foo" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "named_routes": { + "foo": { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "api", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/api/*" + ] + } + ] + } + ] + } + ] + } + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_inside_named_route_nested.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_inside_named_route_nested.caddyfiletest new file mode 100644 index 000000000..ff8683381 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_inside_named_route_nested.caddyfiletest @@ -0,0 +1,66 @@ +(inner) { + respond "inner" +} + +(outer) { + import inner +} + +&(foo) { + import outer +} + +localhost:9080 { + invoke foo +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "foo" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "named_routes": { + "foo": { + "handle": [ + { + "body": "inner", + "handler": "static_response" + } + ] + } + } + } + } + } + } +}