diff --git a/internal/api/v1/docs/swagger.json b/internal/api/v1/docs/swagger.json index 29d9018b..663084c0 100644 --- a/internal/api/v1/docs/swagger.json +++ b/internal/api/v1/docs/swagger.json @@ -5125,10 +5125,7 @@ "$ref": "#/definitions/MockResponse" }, "rules": { - "type": "array", - "items": { - "$ref": "#/definitions/routeApi.RawRule" - }, + "type": "string", "x-nullable": false, "x-omitempty": false } @@ -6926,28 +6923,6 @@ "x-nullable": false, "x-omitempty": false }, - "routeApi.RawRule": { - "type": "object", - "properties": { - "do": { - "type": "string", - "x-nullable": false, - "x-omitempty": false - }, - "name": { - "type": "string", - "x-nullable": false, - "x-omitempty": false - }, - "on": { - "type": "string", - "x-nullable": false, - "x-omitempty": false - } - }, - "x-nullable": false, - "x-omitempty": false - }, "routeApi.RoutesByProvider": { "type": "object", "additionalProperties": { diff --git a/internal/api/v1/docs/swagger.yaml b/internal/api/v1/docs/swagger.yaml index a15a9155..33299ead 100644 --- a/internal/api/v1/docs/swagger.yaml +++ b/internal/api/v1/docs/swagger.yaml @@ -905,9 +905,7 @@ definitions: mockResponse: $ref: '#/definitions/MockResponse' rules: - items: - $ref: '#/definitions/routeApi.RawRule' - type: array + type: string required: - rules type: object @@ -1858,15 +1856,6 @@ definitions: uptime: type: string type: object - routeApi.RawRule: - properties: - do: - type: string - name: - type: string - "on": - type: string - type: object routeApi.RoutesByProvider: additionalProperties: items: diff --git a/internal/api/v1/route/playground.go b/internal/api/v1/route/playground.go index 02fba8b5..b1133c0a 100644 --- a/internal/api/v1/route/playground.go +++ b/internal/api/v1/route/playground.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/goccy/go-yaml" "github.com/yusing/godoxy/internal/common" "github.com/yusing/godoxy/internal/route/rules" apitypes "github.com/yusing/goutils/apitypes" @@ -23,7 +24,7 @@ type RawRule struct { } type PlaygroundRequest struct { - Rules []RawRule `json:"rules" binding:"required"` + Rules string `json:"rules" binding:"required"` MockRequest MockRequest `json:"mockRequest"` MockResponse MockResponse `json:"mockResponse"` } // @name PlaygroundRequest @@ -255,7 +256,35 @@ func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFu h(w, r) } -func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) { +func parseRules(config string) ([]ParsedRule, rules.Rules, error) { + config = strings.TrimSpace(config) + if config == "" { + return []ParsedRule{}, nil, nil + } + + var rawRules []RawRule + if err := yaml.Unmarshal([]byte(config), &rawRules); err == nil && len(rawRules) > 0 { + return parseRawRules(rawRules) + } + + var rulesList rules.Rules + if err := rulesList.Parse(config); err != nil { + return nil, nil, err + } + + parsedRules := make([]ParsedRule, 0, len(rulesList)) + for _, rule := range rulesList { + parsedRules = append(parsedRules, ParsedRule{ + Name: rule.Name, + On: rule.On.String(), + Do: rule.Do.String(), + }) + } + + return parsedRules, rulesList, nil +} + +func parseRawRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) { parsedRules := make([]ParsedRule, 0, len(rawRules)) rulesList := make(rules.Rules, 0, len(rawRules)) diff --git a/internal/api/v1/route/playground_test.go b/internal/api/v1/route/playground_test.go index 91c79026..953c3857 100644 --- a/internal/api/v1/route/playground_test.go +++ b/internal/api/v1/route/playground_test.go @@ -22,13 +22,10 @@ func TestPlayground(t *testing.T) { { name: "simple path matching rule", request: PlaygroundRequest{ - Rules: []RawRule{ - { - Name: "test rule", - On: "path /api", - Do: "pass", - }, - }, + Rules: `- name: test rule + on: path /api + do: pass +`, MockRequest: MockRequest{ Method: "GET", Path: "/api", @@ -53,13 +50,10 @@ func TestPlayground(t *testing.T) { { name: "header matching rule", request: PlaygroundRequest{ - Rules: []RawRule{ - { - Name: "check user agent", - On: "header User-Agent Chrome", - Do: "error 403 Forbidden", - }, - }, + Rules: `- name: check user agent + on: header User-Agent Chrome + do: error 403 Forbidden +`, MockRequest: MockRequest{ Method: "GET", Path: "/", @@ -90,13 +84,10 @@ func TestPlayground(t *testing.T) { { name: "invalid rule syntax", request: PlaygroundRequest{ - Rules: []RawRule{ - { - Name: "bad rule", - On: "invalid_checker something", - Do: "pass", - }, - }, + Rules: `- name: bad rule + on: invalid_checker something + do: pass +`, MockRequest: MockRequest{ Method: "GET", Path: "/", @@ -115,13 +106,10 @@ func TestPlayground(t *testing.T) { { name: "rewrite path rule", request: PlaygroundRequest{ - Rules: []RawRule{ - { - Name: "rewrite rule", - On: "path glob(/api/*)", - Do: "rewrite /api/ /v1/", - }, - }, + Rules: `- name: rewrite rule + on: path glob(/api/*) + do: rewrite /api/ /v1/ +`, MockRequest: MockRequest{ Method: "GET", Path: "/api/users", @@ -148,13 +136,10 @@ func TestPlayground(t *testing.T) { { name: "method matching rule", request: PlaygroundRequest{ - Rules: []RawRule{ - { - Name: "block POST", - On: "method POST", - Do: `error "405" "Method Not Allowed"`, - }, - }, + Rules: `- name: block POST + on: method POST + do: error "405" "Method Not Allowed" +`, MockRequest: MockRequest{ Method: "POST", Path: "/api", @@ -173,6 +158,63 @@ func TestPlayground(t *testing.T) { } }, }, + { + name: "block syntax default rule", + request: PlaygroundRequest{ + Rules: `default { + pass +}`, + MockRequest: MockRequest{ + Method: "GET", + Path: "/", + }, + }, + wantStatusCode: http.StatusOK, + checkResponse: func(t *testing.T, resp PlaygroundResponse) { + if len(resp.ParsedRules) != 1 { + t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules)) + } + if resp.ParsedRules[0].ValidationError != nil { + t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError) + } + if !resp.UpstreamCalled { + t.Error("expected upstream to be called") + } + }, + }, + { + name: "block syntax conditional rule", + request: PlaygroundRequest{ + Rules: `header User-Agent Chrome { + error 403 Forbidden +}`, + MockRequest: MockRequest{ + Method: "GET", + Path: "/", + Headers: map[string][]string{ + "User-Agent": {"Chrome"}, + }, + }, + }, + wantStatusCode: http.StatusOK, + checkResponse: func(t *testing.T, resp PlaygroundResponse) { + if len(resp.ParsedRules) != 1 { + t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules)) + } + if resp.ParsedRules[0].ValidationError != nil { + t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError) + } + if len(resp.MatchedRules) != 1 { + t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules)) + } + if resp.FinalResponse.StatusCode != http.StatusForbidden { + t.Errorf("expected status 403, got %d", resp.FinalResponse.StatusCode) + } + if resp.UpstreamCalled { + t.Error("expected upstream not to be called") + } + }, + }, } for _, tt := range tests {