Skip to content

Commit 64ffc78

Browse files
committed
feat: add CEL XValidation for ApisixRouteHTTPMatchExprSubject
Add +kubebuilder:validation:XValidation rule requiring name to be non-empty when scope is not Path. Regenerate CRD YAML and update CRD reference docs accordingly. All unit and CEL tests pass.
1 parent 930df17 commit 64ffc78

4 files changed

Lines changed: 65 additions & 2 deletions

File tree

api/v2/apisixroute_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ type ApisixRouteAuthenticationLDAPAuth struct {
412412
}
413413

414414
// ApisixRouteHTTPMatchExprSubject describes the subject of a route matching expression.
415+
// +kubebuilder:validation:XValidation:rule="self.scope == 'Path' || self.name != ''",message="name is required when scope is not Path"
415416
type ApisixRouteHTTPMatchExprSubject struct {
416417
// Scope specifies the subject scope.
417418
// Supported values: `Header`, `Query`, `Path`, `Cookie`, `Variable`, `Body`.

api/v2/apisixroute_types_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,73 @@
1818
package v2
1919

2020
import (
21+
"os"
2122
"testing"
2223

24+
"github.com/google/cel-go/cel"
2325
"github.com/stretchr/testify/assert"
2426
"github.com/stretchr/testify/require"
27+
"sigs.k8s.io/yaml"
2528
)
2629

2730
func strPtr(s string) *string { return &s }
2831

32+
// celSubjectRule is the CEL expression used in the +kubebuilder:validation:XValidation
33+
// marker on ApisixRouteHTTPMatchExprSubject.
34+
const celSubjectRule = `self.scope == 'Path' || self.name != ''`
35+
36+
func evalCELSubjectRule(t *testing.T, scope, name string) bool {
37+
t.Helper()
38+
env, err := cel.NewEnv(
39+
cel.Variable("self", cel.MapType(cel.StringType, cel.StringType)),
40+
)
41+
require.NoError(t, err)
42+
ast, issues := env.Compile(celSubjectRule)
43+
require.NoError(t, issues.Err())
44+
prg, err := env.Program(ast)
45+
require.NoError(t, err)
46+
out, _, err := prg.Eval(map[string]any{
47+
"self": map[string]any{"scope": scope, "name": name},
48+
})
49+
require.NoError(t, err)
50+
return out.Value().(bool)
51+
}
52+
53+
// TestCEL_SubjectRule_Logic verifies the CEL expression used in the XValidation marker.
54+
func TestCEL_SubjectRule_Logic(t *testing.T) {
55+
// Non-Path scopes with a non-empty name must pass.
56+
for _, scope := range []string{ScopeHeader, ScopeQuery, ScopeCookie, ScopeVariable, ScopeBody} {
57+
assert.True(t, evalCELSubjectRule(t, scope, "field"), "scope=%s with name should pass", scope)
58+
}
59+
// Path scope with empty name must pass (name is ignored for Path).
60+
assert.True(t, evalCELSubjectRule(t, ScopePath, ""), "Path with empty name should pass")
61+
// Non-Path scopes with empty name must fail.
62+
for _, scope := range []string{ScopeHeader, ScopeQuery, ScopeCookie, ScopeVariable, ScopeBody} {
63+
assert.False(t, evalCELSubjectRule(t, scope, ""), "scope=%s with empty name should fail", scope)
64+
}
65+
}
66+
67+
// TestCEL_SubjectRule_InCRD verifies the generated CRD YAML contains the XValidation rule
68+
// with correct (ASCII) quote characters and not typographic quotes.
69+
func TestCEL_SubjectRule_InCRD(t *testing.T) {
70+
const crdPath = "../../config/crd/bases/apisix.apache.org_apisixroutes.yaml"
71+
data, err := os.ReadFile(crdPath)
72+
require.NoError(t, err, "CRD file should exist; run 'make manifests' if missing")
73+
74+
var crd map[string]any
75+
require.NoError(t, yaml.Unmarshal(data, &crd))
76+
77+
raw := string(data)
78+
// The CEL rule must appear with ASCII single-quotes only.
79+
assert.Contains(t, raw, `self.scope == 'Path' || self.name != ''`,
80+
"CRD should contain the XValidation rule with ASCII quotes")
81+
// Ensure no typographic/smart quotes crept in.
82+
assert.NotContains(t, raw, "\u2018", "CRD must not contain left single quotation mark \u2018")
83+
assert.NotContains(t, raw, "\u2019", "CRD must not contain right single quotation mark \u2019")
84+
assert.NotContains(t, raw, "\u201c", "CRD must not contain left double quotation mark \u201c")
85+
assert.NotContains(t, raw, "\u201d", "CRD must not contain right double quotation mark \u201d")
86+
}
87+
2988
func TestToVars_ScopeBody_SimpleField(t *testing.T) {
3089
exprs := ApisixRouteHTTPMatchExprs{
3190
{

config/crd/bases/apisix.apache.org_apisixroutes.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ spec:
230230
required:
231231
- scope
232232
type: object
233+
x-kubernetes-validations:
234+
- message: name is required when scope is not Path
235+
rule: self.scope == 'Path' || self.name != ''
233236
value:
234237
description: |-
235238
Value defines a single value to compare against the subject.

docs/en/latest/reference/api-reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,8 +1119,8 @@ ApisixRouteHTTPMatchExprSubject describes the subject of a route matching expres
11191119

11201120
| Field | Description |
11211121
| --- | --- |
1122-
| `scope` _string_ | Scope specifies the subject scope and can be `Header`, `Query`, or `Path`. When Scope is `Path`, Name will be ignored. |
1123-
| `name` _string_ | Name is the name of the header or query parameter. |
1122+
| `scope` _string_ | Scope specifies the subject scope. Supported values: `Header`, `Query`, `Path`, `Cookie`, `Variable`, `Body`. When Scope is `Path`, Name will be ignored. When Scope is `Body`, Name supports dot-notation JSON path (e.g., "model.version", "messages[*].role") and maps to APISIX's post_arg.<name> variable, which works with application/json, application/x-www-form-urlencoded, and multipart/form-data. |
1123+
| `name` _string_ | Name is the name of the subject within the given scope: the header name, query parameter name, cookie name, Nginx variable name, or body field name (dot-notation JSON path supported for Body scope). Optional when Scope is Path. |
11241124

11251125

11261126
_Appears in:_

0 commit comments

Comments
 (0)