This commit is contained in:
yusing
2026-02-16 08:59:01 +08:00
parent 15b9635ee1
commit e4e6f6b3e8
242 changed files with 3953 additions and 3502 deletions

View File

@@ -4,10 +4,10 @@ import (
"net/http"
"github.com/gin-gonic/gin"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
"github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes"
_ "github.com/yusing/goutils/apitypes"
apitypes "github.com/yusing/goutils/apitypes"
)
type RoutesByProvider map[string][]route.Route
@@ -24,5 +24,10 @@ type RoutesByProvider map[string][]route.Route
// @Failure 500 {object} apitypes.ErrorResponse
// @Router /route/by_provider [get]
func ByProvider(c *gin.Context) {
c.JSON(http.StatusOK, routes.ByProvider())
ep := entrypoint.FromCtx(c.Request.Context())
if ep == nil { // impossible, but just in case
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
return
}
c.JSON(http.StatusOK, ep.RoutesByProvider())
}

View File

@@ -1,6 +1,7 @@
package routeApi
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
@@ -54,16 +55,16 @@ type PlaygroundResponse struct {
MatchedRules []string `json:"matchedRules"`
FinalRequest FinalRequest `json:"finalRequest"`
FinalResponse FinalResponse `json:"finalResponse"`
ExecutionError gperr.Error `json:"executionError,omitempty"`
ExecutionError error `json:"executionError,omitempty"` // we need the structured error, not the plain string
UpstreamCalled bool `json:"upstreamCalled"`
} // @name PlaygroundResponse
type ParsedRule struct {
Name string `json:"name"`
On string `json:"on"`
Do string `json:"do"`
ValidationError gperr.Error `json:"validationError,omitempty"`
IsResponseRule bool `json:"isResponseRule"`
Name string `json:"name"`
On string `json:"on"`
Do string `json:"do"`
ValidationError error `json:"validationError,omitempty"` // we need the structured error, not the plain string
IsResponseRule bool `json:"isResponseRule"`
} // @name ParsedRule
type FinalRequest struct {
@@ -138,7 +139,7 @@ func Playground(c *gin.Context) {
// Execute rules
matchedRules := []string{}
upstreamCalled := false
var executionError gperr.Error
var executionError error
// Variables to capture modified request state
var finalReqMethod, finalReqPath, finalReqHost string
@@ -244,20 +245,22 @@ func Playground(c *gin.Context) {
c.JSON(http.StatusOK, response)
}
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *gperr.Error) {
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *error) {
defer func() {
if r := recover(); r != nil {
if outErr != nil {
*outErr = gperr.Errorf("panic during rule execution: %v", r)
*outErr = fmt.Errorf("panic during rule execution: %v", r)
}
}
}()
h(w, r)
}
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
var parsedRules []ParsedRule
var rulesList rules.Rules
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
parsedRules := make([]ParsedRule, 0, len(rawRules))
rulesList := make(rules.Rules, 0, len(rawRules))
var valErrs gperr.Builder
// Parse each rule individually to capture per-rule errors
for _, rawRule := range rawRules {
@@ -284,7 +287,11 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
// Determine if valid
isValid := onErr == nil && doErr == nil
validationErr := gperr.Join(gperr.PrependSubject("on", onErr), gperr.PrependSubject("do", doErr))
var validationErr error
if !isValid {
validationErr = gperr.Join(gperr.PrependSubject(onErr, "on"), gperr.PrependSubject(doErr, "do"))
valErrs.Add(validationErr)
}
parsedRules = append(parsedRules, ParsedRule{
Name: name,
@@ -300,7 +307,7 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
}
}
return parsedRules, rulesList, nil
return parsedRules, rulesList, valErrs.Error()
}
func createMockRequest(mock MockRequest) *http.Request {

View File

@@ -79,7 +79,7 @@ func TestPlayground(t *testing.T) {
if len(resp.MatchedRules) != 1 {
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
}
if resp.FinalResponse.StatusCode != 403 {
if resp.FinalResponse.StatusCode != http.StatusForbidden {
t.Errorf("expected status 403, got %d", resp.FinalResponse.StatusCode)
}
if resp.UpstreamCalled {
@@ -168,7 +168,7 @@ func TestPlayground(t *testing.T) {
if len(resp.MatchedRules) != 1 {
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
}
if resp.FinalResponse.StatusCode != 405 {
if resp.FinalResponse.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("expected status 405, got %d", resp.FinalResponse.StatusCode)
}
},
@@ -179,7 +179,7 @@ func TestPlayground(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Create request
body, _ := json.Marshal(tt.request)
req := httptest.NewRequest("POST", "/api/v1/route/playground", bytes.NewReader(body))
req := httptest.NewRequest(http.MethodPost, "/api/v1/route/playground", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
// Create response recorder
@@ -214,7 +214,7 @@ func TestPlayground(t *testing.T) {
func TestPlaygroundInvalidRequest(t *testing.T) {
gin.SetMode(gin.TestMode)
req := httptest.NewRequest("POST", "/api/v1/route/playground", bytes.NewReader([]byte(`{}`)))
req := httptest.NewRequest(http.MethodPost, "/api/v1/route/playground", bytes.NewReader([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/internal/route/routes"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
apitypes "github.com/yusing/goutils/apitypes"
)
@@ -32,7 +32,13 @@ func Route(c *gin.Context) {
return
}
route, ok := routes.GetIncludeExcluded(request.Which)
ep := entrypoint.FromCtx(c.Request.Context())
if ep == nil { // impossible, but just in case
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
return
}
route, ok := ep.GetRoute(request.Which)
if ok {
c.JSON(http.StatusOK, route)
return

View File

@@ -6,8 +6,8 @@ import (
"time"
"github.com/gin-gonic/gin"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
"github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/http/httpheaders"
"github.com/yusing/goutils/http/websocket"
@@ -32,14 +32,16 @@ func Routes(c *gin.Context) {
return
}
ep := entrypoint.FromCtx(c.Request.Context())
provider := c.Query("provider")
if provider == "" {
c.JSON(http.StatusOK, slices.Collect(routes.IterAll))
c.JSON(http.StatusOK, slices.Collect(ep.IterRoutes))
return
}
rts := make([]types.Route, 0, routes.NumAllRoutes())
for r := range routes.IterAll {
rts := make([]types.Route, 0, ep.NumRoutes())
for r := range ep.IterRoutes {
if r.ProviderName() == provider {
rts = append(rts, r)
}
@@ -48,17 +50,19 @@ func Routes(c *gin.Context) {
}
func RoutesWS(c *gin.Context) {
ep := entrypoint.FromCtx(c.Request.Context())
provider := c.Query("provider")
if provider == "" {
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
return slices.Collect(routes.IterAll), nil
return slices.Collect(ep.IterRoutes), nil
})
return
}
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
rts := make([]types.Route, 0, routes.NumAllRoutes())
for r := range routes.IterAll {
rts := make([]types.Route, 0, ep.NumRoutes())
for r := range ep.IterRoutes {
if r.ProviderName() == provider {
rts = append(rts, r)
}