update route
This commit is contained in:
@@ -3,6 +3,7 @@ package route
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/iancoleman/strcase"
|
"github.com/iancoleman/strcase"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
@@ -106,14 +107,24 @@ func buildParamToken(item ParamDefinition) string {
|
|||||||
return fmt.Sprintf(`Body[%s]("%s")`, item.Type, key)
|
return fmt.Sprintf(`Body[%s]("%s")`, item.Type, key)
|
||||||
case PositionPath:
|
case PositionPath:
|
||||||
// If a model field is specified, generate a model-lookup binder from path value.
|
// If a model field is specified, generate a model-lookup binder from path value.
|
||||||
if item.ModelField != "" || item.Model != "" {
|
if item.Model != "" {
|
||||||
field := item.ModelField
|
field := "id"
|
||||||
if field == "" {
|
fieldType := "int"
|
||||||
field = "id"
|
if strings.Contains(item.Model, ":") {
|
||||||
|
parts := strings.SplitN(item.Model, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
field = parts[0]
|
||||||
|
fieldType = parts[1]
|
||||||
}
|
}
|
||||||
// PathModel is expected to resolve the path param to the specified model by field.
|
} else {
|
||||||
// Example: PathModel[models.User]("id", "user_id")
|
field = item.Model
|
||||||
return fmt.Sprintf(`PathModel[%s]("%s", "%s")`, item.Type, field, key)
|
}
|
||||||
|
|
||||||
|
tpl := `func(ctx fiber.Ctx) (*%s, error) {
|
||||||
|
v := fiber.Params[%s](ctx, "%s")
|
||||||
|
return %sQuery.WithContext(ctx).Where(field.NewUnsafeFieldRaw("%s = ?", v)).First()
|
||||||
|
}`
|
||||||
|
return fmt.Sprintf(tpl, item.Type, fieldType, key, item.Type, field)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(`Path%s[%s]("%s")`, scalarSuffix(item.Type), item.Type, key)
|
return fmt.Sprintf(`Path%s[%s]("%s")`, scalarSuffix(item.Type), item.Type, key)
|
||||||
case PositionLocal:
|
case PositionLocal:
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ type ParamDefinition struct {
|
|||||||
Type string
|
Type string
|
||||||
Key string
|
Key string
|
||||||
Model string
|
Model string
|
||||||
// ModelField is the field/column name used to lookup the model when Model is set.
|
|
||||||
// Example: `@Bind user path key(id) model(database/models.User:id)` -> Model=database/models.User, ModelField=id
|
|
||||||
ModelField string
|
|
||||||
Position Position
|
Position Position
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +146,11 @@ func ParseFile(file string) []RouteDefinition {
|
|||||||
if path == "" || method == "" {
|
if path == "" || method == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.WithField("file", file).WithField("action", decl.Name.Name).WithField("path", path).WithField("method", method).Info("get router")
|
log.WithField("file", file).
|
||||||
|
WithField("action", decl.Name.Name).
|
||||||
|
WithField("path", path).
|
||||||
|
WithField("method", method).
|
||||||
|
Info("get router")
|
||||||
|
|
||||||
// 拿参数列表去, 忽略 context.Context 参数
|
// 拿参数列表去, 忽略 context.Context 参数
|
||||||
orderBindParams := []ParamDefinition{}
|
orderBindParams := []ParamDefinition{}
|
||||||
@@ -256,27 +257,15 @@ func parseRouteBind(bind string) ParamDefinition {
|
|||||||
param.Key = parts[i+1]
|
param.Key = parts[i+1]
|
||||||
case "model":
|
case "model":
|
||||||
// Supported formats:
|
// Supported formats:
|
||||||
// - model(field) -> only specify model field/column; model type inferred from parameter
|
// - model(field:field_type) -> only specify model field/column;
|
||||||
// - model(pkg/path.Type) -> type hint (optional); default field will be used later
|
|
||||||
// - model(pkg/path.Type:id) or model(pkg/path.Type#id) -> type + field
|
|
||||||
mv := parts[i+1]
|
mv := parts[i+1]
|
||||||
// if mv contains no dot, treat as field name directly
|
// if mv contains no dot, treat as field name directly
|
||||||
if !strings.Contains(mv, ".") && !strings.Contains(mv, "/") {
|
if mv == "" {
|
||||||
param.ModelField = mv
|
param.Model = "id"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// otherwise try type[:field]
|
|
||||||
fieldSep := ":"
|
|
||||||
if strings.Contains(mv, "#") {
|
|
||||||
fieldSep = "#"
|
|
||||||
}
|
|
||||||
if idx := strings.LastIndex(mv, fieldSep); idx > 0 && idx < len(mv)-1 {
|
|
||||||
param.Model = mv[:idx]
|
|
||||||
param.ModelField = mv[idx+1:]
|
|
||||||
} else {
|
|
||||||
param.Model = mv
|
param.Model = mv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return param
|
return param
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.ipao.vip/atomctl/v2/pkg/utils/gomod"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that @Bind with model(field) on a path parameter generates PathModel[T](field, key)
|
|
||||||
func Test_PathModelBind_FromRouteComments(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
src := `package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct{}
|
|
||||||
|
|
||||||
type Demo struct{}
|
|
||||||
|
|
||||||
// @Router /users/:id [get]
|
|
||||||
// @Bind user path key(id) model(id)
|
|
||||||
func (d *Demo) Show(ctx context.Context, user *User) (*User, error) {
|
|
||||||
return nil, nil
|
|
||||||
}`
|
|
||||||
|
|
||||||
// minimal go.mod so gomod.GetPackageModuleName works without panic
|
|
||||||
gomodPath := filepath.Join(dir, "go.mod")
|
|
||||||
goModContent := "module example.com/test\n\ngo 1.23\n"
|
|
||||||
if err := os.WriteFile(gomodPath, []byte(goModContent), 0o644); err != nil {
|
|
||||||
t.Fatalf("write go.mod: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gomod.Parse(gomodPath); err != nil {
|
|
||||||
t.Fatalf("gomod.Parse error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file := filepath.Join(dir, "demo.go")
|
|
||||||
if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
|
|
||||||
t.Fatalf("write file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defs := ParseFile(file)
|
|
||||||
if len(defs) != 1 {
|
|
||||||
t.Fatalf("expected 1 route definition, got %d", len(defs))
|
|
||||||
}
|
|
||||||
if len(defs[0].Actions) != 1 {
|
|
||||||
t.Fatalf("expected 1 action, got %d", len(defs[0].Actions))
|
|
||||||
}
|
|
||||||
act := defs[0].Actions[0]
|
|
||||||
if len(act.Params) != 1 {
|
|
||||||
t.Fatalf("expected 1 param, got %d", len(act.Params))
|
|
||||||
}
|
|
||||||
p := act.Params[0]
|
|
||||||
if p.Position != PositionPath {
|
|
||||||
t.Fatalf("expected path position, got %s", p.Position)
|
|
||||||
}
|
|
||||||
if p.Key != "id" {
|
|
||||||
t.Fatalf("expected key=id, got %s", p.Key)
|
|
||||||
}
|
|
||||||
if p.ModelField != "id" {
|
|
||||||
t.Fatalf("expected ModelField=id, got %s", p.ModelField)
|
|
||||||
}
|
|
||||||
if p.Type != "User" { // pointer should be trimmed for non-local
|
|
||||||
t.Fatalf("expected Type=User, got %s", p.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build render data and check binder token
|
|
||||||
rd, err := buildRenderData(RenderBuildOpts{
|
|
||||||
PackageName: "v1",
|
|
||||||
ProjectPackage: "example.com/test",
|
|
||||||
Routes: defs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("buildRenderData error: %v", err)
|
|
||||||
}
|
|
||||||
// Render to text and assert PathModel usage
|
|
||||||
out, err := renderTemplate(rd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("renderTemplate error: %v", err)
|
|
||||||
}
|
|
||||||
got := string(out)
|
|
||||||
if !strings.Contains(got, "PathModel[User](\"id\", \"id\")") {
|
|
||||||
t.Fatalf("expected generated code to contain PathModel[User](\"id\", \"id\"), got:\n%s", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user