refactor(rules): introduce block DSL, phase-based execution, and flow validation

- add block syntax parser/scanner with nested @blocks and elif/else support
- restructure rule execution into explicit pre/post phases with phase flags
- classify commands by phase and termination behavior
- enforce flow semantics (default rule handling, dead-rule detection)
- expand HTTP flow coverage with block + YAML parity tests and benches
- refresh rules README/spec and update playground/docs integration
This commit is contained in:
yusing
2026-02-23 22:24:15 +08:00
parent 0850ea3918
commit faecbab2cb
34 changed files with 4691 additions and 1057 deletions

View File

@@ -19,28 +19,71 @@ func TestRulesValidate(t *testing.T) {
{
name: "no default rule",
rules: `
- name: rule1
on: header Host example.com
do: pass
`,
header Host example.com {
pass
}`,
},
{
name: "multiple default rules",
rules: `
- name: default
do: pass
- name: rule1
on: default
do: pass
`,
default {
pass
}
default {
pass
}`,
want: ErrMultipleDefaultRules,
},
{
name: "multiple responses on same condition",
rules: `
header Host example.com {
error 404 "not found"
}
header Host example.com {
error 403 "forbidden"
}
`,
want: ErrDeadRule,
},
{
name: "same condition different formatting error then proxy",
rules: `
header Host example.com & method GET {
error 404 "not found"
}
method GET
header Host example.com {
proxy http://127.0.0.1:8080
}
`,
want: ErrDeadRule,
},
{
name: "same condition with non terminating first rule",
rules: `
header Host example.com {
set resp_header X-Test first
}
header Host example.com {
error 403 "forbidden"
}
`,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rules Rules
convertible, err := serialization.ConvertString(strings.TrimSpace(tt.rules), reflect.ValueOf(&rules))
require.True(t, convertible)
require.NoError(t, err)
err = rules.Validate()
if tt.want == nil {
assert.NoError(t, err)
@@ -50,3 +93,38 @@ func TestRulesValidate(t *testing.T) {
})
}
}
func TestHasTopLevelLBrace(t *testing.T) {
tests := []struct {
name string
in string
want bool
}{
{
name: "escaped quote inside double quoted string",
in: `"test\"more{"`,
want: false,
},
{
name: "escaped quote inside single quoted string",
in: "'test\\'more{'",
want: false,
},
{
name: "top-level brace outside quoted string",
in: `"test\"more" {`,
want: true,
},
{
name: "backtick keeps existing behavior",
in: "`test\\`more{`",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, hasTopLevelLBrace(tt.in))
})
}
}