mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-24 19:34:53 +01:00
feat(api): accept rule config block string in playground
Update playground request to take rules as a string and parse either YAML list or DSL config, with tests and swagger updates.
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user