Skip to content

Commit 4082ece

Browse files
authored
Merge pull request #97 from dengsh12/NLB5028
Add `DirectiveSources` field to `ParseOptions`
2 parents b1f21d9 + f91265b commit 4082ece

File tree

3 files changed

+154
-5
lines changed

3 files changed

+154
-5
lines changed

analyze.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,32 @@ func enterBlockCtx(stmt *Directive, ctx blockCtx) blockCtx {
9494

9595
//nolint:gocyclo,funlen,gocognit
9696
func analyze(fname string, stmt *Directive, term string, ctx blockCtx, options *ParseOptions) error {
97-
masks, knownDirective := directives[stmt.Directive]
97+
var masks []uint
98+
knownDirective := false
99+
98100
currCtx, knownContext := contexts[ctx.key()]
101+
directiveName := stmt.Directive
102+
103+
// Find all bitmasks from the sources invoker provides.
104+
for _, matchFn := range options.DirectiveSources {
105+
if masksInFn, found := matchFn(directiveName); found {
106+
masks = append(masks, masksInFn...)
107+
knownDirective = true
108+
}
109+
}
99110

100-
if !knownDirective {
101-
for _, matchFn := range options.MatchFuncs {
102-
if masks, knownDirective = matchFn(stmt.Directive); knownDirective {
103-
break
111+
// If DirectiveSources was not provided, try to find the directive in legacy way.
112+
// We want to use DirectiveSources to indicate the OSS/N+ version, and dynamic
113+
// modules we want to include for validation. However, invokers used MatchFuncs
114+
// and a directive map before. This is a transition plan. After MatchFuncs is deleted
115+
// from ParseOptions, this if block should be deleted.
116+
if len(options.DirectiveSources) == 0 {
117+
masks, knownDirective = directives[directiveName]
118+
if !knownDirective {
119+
for _, matchFn := range options.MatchFuncs {
120+
if masks, knownDirective = matchFn(stmt.Directive); knownDirective {
121+
break
122+
}
104123
}
105124
}
106125
}

analyze_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,3 +2427,125 @@ func TestAnalyze_headers_more(t *testing.T) {
24272427
})
24282428
}
24292429
}
2430+
2431+
//nolint:funlen
2432+
func TestAnalyze_directiveSources(t *testing.T) {
2433+
t.Parallel()
2434+
// two self defined maps and matchFn to ensure it is a separate test
2435+
testDirectiveMap1 := map[string][]uint{
2436+
"common_dir": {ngxAnyConf | ngxConfTake1},
2437+
"test_dir1": {ngxAnyConf | ngxConfTake1},
2438+
}
2439+
testSource1 := func(directive string) ([]uint, bool) {
2440+
masks, matched := testDirectiveMap1[directive]
2441+
return masks, matched
2442+
}
2443+
2444+
testDirectiveMap2 := map[string][]uint{
2445+
"common_dir": {ngxAnyConf | ngxConfTake2},
2446+
"test_dir2": {ngxAnyConf | ngxConfTake2},
2447+
}
2448+
testSource2 := func(directive string) ([]uint, bool) {
2449+
masks, matched := testDirectiveMap2[directive]
2450+
return masks, matched
2451+
}
2452+
2453+
testcases := map[string]struct {
2454+
stmt *Directive
2455+
ctx blockCtx
2456+
wantErr bool
2457+
}{
2458+
// The directive only found in source1 and satisfies the bitmask in it
2459+
"DirectiveFoundOnlyInSource1_pass": {
2460+
&Directive{
2461+
Directive: "test_dir1",
2462+
Args: []string{"arg1"},
2463+
Line: 5,
2464+
},
2465+
blockCtx{"http", "upstream"},
2466+
false,
2467+
},
2468+
// The directive only found in source2 and satisfies the bitmask in it
2469+
"DirectiveFoundOnlyInSource2_pass": {
2470+
&Directive{
2471+
Directive: "test_dir2",
2472+
Args: []string{"arg1", "arg2"},
2473+
Line: 5,
2474+
},
2475+
blockCtx{"http", "upstream"},
2476+
false,
2477+
},
2478+
// The directive only found in source2 but not satisfies the bitmask in it
2479+
"DirectiveFoundOnlyInsource2_fail": {
2480+
&Directive{
2481+
Directive: "test_dir2",
2482+
Args: []string{"arg1"},
2483+
Line: 5,
2484+
},
2485+
blockCtx{"http", "upstream"},
2486+
true,
2487+
},
2488+
// The directive found in both sources,
2489+
// but only satisfies bitmasks in source1 it should still pass validation
2490+
"DirectiveFoundInBothSources_pass_case1": {
2491+
&Directive{
2492+
Directive: "common_dir",
2493+
Args: []string{"arg1"},
2494+
Line: 5,
2495+
},
2496+
blockCtx{"http", "upstream"},
2497+
false,
2498+
},
2499+
// The directive found in both Sources,
2500+
// but only satisfies bitmasks in source2 it should still pass validation
2501+
"DirectiveFoundInBothSources_pass_case2": {
2502+
&Directive{
2503+
Directive: "common_dir",
2504+
Args: []string{"arg1", "arg2"},
2505+
Line: 5,
2506+
},
2507+
blockCtx{"http", "upstream"},
2508+
false,
2509+
},
2510+
// The directive found in both sources,
2511+
// but doesn't satisfy bitmask in any of them
2512+
"DirectiveFoundInBothSources_fail": {
2513+
&Directive{
2514+
Directive: "common_dir",
2515+
Args: []string{"arg1", "arg2", "arg3"},
2516+
Line: 5,
2517+
},
2518+
blockCtx{"http", "upstream"},
2519+
true,
2520+
},
2521+
// The directive not found in any source
2522+
"DirectiveNotFoundInAnySource_fail": {
2523+
&Directive{
2524+
Directive: "no_exist",
2525+
Args: []string{},
2526+
Line: 5,
2527+
},
2528+
blockCtx{"http", "location"},
2529+
true,
2530+
},
2531+
}
2532+
2533+
for name, tc := range testcases {
2534+
tc := tc
2535+
t.Run(name, func(t *testing.T) {
2536+
t.Parallel()
2537+
err := analyze("nginx.conf", tc.stmt, ";", tc.ctx, &ParseOptions{
2538+
DirectiveSources: []MatchFunc{testSource1, testSource2},
2539+
ErrorOnUnknownDirectives: true,
2540+
})
2541+
2542+
if !tc.wantErr && err != nil {
2543+
t.Fatal(err)
2544+
}
2545+
2546+
if tc.wantErr && err == nil {
2547+
t.Fatal("expected error, got nil")
2548+
}
2549+
})
2550+
}
2551+
}

parse.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,15 @@ type ParseOptions struct {
108108
// encountered by the parser to determine the valid contexts and argument count of the
109109
// directive. Set this option to enable parsing of directives belonging to non-core or
110110
// dynamic NGINX modules that follow the usual grammar rules of an NGINX configuration.
111+
// If DirectiveSources was provided, this will be ignored.
111112
MatchFuncs []MatchFunc
113+
114+
// An array of MatchFunc, which is used to indicate the OSS/N+ version, and
115+
// dynamic modules you want to include for validation. If a directive matches
116+
// any of them, and satisfies the corresponding bitmask, it should pass the validation.
117+
// If we provide this, MatchFuncs will be ignored
118+
DirectiveSources []MatchFunc
119+
112120
LexOptions LexOptions
113121
}
114122

0 commit comments

Comments
 (0)