From f2079bcb02185b97591543e3cc583cf02cb303b2 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Sat, 24 Aug 2024 10:54:33 -0700 Subject: [PATCH 01/26] remove unused variable --- generate.go | 1 - 1 file changed, 1 deletion(-) diff --git a/generate.go b/generate.go index b304d48..d5eed59 100644 --- a/generate.go +++ b/generate.go @@ -21,7 +21,6 @@ const ( muxVarIdent = "mux" requestPathValue = "PathValue" - templatesLookup = "Lookup" httpRequestContextMethod = "Context" httpPackageIdent = "http" httpResponseWriterIdent = "ResponseWriter" From 9672cc5210dd8f53f28ee9b9c5cca235f36707d6 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:45:01 -0700 Subject: [PATCH 02/26] add template param to Generate --- cmd/muxt/generate.go | 2 +- generate.go | 3 ++- generate_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/muxt/generate.go b/cmd/muxt/generate.go index 4b8c2e9..e56d2a1 100644 --- a/cmd/muxt/generate.go +++ b/cmd/muxt/generate.go @@ -100,7 +100,7 @@ func generateCommand(args []string, workingDirectory string, getEnv func(string) return err } out := log.New(stdout, "", 0) - s, err := muxt.Generate(templateNames, g.goPackage, g.templatesVariable, g.routesFunction, g.receiverIdent, g.Package.Fset, g.Package.Syntax, g.Package.Syntax, out) + s, err := muxt.Generate(templateNames, ts, g.goPackage, g.templatesVariable, g.routesFunction, g.receiverIdent, g.Package.Fset, g.Package.Syntax, g.Package.Syntax, out) if err != nil { return err } diff --git a/generate.go b/generate.go index d5eed59..5ce7b0f 100644 --- a/generate.go +++ b/generate.go @@ -5,6 +5,7 @@ import ( "fmt" "go/ast" "go/token" + "html/template" "log" "slices" "strconv" @@ -41,7 +42,7 @@ const ( receiverInterfaceIdent = "RoutesReceiver" ) -func Generate(templateNames []TemplateName, packageName, templatesVariableName, routesFunctionName, receiverTypeIdent string, _ *token.FileSet, receiverPackage, templatesPackage []*ast.File, log *log.Logger) (string, error) { +func Generate(templateNames []TemplateName, _ *template.Template, packageName, templatesVariableName, routesFunctionName, receiverTypeIdent string, _ *token.FileSet, receiverPackage, templatesPackage []*ast.File, log *log.Logger) (string, error) { packageName = cmp.Or(packageName, defaultPackageName) templatesVariableName = cmp.Or(templatesVariableName, DefaultTemplatesVariableName) routesFunctionName = cmp.Or(routesFunctionName, DefaultRoutesFunctionName) diff --git a/generate_test.go b/generate_test.go index d34e5e3..8d9679d 100644 --- a/generate_test.go +++ b/generate_test.go @@ -571,7 +571,7 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo logs := log.New(io.Discard, "", 0) set := token.NewFileSet() goFiles := methodFuncTypeLoader(t, set, tt.ReceiverPackage) - out, err := muxt.Generate(templateNames, tt.PackageName, tt.TemplatesVar, tt.RoutesFunc, tt.Receiver, set, goFiles, goFiles, logs) + out, err := muxt.Generate(templateNames, ts, tt.PackageName, tt.TemplatesVar, tt.RoutesFunc, tt.Receiver, set, goFiles, goFiles, logs) if tt.ExpectedError == "" { assert.NoError(t, err) assert.Equal(t, tt.ExpectedFile, out) From 7547b18f8fcaef2b048703e48bf54407d9488332 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:45:27 -0700 Subject: [PATCH 03/26] refactor: rename pattern variable to name --- generate.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/generate.go b/generate.go index 5ce7b0f..9172c1b 100644 --- a/generate.go +++ b/generate.go @@ -60,33 +60,33 @@ func Generate(templateNames []TemplateName, _ *template.Template, packageName, t imports := []*ast.ImportSpec{ importSpec("net/" + httpPackageIdent), } - for _, pattern := range templateNames { + for _, name := range templateNames { var method *ast.FuncType - if pattern.fun != nil { + if name.fun != nil { for _, funcDecl := range source.IterateFunctions(receiverPackage) { - if !pattern.matchReceiver(funcDecl, receiverTypeIdent) { + if !name.matchReceiver(funcDecl, receiverTypeIdent) { continue } method = funcDecl.Type break } if method == nil { - me, im := pattern.funcType() + me, im := name.funcType() method = me imports = append(imports, im...) } receiverInterface.Methods.List = append(receiverInterface.Methods.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(pattern.fun.Name)}, + Names: []*ast.Ident{ast.NewIdent(name.fun.Name)}, Type: method, }) } - handlerFunc, methodImports, err := pattern.funcLit(method) + handlerFunc, methodImports, err := name.funcLit(method) if err != nil { return "", err } imports = sortImports(append(imports, methodImports...)) - routes.Body.List = append(routes.Body.List, pattern.callHandleFunc(handlerFunc)) - log.Printf("%s has route for %s", routesFunctionName, pattern.String()) + routes.Body.List = append(routes.Body.List, name.callHandleFunc(handlerFunc)) + log.Printf("%s has route for %s", routesFunctionName, name.String()) } importGen := &ast.GenDecl{ Tok: token.IMPORT, From 12519f927fa1f4f9edb5525e85e7726c8177d0ea Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:13:56 -0700 Subject: [PATCH 04/26] rename ast -> go --- internal/source/{ast.go => go.go} | 0 internal/source/{ast_test.go => go_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename internal/source/{ast.go => go.go} (100%) rename internal/source/{ast_test.go => go_test.go} (100%) diff --git a/internal/source/ast.go b/internal/source/go.go similarity index 100% rename from internal/source/ast.go rename to internal/source/go.go diff --git a/internal/source/ast_test.go b/internal/source/go_test.go similarity index 100% rename from internal/source/ast_test.go rename to internal/source/go_test.go From 204380d223bd699fa51ede6f73075dd301a62620 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:09:09 -0700 Subject: [PATCH 05/26] refactor: add helper function for generating status code expressions --- generate.go | 21 ++++------- internal/source/go.go | 81 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/generate.go b/generate.go index 9172c1b..83ad09d 100644 --- a/generate.go +++ b/generate.go @@ -7,6 +7,7 @@ import ( "go/token" "html/template" "log" + "net/http" "slices" "strconv" "strings" @@ -27,9 +28,6 @@ const ( httpResponseWriterIdent = "ResponseWriter" httpServeMuxIdent = "ServeMux" httpRequestIdent = "Request" - httpStatusCode200Ident = "StatusOK" - httpStatusCode500Ident = "StatusInternalServerError" - httpStatusCode400Ident = "StatusBadRequest" httpHandleFuncIdent = "HandleFunc" contextPackageIdent = "context" @@ -200,7 +198,7 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo }, Args: []ast.Expr{}, }, - httpStatusCode(httpStatusCode500Ident), + source.HTTPStatusCode(httpPackageIdent, http.StatusInternalServerError), }, }}, &ast.ReturnStmt{}, @@ -211,7 +209,7 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo } else { lit.Body.List = append(lit.Body.List, &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent(dataVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{call}}) } - lit.Body.List = append(lit.Body.List, def.executeCall(httpStatusCode(httpStatusCode200Ident), ast.NewIdent(dataVarIdent), writeHeader)) + lit.Body.List = append(lit.Body.List, def.executeCall(source.HTTPStatusCode(httpPackageIdent, http.StatusOK), ast.NewIdent(dataVarIdent), writeHeader)) return lit, imports, nil } @@ -371,13 +369,6 @@ func contextContextType() *ast.SelectorExpr { return &ast.SelectorExpr{X: ast.NewIdent(contextPackageIdent), Sel: ast.NewIdent(contextContextTypeIdent)} } -func httpStatusCode(name string) *ast.SelectorExpr { - return &ast.SelectorExpr{ - X: ast.NewIdent(httpPackageIdent), - Sel: ast.NewIdent(name), - } -} - func contextAssignment() *ast.AssignStmt { return &ast.AssignStmt{ Tok: token.DEFINE, @@ -827,7 +818,7 @@ func paramParseError(errVar *ast.Ident) *ast.IfStmt { }, Args: []ast.Expr{}, }, - httpStatusCode(httpStatusCode400Ident), + source.HTTPStatusCode(httpPackageIdent, http.StatusBadRequest), }, }}, &ast.ReturnStmt{}, @@ -853,7 +844,7 @@ func (def TemplateName) executeCall(status, data ast.Expr, writeHeader bool) *as func (def TemplateName) httpRequestReceiverTemplateHandlerFunc() *ast.FuncLit { return &ast.FuncLit{ Type: httpHandlerFuncType(), - Body: &ast.BlockStmt{List: []ast.Stmt{def.executeCall(httpStatusCode(httpStatusCode200Ident), ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), true)}}, + Body: &ast.BlockStmt{List: []ast.Stmt{def.executeCall(source.HTTPStatusCode(httpPackageIdent, http.StatusOK), ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), true)}}, } } @@ -932,7 +923,7 @@ func executeFuncDecl(templatesVariableIdent string) *ast.FuncDecl { }, Args: []ast.Expr{}, }, - httpStatusCode(httpStatusCode500Ident), + source.HTTPStatusCode(httpPackageIdent, http.StatusInternalServerError), }, }}, &ast.ReturnStmt{}, diff --git a/internal/source/go.go b/internal/source/go.go index 133f266..2326ed3 100644 --- a/internal/source/go.go +++ b/internal/source/go.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/printer" "go/token" + "net/http" "strconv" "strings" ) @@ -141,3 +142,83 @@ func IterateFieldTypes(list []*ast.Field) func(func(int, ast.Expr) bool) { } } } + +var httpCodes = map[int]string{ + http.StatusContinue: "StatusContinue", + http.StatusSwitchingProtocols: "StatusSwitchingProtocols", + http.StatusProcessing: "StatusProcessing", + http.StatusEarlyHints: "StatusEarlyHints", + + http.StatusOK: "StatusOK", + http.StatusCreated: "StatusCreated", + http.StatusAccepted: "StatusAccepted", + http.StatusNonAuthoritativeInfo: "StatusNonAuthoritativeInfo", + http.StatusNoContent: "StatusNoContent", + http.StatusResetContent: "StatusResetContent", + http.StatusPartialContent: "StatusPartialContent", + http.StatusMultiStatus: "StatusMultiStatus", + http.StatusAlreadyReported: "StatusAlreadyReported", + http.StatusIMUsed: "StatusIMUsed", + + http.StatusMultipleChoices: "StatusMultipleChoices", + http.StatusMovedPermanently: "StatusMovedPermanently", + http.StatusFound: "StatusFound", + http.StatusSeeOther: "StatusSeeOther", + http.StatusNotModified: "StatusNotModified", + http.StatusUseProxy: "StatusUseProxy", + http.StatusTemporaryRedirect: "StatusTemporaryRedirect", + http.StatusPermanentRedirect: "StatusPermanentRedirect", + + http.StatusBadRequest: "StatusBadRequest", + http.StatusUnauthorized: "StatusUnauthorized", + http.StatusPaymentRequired: "StatusPaymentRequired", + http.StatusForbidden: "StatusForbidden", + http.StatusNotFound: "StatusNotFound", + http.StatusMethodNotAllowed: "StatusMethodNotAllowed", + http.StatusNotAcceptable: "StatusNotAcceptable", + http.StatusProxyAuthRequired: "StatusProxyAuthRequired", + http.StatusRequestTimeout: "StatusRequestTimeout", + http.StatusConflict: "StatusConflict", + http.StatusGone: "StatusGone", + http.StatusLengthRequired: "StatusLengthRequired", + http.StatusPreconditionFailed: "StatusPreconditionFailed", + http.StatusRequestEntityTooLarge: "StatusRequestEntityTooLarge", + http.StatusRequestURITooLong: "StatusRequestURITooLong", + http.StatusUnsupportedMediaType: "StatusUnsupportedMediaType", + http.StatusRequestedRangeNotSatisfiable: "StatusRequestedRangeNotSatisfiable", + http.StatusExpectationFailed: "StatusExpectationFailed", + http.StatusTeapot: "StatusTeapot", + http.StatusMisdirectedRequest: "StatusMisdirectedRequest", + http.StatusUnprocessableEntity: "StatusUnprocessableEntity", + http.StatusLocked: "StatusLocked", + http.StatusFailedDependency: "StatusFailedDependency", + http.StatusTooEarly: "StatusTooEarly", + http.StatusUpgradeRequired: "StatusUpgradeRequired", + http.StatusPreconditionRequired: "StatusPreconditionRequired", + http.StatusTooManyRequests: "StatusTooManyRequests", + http.StatusRequestHeaderFieldsTooLarge: "StatusRequestHeaderFieldsTooLarge", + http.StatusUnavailableForLegalReasons: "StatusUnavailableForLegalReasons", + + http.StatusInternalServerError: "StatusInternalServerError", + http.StatusNotImplemented: "StatusNotImplemented", + http.StatusBadGateway: "StatusBadGateway", + http.StatusServiceUnavailable: "StatusServiceUnavailable", + http.StatusGatewayTimeout: "StatusGatewayTimeout", + http.StatusHTTPVersionNotSupported: "StatusHTTPVersionNotSupported", + http.StatusVariantAlsoNegotiates: "StatusVariantAlsoNegotiates", + http.StatusInsufficientStorage: "StatusInsufficientStorage", + http.StatusLoopDetected: "StatusLoopDetected", + http.StatusNotExtended: "StatusNotExtended", + http.StatusNetworkAuthenticationRequired: "StatusNetworkAuthenticationRequired", +} + +func HTTPStatusCode(pkg string, n int) ast.Expr { + ident, ok := httpCodes[n] + if !ok { + return &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(n)} + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkg), + Sel: ast.NewIdent(ident), + } +} From 25837999054844dfffe4277928ab8f34354ddd11 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:16:36 -0700 Subject: [PATCH 06/26] remove unused template name scope --- name.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/name.go b/name.go index 3437d38..90bfbe0 100644 --- a/name.go +++ b/name.go @@ -173,8 +173,6 @@ const ( TemplateNameScopeIdentifierHTTPRequest = "request" TemplateNameScopeIdentifierHTTPResponse = "response" TemplateNameScopeIdentifierContext = "ctx" - TemplateNameScopeIdentifierTemplate = "template" - TemplateNameScopeIdentifierLogger = "logger" ) func patternScope() []string { @@ -182,7 +180,5 @@ func patternScope() []string { TemplateNameScopeIdentifierHTTPRequest, TemplateNameScopeIdentifierHTTPResponse, TemplateNameScopeIdentifierContext, - TemplateNameScopeIdentifierTemplate, - TemplateNameScopeIdentifierLogger, } } From e78bdc9f62ee77fca3225bc3975fb171be17dba2 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:21:18 -0700 Subject: [PATCH 07/26] refactor: factor our errVar --- generate.go | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/generate.go b/generate.go index 83ad09d..2300313 100644 --- a/generate.go +++ b/generate.go @@ -164,7 +164,8 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo call.Args = append(call.Args, ast.NewIdent(TemplateNameScopeIdentifierContext)) imports = append(imports, importSpec("context")) default: - statements, parseImports, err := httpPathValueAssignment(method, i, arg) + errVar := ast.NewIdent("err") + statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar) if err != nil { return nil, nil, err } @@ -382,7 +383,7 @@ func contextAssignment() *ast.AssignStmt { } } -func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast.Stmt, []*ast.ImportSpec, error) { +func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { @@ -396,8 +397,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast default: return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) case "bool": - errVar := ast.NewIdent("err") - assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, Tok: token.DEFINE, @@ -420,8 +419,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -458,8 +455,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int16": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -496,8 +491,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int32": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -534,8 +527,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int8": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -572,8 +563,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int64": - errVar := ast.NewIdent("err") - assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, Tok: token.DEFINE, @@ -600,8 +589,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -638,8 +625,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint16": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -676,8 +661,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint32": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ @@ -715,8 +698,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint64": - errVar := ast.NewIdent("err") - assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, Tok: token.DEFINE, @@ -743,8 +724,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident) ([]ast return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint8": - errVar := ast.NewIdent("err") - tmp := arg.Name + parsedVarSuffix parse := &ast.AssignStmt{ From aead362144e61bf443b002990d0ee48ed84beca8 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:27:46 -0700 Subject: [PATCH 08/26] refactor: move err checkout of assignment func --- generate.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/generate.go b/generate.go index 2300313..c5d3a91 100644 --- a/generate.go +++ b/generate.go @@ -165,7 +165,8 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo imports = append(imports, importSpec("context")) default: errVar := ast.NewIdent("err") - statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar) + errCheck := paramParseError(errVar) + statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, errCheck) if err != nil { return nil, nil, err } @@ -383,7 +384,7 @@ func contextAssignment() *ast.AssignStmt { } } -func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident) ([]ast.Stmt, []*ast.ImportSpec, error) { +func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { @@ -415,8 +416,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int": tmp := arg.Name + parsedVarSuffix @@ -443,7 +442,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -479,7 +477,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -515,7 +512,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -551,7 +547,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -585,8 +580,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint": tmp := arg.Name + parsedVarSuffix @@ -613,7 +606,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -649,7 +641,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -685,7 +676,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, @@ -720,8 +710,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint8": tmp := arg.Name + parsedVarSuffix @@ -748,7 +736,6 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident }}, } - errCheck := paramParseError(errVar) assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, Tok: token.DEFINE, From 63695a5d92f6b8ef6e0aa4dc5b7dc3a739e0d82b Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:31:10 -0700 Subject: [PATCH 09/26] refactor: pass in str source --- generate.go | 107 ++++++++++------------------------------------------ 1 file changed, 20 insertions(+), 87 deletions(-) diff --git a/generate.go b/generate.go index c5d3a91..fd40645 100644 --- a/generate.go +++ b/generate.go @@ -166,7 +166,14 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo default: errVar := ast.NewIdent("err") errCheck := paramParseError(errVar) - statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, errCheck) + src := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent(requestPathValue), + }, + Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, + } + statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, src, errCheck) if err != nil { return nil, nil, err } @@ -384,7 +391,7 @@ func contextAssignment() *ast.AssignStmt { } } -func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { +func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, str ast.Expr, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { @@ -406,13 +413,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseBool"), }, - Args: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }}, + Args: []ast.Expr{str}, }}, } @@ -428,17 +429,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "64", Kind: token.INT}, - }, + Args: []ast.Expr{str}, }}, } @@ -463,17 +454,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "16", Kind: token.INT}, - }, + Args: []ast.Expr{str}, }}, } @@ -499,13 +480,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseInt"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "32", Kind: token.INT}, }, @@ -534,13 +509,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseInt"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "8", Kind: token.INT}, }, @@ -567,13 +536,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseInt"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "64", Kind: token.INT}, }, @@ -593,13 +556,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseUint"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "64", Kind: token.INT}, }, @@ -628,13 +585,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseUint"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "16", Kind: token.INT}, }, @@ -663,13 +614,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseUint"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "32", Kind: token.INT}, }, @@ -697,13 +642,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseUint"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "64", Kind: token.INT}, }, @@ -723,13 +662,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Sel: ast.NewIdent("ParseUint"), }, Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }, + str, &ast.BasicLit{Value: "10", Kind: token.INT}, &ast.BasicLit{Value: "8", Kind: token.INT}, }, From 63f079ef6814e83419fa4c0fbca6d0360695fdbb Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:37:36 -0700 Subject: [PATCH 10/26] refactor: use Atoi for int and factor out lit to source pkg --- generate.go | 53 +++++++++---------------------------------- generate_test.go | 2 +- internal/source/go.go | 2 ++ 3 files changed, 14 insertions(+), 43 deletions(-) diff --git a/generate.go b/generate.go index fd40645..598e0b1 100644 --- a/generate.go +++ b/generate.go @@ -401,6 +401,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident if !ok { return nil, nil, fmt.Errorf("unsupported type: %s", source.Format(typeExp)) } + base10 := source.Int(10) switch paramTypeIdent.Name { default: return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) @@ -427,7 +428,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseInt"), + Sel: ast.NewIdent("Atoi"), }, Args: []ast.Expr{str}, }}, @@ -454,7 +455,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{str}, + Args: []ast.Expr{str, base10, source.Int(16)}, }}, } @@ -479,11 +480,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "32", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(32)}, }}, } @@ -508,11 +505,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "8", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(8)}, }}, } @@ -535,11 +528,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseInt"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "64", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(64)}, }}, } @@ -555,11 +544,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseUint"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "64", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(64)}, }}, } @@ -584,11 +569,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseUint"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "16", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(16)}, }}, } @@ -613,11 +594,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseUint"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "32", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(32)}, }}, } @@ -641,11 +618,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseUint"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "64", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(64)}, }}, } @@ -661,11 +634,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident X: ast.NewIdent("strconv"), Sel: ast.NewIdent("ParseUint"), }, - Args: []ast.Expr{ - str, - &ast.BasicLit{Value: "10", Kind: token.INT}, - &ast.BasicLit{Value: "8", Kind: token.INT}, - }, + Args: []ast.Expr{str, base10, source.Int(8)}, }}, } diff --git a/generate_test.go b/generate_test.go index 8d9679d..415830a 100644 --- a/generate_test.go +++ b/generate_test.go @@ -452,7 +452,7 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /bool/{value} PassBool(value)", http.StatusOK, data) }) mux.HandleFunc("GET /int/{value}", func(response http.ResponseWriter, request *http.Request) { - valueParsed, err := strconv.ParseInt(request.PathValue("value"), 10, 64) + valueParsed, err := strconv.Atoi(request.PathValue("value")) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return diff --git a/internal/source/go.go b/internal/source/go.go index 2326ed3..9bb9b56 100644 --- a/internal/source/go.go +++ b/internal/source/go.go @@ -222,3 +222,5 @@ func HTTPStatusCode(pkg string, n int) ast.Expr { Sel: ast.NewIdent(ident), } } + +func Int(n int) *ast.BasicLit { return &ast.BasicLit{Value: strconv.Itoa(n), Kind: token.INT} } From 1af4420ae95afc9ff981caca0d7be710ff1f88c8 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:42:30 -0700 Subject: [PATCH 11/26] refactor: move token out of assign func --- generate.go | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/generate.go b/generate.go index 598e0b1..cd2e94f 100644 --- a/generate.go +++ b/generate.go @@ -173,7 +173,7 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo }, Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, } - statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, src, errCheck) + statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, src, token.DEFINE, errCheck) if err != nil { return nil, nil, err } @@ -391,7 +391,7 @@ func contextAssignment() *ast.AssignStmt { } } -func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, str ast.Expr, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { +func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, str ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { @@ -436,7 +436,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -461,7 +461,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -486,7 +486,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -511,7 +511,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -550,7 +550,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -575,7 +575,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -600,7 +600,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -640,7 +640,7 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, + Tok: assignTok, Rhs: []ast.Expr{&ast.CallExpr{ Fun: ast.NewIdent(paramTypeIdent.Name), Args: []ast.Expr{ast.NewIdent(tmp)}, @@ -651,16 +651,9 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident case "string": assign := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent(requestPathValue), - }, - Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, - }}, + Tok: assignTok, + Rhs: []ast.Expr{str}, } - return []ast.Stmt{assign}, nil, nil } } From e9368d41a31996d9674c606d97acbc96ba62031e Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:47:54 -0700 Subject: [PATCH 12/26] refactor: move parse statement generator to new func --- generate.go | 466 ++++++++++++++++++++++++++-------------------------- 1 file changed, 235 insertions(+), 231 deletions(-) diff --git a/generate.go b/generate.go index cd2e94f..659e205 100644 --- a/generate.go +++ b/generate.go @@ -392,272 +392,276 @@ func contextAssignment() *ast.AssignStmt { } func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, str ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { - const parsedVarSuffix = "Parsed" for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { continue } - paramTypeIdent, ok := typeExp.(*ast.Ident) - if !ok { - return nil, nil, fmt.Errorf("unsupported type: %s", source.Format(typeExp)) + return parseStringStatements(arg.Name, ast.NewIdent(arg.Name), errVar, str, typeExp, assignTok, errCheck) + } + return nil, nil, fmt.Errorf("type for argumement %d not found", i) +} + +func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, typeExp ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { + const parsedVarSuffix = "Parsed" + paramTypeIdent, ok := typeExp.(*ast.Ident) + if !ok { + return nil, nil, fmt.Errorf("unsupported type: %s", source.Format(typeExp)) + } + base10 := source.Int(10) + switch paramTypeIdent.Name { + default: + return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) + case "bool": + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseBool"), + }, + Args: []ast.Expr{str}, + }}, } - base10 := source.Int(10) - switch paramTypeIdent.Name { - default: - return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) - case "bool": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseBool"), - }, - Args: []ast.Expr{str}, - }}, - } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "int": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "int": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("Atoi"), - }, - Args: []ast.Expr{str}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("Atoi"), + }, + Args: []ast.Expr{str}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "int16": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "int16": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseInt"), - }, - Args: []ast.Expr{str, base10, source.Int(16)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseInt"), + }, + Args: []ast.Expr{str, base10, source.Int(16)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "int32": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "int32": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseInt"), - }, - Args: []ast.Expr{str, base10, source.Int(32)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseInt"), + }, + Args: []ast.Expr{str, base10, source.Int(32)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "int8": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "int8": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseInt"), - }, - Args: []ast.Expr{str, base10, source.Int(8)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseInt"), + }, + Args: []ast.Expr{str, base10, source.Int(8)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "int64": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseInt"), - }, - Args: []ast.Expr{str, base10, source.Int(64)}, - }}, - } + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "int64": + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseInt"), + }, + Args: []ast.Expr{str, base10, source.Int(64)}, + }}, + } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "uint": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "uint": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseUint"), - }, - Args: []ast.Expr{str, base10, source.Int(64)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseUint"), + }, + Args: []ast.Expr{str, base10, source.Int(64)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "uint16": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "uint16": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseUint"), - }, - Args: []ast.Expr{str, base10, source.Int(16)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseUint"), + }, + Args: []ast.Expr{str, base10, source.Int(16)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "uint32": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "uint32": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseUint"), - }, - Args: []ast.Expr{str, base10, source.Int(32)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseUint"), + }, + Args: []ast.Expr{str, base10, source.Int(32)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "uint64": + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "uint64": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseUint"), - }, - Args: []ast.Expr{str, base10, source.Int(64)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseUint"), + }, + Args: []ast.Expr{str, base10, source.Int(64)}, + }}, + } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "uint8": - tmp := arg.Name + parsedVarSuffix + return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "uint8": + tmp := name + parsedVarSuffix - parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("strconv"), - Sel: ast.NewIdent("ParseUint"), - }, - Args: []ast.Expr{str, base10, source.Int(8)}, - }}, - } + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("strconv"), + Sel: ast.NewIdent("ParseUint"), + }, + Args: []ast.Expr{str, base10, source.Int(8)}, + }}, + } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }}, + } - return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil - case "string": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(arg.Name)}, - Tok: assignTok, - Rhs: []ast.Expr{str}, - } - return []ast.Stmt{assign}, nil, nil + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil + case "string": + assign := &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{str}, } + return []ast.Stmt{assign}, nil, nil } - return nil, nil, fmt.Errorf("type for argumement %d not found", i) } func paramParseError(errVar *ast.Ident) *ast.IfStmt { From 3861359ee24f4e36eb1eb69249bf025228cd87fb Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:38:57 -0700 Subject: [PATCH 13/26] refactor: make parsing strings more general --- generate.go | 75 +++++++++++++++++-------------------------- internal/source/go.go | 7 ++++ 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/generate.go b/generate.go index 659e205..88e9c43 100644 --- a/generate.go +++ b/generate.go @@ -164,8 +164,21 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo call.Args = append(call.Args, ast.NewIdent(TemplateNameScopeIdentifierContext)) imports = append(imports, importSpec("context")) default: - errVar := ast.NewIdent("err") - errCheck := paramParseError(errVar) + const errVarIdent = "err" + errCheck := source.ErrorCheckReturn(errVarIdent, &ast.ExprStmt{X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(httpPackageIdent), + Sel: ast.NewIdent("Error"), + }, + Args: []ast.Expr{ + ast.NewIdent(httpResponseField().Names[0].Name), + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: ast.NewIdent("err"), Sel: ast.NewIdent("Error")}, + Args: []ast.Expr{}, + }, + source.HTTPStatusCode(httpPackageIdent, http.StatusBadRequest), + }, + }}, &ast.ReturnStmt{}) src := &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), @@ -173,7 +186,7 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo }, Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(arg.Name)}}, } - statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVar, src, token.DEFINE, errCheck) + statements, parseImports, err := httpPathValueAssignment(method, i, arg, errVarIdent, src, token.DEFINE, errCheck) if err != nil { return nil, nil, err } @@ -391,17 +404,17 @@ func contextAssignment() *ast.AssignStmt { } } -func httpPathValueAssignment(method *ast.FuncType, i int, arg, errVar *ast.Ident, str ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { +func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident, errVarIdent string, str ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { continue } - return parseStringStatements(arg.Name, ast.NewIdent(arg.Name), errVar, str, typeExp, assignTok, errCheck) + return parseStringStatements(arg.Name, ast.NewIdent(arg.Name), errVarIdent, str, typeExp, assignTok, errCheck) } return nil, nil, fmt.Errorf("type for argumement %d not found", i) } -func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, typeExp ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { +func parseStringStatements(name string, result ast.Expr, errVarIdent string, str, typeExp ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" paramTypeIdent, ok := typeExp.(*ast.Ident) if !ok { @@ -413,7 +426,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) case "bool": assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -429,7 +442,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -454,7 +467,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -479,7 +492,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -504,7 +517,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -527,7 +540,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int64": assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -543,7 +556,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -568,7 +581,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -593,7 +606,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -617,7 +630,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, case "uint64": assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -633,7 +646,7 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, tmp := name + parsedVarSuffix parse := &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVar.Name)}, + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -664,34 +677,6 @@ func parseStringStatements(name string, result ast.Expr, errVar *ast.Ident, str, } } -func paramParseError(errVar *ast.Ident) *ast.IfStmt { - return &ast.IfStmt{ - Cond: &ast.BinaryExpr{X: ast.NewIdent(errVar.Name), Op: token.NEQ, Y: ast.NewIdent("nil")}, - Body: &ast.BlockStmt{ - List: []ast.Stmt{ - &ast.ExprStmt{X: &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(httpPackageIdent), - Sel: ast.NewIdent("Error"), - }, - Args: []ast.Expr{ - ast.NewIdent(httpResponseField().Names[0].Name), - &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent("err"), - Sel: ast.NewIdent("Error"), - }, - Args: []ast.Expr{}, - }, - source.HTTPStatusCode(httpPackageIdent, http.StatusBadRequest), - }, - }}, - &ast.ReturnStmt{}, - }, - }, - } -} - func (def TemplateName) executeCall(status, data ast.Expr, writeHeader bool) *ast.ExprStmt { return &ast.ExprStmt{X: &ast.CallExpr{ Fun: ast.NewIdent(executeIdentName), diff --git a/internal/source/go.go b/internal/source/go.go index 9bb9b56..80a8ef7 100644 --- a/internal/source/go.go +++ b/internal/source/go.go @@ -224,3 +224,10 @@ func HTTPStatusCode(pkg string, n int) ast.Expr { } func Int(n int) *ast.BasicLit { return &ast.BasicLit{Value: strconv.Itoa(n), Kind: token.INT} } + +func ErrorCheckReturn(errVarIdent string, body ...ast.Stmt) *ast.IfStmt { + return &ast.IfStmt{ + Cond: &ast.BinaryExpr{X: ast.NewIdent(errVarIdent), Op: token.NEQ, Y: ast.NewIdent("nil")}, + Body: &ast.BlockStmt{List: body}, + } +} From be77fa1cb4ff4d1dcfef4d4cdd8b76d04424d8ba Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:50:01 -0700 Subject: [PATCH 14/26] add form declaration to func literal --- generate.go | 18 +++++++++++++++++ generate_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++ internal/source/go.go | 14 ++++++++++++++ name.go | 2 ++ 4 files changed, 79 insertions(+) diff --git a/generate.go b/generate.go index 88e9c43..5030387 100644 --- a/generate.go +++ b/generate.go @@ -163,6 +163,10 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo lit.Body.List = append(lit.Body.List, contextAssignment()) call.Args = append(call.Args, ast.NewIdent(TemplateNameScopeIdentifierContext)) imports = append(imports, importSpec("context")) + case TemplateNameScopeIdentifierForm: + _, tp, _ := source.FieldIndex(method.Params.List, i) + lit.Body.List = append(lit.Body.List, formDeclaration(arg.Name, tp)) + call.Args = append(call.Args, ast.NewIdent(arg.Name)) default: const errVarIdent = "err" errCheck := source.ErrorCheckReturn(errVarIdent, &ast.ExprStmt{X: &ast.CallExpr{ @@ -404,6 +408,20 @@ func contextAssignment() *ast.AssignStmt { } } +func formDeclaration(ident string, typeExp ast.Expr) *ast.DeclStmt { + return &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ast.NewIdent(ident)}, + Type: typeExp, + }, + }, + }, + } +} + func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident, errVarIdent string, str ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { for typeIndex, typeExp := range source.IterateFieldTypes(method.Params.List) { if typeIndex != i { diff --git a/generate_test.go b/generate_test.go index 415830a..d8b902c 100644 --- a/generate_test.go +++ b/generate_test.go @@ -561,6 +561,51 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo } _, _ = buf.WriteTo(response) } +`, + }, + { + Name: "form has no fields", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type T struct{} + +type In struct{} + +func (T) F(form In) any { return nil } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F(form In) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + var form In + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} `, }, } { diff --git a/internal/source/go.go b/internal/source/go.go index 80a8ef7..ef78702 100644 --- a/internal/source/go.go +++ b/internal/source/go.go @@ -231,3 +231,17 @@ func ErrorCheckReturn(errVarIdent string, body ...ast.Stmt) *ast.IfStmt { Body: &ast.BlockStmt{List: body}, } } + +func FieldIndex(fields []*ast.Field, i int) (*ast.Ident, ast.Expr, bool) { + n := 0 + for _, field := range fields { + for _, name := range field.Names { + if n != i { + n++ + continue + } + return name, field.Type, true + } + } + return nil, nil, false +} diff --git a/name.go b/name.go index 90bfbe0..a92d9b9 100644 --- a/name.go +++ b/name.go @@ -173,6 +173,7 @@ const ( TemplateNameScopeIdentifierHTTPRequest = "request" TemplateNameScopeIdentifierHTTPResponse = "response" TemplateNameScopeIdentifierContext = "ctx" + TemplateNameScopeIdentifierForm = "form" ) func patternScope() []string { @@ -180,5 +181,6 @@ func patternScope() []string { TemplateNameScopeIdentifierHTTPRequest, TemplateNameScopeIdentifierHTTPResponse, TemplateNameScopeIdentifierContext, + TemplateNameScopeIdentifierForm, } } From 141f401ad22b577d20a98968b176bdb9617a7a4a Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:22:42 -0700 Subject: [PATCH 15/26] pass add form arg from request.Form --- generate.go | 51 ++++++++++++++++++++++++++++++++++++--- generate_internal_test.go | 6 +++-- generate_test.go | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/generate.go b/generate.go index 5030387..2cc79f4 100644 --- a/generate.go +++ b/generate.go @@ -59,7 +59,10 @@ func Generate(templateNames []TemplateName, _ *template.Template, packageName, t importSpec("net/" + httpPackageIdent), } for _, name := range templateNames { - var method *ast.FuncType + var ( + method *ast.FuncType + form *ast.StructType + ) if name.fun != nil { for _, funcDecl := range source.IterateFunctions(receiverPackage) { if !name.matchReceiver(funcDecl, receiverTypeIdent) { @@ -78,7 +81,7 @@ func Generate(templateNames []TemplateName, _ *template.Template, packageName, t Type: method, }) } - handlerFunc, methodImports, err := name.funcLit(method) + handlerFunc, methodImports, err := name.funcLit(method, form) if err != nil { return "", err } @@ -127,7 +130,7 @@ func (def TemplateName) callHandleFunc(handlerFuncLit *ast.FuncLit) *ast.ExprStm }} } -func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.ImportSpec, error) { +func (def TemplateName) funcLit(method *ast.FuncType, _ *ast.StructType) (*ast.FuncLit, []*ast.ImportSpec, error) { if def.handler == "" { return def.httpRequestReceiverTemplateHandlerFunc(), nil, nil } @@ -165,7 +168,16 @@ func (def TemplateName) funcLit(method *ast.FuncType) (*ast.FuncLit, []*ast.Impo imports = append(imports, importSpec("context")) case TemplateNameScopeIdentifierForm: _, tp, _ := source.FieldIndex(method.Params.List, i) - lit.Body.List = append(lit.Body.List, formDeclaration(arg.Name, tp)) + lit.Body.List = append(lit.Body.List, + &ast.ExprStmt{X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent("ParseForm"), + }, + Args: []ast.Expr{}, + }}, + formDeclaration(arg.Name, tp)) + imports = append(imports, importSpec("net/url")) call.Args = append(call.Args, ast.NewIdent(arg.Name)) default: const errVarIdent = "err" @@ -257,6 +269,8 @@ func (def TemplateName) funcType() (*ast.FuncType, []*ast.ImportSpec) { case TemplateNameScopeIdentifierContext: method.Params.List = append(method.Params.List, contextContextField()) imports = append(imports, importSpec(contextPackageIdent)) + case TemplateNameScopeIdentifierForm: + method.Params.List = append(method.Params.List, urlValuesField(arg.Name)) default: method.Params.List = append(method.Params.List, pathValueField(arg.Name)) } @@ -312,6 +326,11 @@ func checkArgument(method *ast.FuncType, argIndex int, exp ast.Expr, argType ast return fmt.Errorf("method expects type %s but %s is %s.%s", source.Format(argType), arg.Name, contextPackageIdent, contextContextTypeIdent) } return nil + case TemplateNameScopeIdentifierForm: + if !matchSelectorIdents(argType, "url", "Values", false) { + return fmt.Errorf("method expects type %s but %s is %s.%s", source.Format(argType), arg.Name, "url", "Values") + } + return nil default: for paramIndex, paramType := range source.IterateFieldTypes(method.Params.List) { if argIndex != paramIndex { @@ -376,6 +395,13 @@ func routesFuncType(receiverType ast.Expr) *ast.FuncType { }} } +func urlValuesField(ident string) *ast.Field { + return &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(ident)}, + Type: &ast.SelectorExpr{X: ast.NewIdent("url"), Sel: ast.NewIdent("Values")}, + } +} + func httpRequestField() *ast.Field { return &ast.Field{ Names: []*ast.Ident{ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest)}, @@ -409,6 +435,23 @@ func contextAssignment() *ast.AssignStmt { } func formDeclaration(ident string, typeExp ast.Expr) *ast.DeclStmt { + if matchSelectorIdents(typeExp, "url", "Values", false) { + return &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ast.NewIdent(ident)}, + Type: typeExp, + Values: []ast.Expr{ + &ast.SelectorExpr{X: ast.NewIdent(httpResponseField().Names[0].Name), Sel: ast.NewIdent("Form")}, + }, + }, + }, + }, + } + } + return &ast.DeclStmt{ Decl: &ast.GenDecl{ Tok: token.VAR, diff --git a/generate_internal_test.go b/generate_internal_test.go index 4c32560..afc09ad 100644 --- a/generate_internal_test.go +++ b/generate_internal_test.go @@ -17,6 +17,7 @@ func TestTemplateName_funcLit(t *testing.T) { Out string Imports []string Method *ast.FuncType + Form *ast.StructType }{ { Name: "get", @@ -105,7 +106,7 @@ func TestTemplateName_funcLit(t *testing.T) { pat, err, ok := NewTemplateName(tt.In) require.True(t, ok) require.NoError(t, err) - out, _, err := pat.funcLit(tt.Method) + out, _, err := pat.funcLit(tt.Method, tt.Form) require.NoError(t, err) assert.Equal(t, tt.Out, source.Format(out)) }) @@ -118,6 +119,7 @@ func TestTemplateName_HandlerFuncLit_err(t *testing.T) { In string ErrSub string Method *ast.FuncType + Form *ast.StructType }{ { Name: "missing arguments", @@ -211,7 +213,7 @@ func TestTemplateName_HandlerFuncLit_err(t *testing.T) { pat, err, ok := NewTemplateName(tt.In) require.True(t, ok) require.NoError(t, err) - _, _, err = pat.funcLit(tt.Method) + _, _, err = pat.funcLit(tt.Method, tt.Form) assert.ErrorContains(t, err, tt.ErrSub) }) } diff --git a/generate_test.go b/generate_test.go index d8b902c..b388484 100644 --- a/generate_test.go +++ b/generate_test.go @@ -606,6 +606,49 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo } _, _ = buf.WriteTo(response) } +`, + }, + { + Name: "F is not defined and a form field is passed", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type T struct{} +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "net/url" + "bytes" +) + +type RoutesReceiver interface { + F(form url.Values) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form url.Values = response.Form + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} `, }, } { From 0489c1dcd4408b987b1842b8ec90084766b87bb6 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:11:59 -0700 Subject: [PATCH 16/26] add field parsing from tag --- generate.go | 90 ++++++++++++++++++++++++++++----- generate_internal_test.go | 6 +-- generate_test.go | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 15 deletions(-) diff --git a/generate.go b/generate.go index 2cc79f4..107e4a0 100644 --- a/generate.go +++ b/generate.go @@ -8,6 +8,7 @@ import ( "html/template" "log" "net/http" + "reflect" "slices" "strconv" "strings" @@ -59,10 +60,7 @@ func Generate(templateNames []TemplateName, _ *template.Template, packageName, t importSpec("net/" + httpPackageIdent), } for _, name := range templateNames { - var ( - method *ast.FuncType - form *ast.StructType - ) + var method *ast.FuncType if name.fun != nil { for _, funcDecl := range source.IterateFunctions(receiverPackage) { if !name.matchReceiver(funcDecl, receiverTypeIdent) { @@ -81,7 +79,7 @@ func Generate(templateNames []TemplateName, _ *template.Template, packageName, t Type: method, }) } - handlerFunc, methodImports, err := name.funcLit(method, form) + handlerFunc, methodImports, err := name.funcLit(method, receiverPackage) if err != nil { return "", err } @@ -130,7 +128,7 @@ func (def TemplateName) callHandleFunc(handlerFuncLit *ast.FuncLit) *ast.ExprStm }} } -func (def TemplateName) funcLit(method *ast.FuncType, _ *ast.StructType) (*ast.FuncLit, []*ast.ImportSpec, error) { +func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.FuncLit, []*ast.ImportSpec, error) { if def.handler == "" { return def.httpRequestReceiverTemplateHandlerFunc(), nil, nil } @@ -139,14 +137,18 @@ func (def TemplateName) funcLit(method *ast.FuncType, _ *ast.StructType) (*ast.F Body: &ast.BlockStmt{}, } call := &ast.CallExpr{Fun: callReceiverMethod(def.fun)} + var formStruct *ast.StructType if method != nil { if method.Params.NumFields() != len(def.call.Args) { return nil, nil, errWrongNumberOfArguments(def, method) } for pi, pt := range fieldListTypes(method.Params) { - if err := checkArgument(method, pi, def.call.Args[pi], pt); err != nil { + if err := checkArgument(method, pi, def.call.Args[pi], pt, files); err != nil { return nil, nil, err } + if s, ok := findFormStruct(pt, files); ok { + formStruct = s + } } } var ( @@ -177,7 +179,48 @@ func (def TemplateName) funcLit(method *ast.FuncType, _ *ast.StructType) (*ast.F Args: []ast.Expr{}, }}, formDeclaration(arg.Name, tp)) - imports = append(imports, importSpec("net/url")) + if formStruct != nil { + for _, field := range formStruct.Fields.List { + var inputNameTag string + if field.Tag != nil { + v, _ := strconv.Unquote(field.Tag.Value) + tags := reflect.StructTag(v) + n, hasInputTag := tags.Lookup("input") + if hasInputTag { + inputNameTag = n + } + } + + for _, name := range field.Names { + inputName := cmp.Or(inputNameTag, name.Name) + lit.Body.List = append(lit.Body.List, &ast.AssignStmt{ + Tok: token.ASSIGN, + Lhs: []ast.Expr{ + &ast.SelectorExpr{ + X: ast.NewIdent(arg.Name), + Sel: ast.NewIdent(name.Name), + }, + }, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent("FormValue"), + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(inputName), + }, + }, + }, + }, + }) + } + } + } else { + imports = append(imports, importSpec("net/url")) + } call.Args = append(call.Args, ast.NewIdent(arg.Name)) default: const errVarIdent = "err" @@ -307,7 +350,7 @@ func errWrongNumberOfArguments(def TemplateName, method *ast.FuncType) error { return fmt.Errorf("handler %s expects %d arguments but call %s has %d", source.Format(&ast.FuncDecl{Name: ast.NewIdent(def.fun.Name), Type: method}), method.Params.NumFields(), def.handler, len(def.call.Args)) } -func checkArgument(method *ast.FuncType, argIndex int, exp ast.Expr, argType ast.Expr) error { +func checkArgument(method *ast.FuncType, argIndex int, exp ast.Expr, argType ast.Expr, files []*ast.File) error { // TODO: rewrite to "cannot use 32 (untyped int constant) as string value in argument to strings.ToUpper" arg := exp.(*ast.Ident) switch arg.Name { @@ -327,8 +370,12 @@ func checkArgument(method *ast.FuncType, argIndex int, exp ast.Expr, argType ast } return nil case TemplateNameScopeIdentifierForm: - if !matchSelectorIdents(argType, "url", "Values", false) { - return fmt.Errorf("method expects type %s but %s is %s.%s", source.Format(argType), arg.Name, "url", "Values") + if matchSelectorIdents(argType, "url", "Values", false) { + return nil + } + _, ok := findFormStruct(argType, files) + if !ok { + return fmt.Errorf("method expects form to have type url.Values or T (where T is some struct type)") } return nil default: @@ -347,6 +394,27 @@ func checkArgument(method *ast.FuncType, argIndex int, exp ast.Expr, argType ast } } +func findFormStruct(argType ast.Expr, files []*ast.File) (*ast.StructType, bool) { + if argTypeIdent, ok := argType.(*ast.Ident); ok { + for _, file := range files { + for _, d := range file.Decls { + decl, ok := d.(*ast.GenDecl) + if !ok || decl.Tok != token.TYPE { + continue + } + for _, s := range decl.Specs { + spec := s.(*ast.TypeSpec) + structType, isStruct := spec.Type.(*ast.StructType) + if isStruct && spec.Name.Name == argTypeIdent.Name { + return structType, true + } + } + } + } + } + return nil, false +} + func matchSelectorIdents(expr ast.Expr, pkg, name string, star bool) bool { if star { st, ok := expr.(*ast.StarExpr) diff --git a/generate_internal_test.go b/generate_internal_test.go index afc09ad..ecf02e1 100644 --- a/generate_internal_test.go +++ b/generate_internal_test.go @@ -17,7 +17,6 @@ func TestTemplateName_funcLit(t *testing.T) { Out string Imports []string Method *ast.FuncType - Form *ast.StructType }{ { Name: "get", @@ -106,7 +105,7 @@ func TestTemplateName_funcLit(t *testing.T) { pat, err, ok := NewTemplateName(tt.In) require.True(t, ok) require.NoError(t, err) - out, _, err := pat.funcLit(tt.Method, tt.Form) + out, _, err := pat.funcLit(tt.Method, nil) require.NoError(t, err) assert.Equal(t, tt.Out, source.Format(out)) }) @@ -119,7 +118,6 @@ func TestTemplateName_HandlerFuncLit_err(t *testing.T) { In string ErrSub string Method *ast.FuncType - Form *ast.StructType }{ { Name: "missing arguments", @@ -213,7 +211,7 @@ func TestTemplateName_HandlerFuncLit_err(t *testing.T) { pat, err, ok := NewTemplateName(tt.In) require.True(t, ok) require.NoError(t, err) - _, _, err = pat.funcLit(tt.Method, tt.Form) + _, _, err = pat.funcLit(tt.Method, nil) assert.ErrorContains(t, err, tt.ErrSub) }) } diff --git a/generate_test.go b/generate_test.go index b388484..07f4b28 100644 --- a/generate_test.go +++ b/generate_test.go @@ -590,6 +590,7 @@ type RoutesReceiver interface { func routes(mux *http.ServeMux, receiver RoutesReceiver) { mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() var form In data := receiver.F(form) execute(response, request, true, "GET / F(form)", http.StatusOK, data) @@ -649,6 +650,106 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo } _, _ = buf.WriteTo(response) } +`, + }, + { + Name: "F is defined and form type is a struct", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + field string + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + form.field = request.FormValue("field") + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} +`, + }, + { + Name: "F is defined and form field has an input tag", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + field string ` + "`input:\"some-field\"`" + ` + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + form.field = request.FormValue("some-field") + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} `, }, } { From d0039139e93885e398a919562d2d0690337abec0 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:15:23 -0700 Subject: [PATCH 17/26] refactor: add helper function to get field name --- generate.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/generate.go b/generate.go index 107e4a0..214bbff 100644 --- a/generate.go +++ b/generate.go @@ -181,18 +181,7 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F formDeclaration(arg.Name, tp)) if formStruct != nil { for _, field := range formStruct.Fields.List { - var inputNameTag string - if field.Tag != nil { - v, _ := strconv.Unquote(field.Tag.Value) - tags := reflect.StructTag(v) - n, hasInputTag := tags.Lookup("input") - if hasInputTag { - inputNameTag = n - } - } - for _, name := range field.Names { - inputName := cmp.Or(inputNameTag, name.Name) lit.Body.List = append(lit.Body.List, &ast.AssignStmt{ Tok: token.ASSIGN, Lhs: []ast.Expr{ @@ -210,7 +199,7 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F Args: []ast.Expr{ &ast.BasicLit{ Kind: token.STRING, - Value: strconv.Quote(inputName), + Value: strconv.Quote(formInputName(field, name)), }, }, }, @@ -294,6 +283,18 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F return lit, imports, nil } +func formInputName(field *ast.Field, name *ast.Ident) string { + if field.Tag != nil { + v, _ := strconv.Unquote(field.Tag.Value) + tags := reflect.StructTag(v) + n, hasInputTag := tags.Lookup("input") + if hasInputTag { + return n + } + } + return name.Name +} + func (def TemplateName) funcType() (*ast.FuncType, []*ast.ImportSpec) { method := &ast.FuncType{ Params: &ast.FieldList{}, From 8ead025d1811a0b9976dc413e0ac25524591d597 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:48:04 -0700 Subject: [PATCH 18/26] add test for tag parsing --- generate_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/generate_test.go b/generate_test.go index 07f4b28..6892c1e 100644 --- a/generate_test.go +++ b/generate_test.go @@ -730,6 +730,56 @@ type RoutesReceiver interface { F(form In) int } +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + form.field = request.FormValue("some-field") + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} +`, + }, + { + Name: "F is defined and form field has an input tag", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + field string ` + "`input:\"some-field\"`" + ` + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F(form In) int +} + func routes(mux *http.ServeMux, receiver RoutesReceiver) { mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { request.ParseForm() From 1802f2b96505db04ed9c8ff6b0b6ffb97e586789 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:01:08 -0700 Subject: [PATCH 19/26] parse forms fields with non-string basic types --- generate.go | 53 ++++++++------ generate_test.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 20 deletions(-) diff --git a/generate.go b/generate.go index 214bbff..cafc936 100644 --- a/generate.go +++ b/generate.go @@ -151,6 +151,7 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F } } } + const errVarIdent = "err" var ( imports []*ast.ImportSpec writeHeader = true @@ -182,29 +183,42 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F if formStruct != nil { for _, field := range formStruct.Fields.List { for _, name := range field.Names { - lit.Body.List = append(lit.Body.List, &ast.AssignStmt{ - Tok: token.ASSIGN, - Lhs: []ast.Expr{ - &ast.SelectorExpr{ - X: ast.NewIdent(arg.Name), - Sel: ast.NewIdent(name.Name), - }, + fieldExpr := &ast.SelectorExpr{ + X: ast.NewIdent(arg.Name), + Sel: ast.NewIdent(name.Name), + } + errCheck := source.ErrorCheckReturn(errVarIdent, &ast.ExprStmt{X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(httpPackageIdent), + Sel: ast.NewIdent("Error"), }, - Rhs: []ast.Expr{ + Args: []ast.Expr{ + ast.NewIdent(httpResponseField().Names[0].Name), &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent("FormValue"), - }, - Args: []ast.Expr{ - &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(formInputName(field, name)), - }, - }, + Fun: &ast.SelectorExpr{X: ast.NewIdent("err"), Sel: ast.NewIdent("Error")}, + Args: []ast.Expr{}, + }, + source.HTTPStatusCode(httpPackageIdent, http.StatusBadRequest), + }, + }}, &ast.ReturnStmt{}) + + statements, parseImports, err := parseStringStatements(name.Name, fieldExpr, errVarIdent, &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent("FormValue"), + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(formInputName(field, name)), }, }, - }) + }, field.Type, token.ASSIGN, errCheck) + if err != nil { + return nil, nil, err + } + lit.Body.List = append(lit.Body.List, statements...) + imports = append(imports, parseImports...) } } } else { @@ -212,7 +226,6 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F } call.Args = append(call.Args, ast.NewIdent(arg.Name)) default: - const errVarIdent = "err" errCheck := source.ErrorCheckReturn(errVarIdent, &ast.ExprStmt{X: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: ast.NewIdent(httpPackageIdent), diff --git a/generate_test.go b/generate_test.go index 6892c1e..f52f25e 100644 --- a/generate_test.go +++ b/generate_test.go @@ -800,6 +800,187 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo } _, _ = buf.WriteTo(response) } +`, + }, + { + Name: "F is defined and form has two string fields", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + fieldInt int + fieldInt64 int64 + fieldInt32 int32 + fieldInt16 int16 + fieldInt8 int8 + fieldUint uint + fieldUint64 uint64 + fieldUint16 uint16 + fieldUint32 uint32 + fieldUint16 uint16 + fieldUint8 uint8 + fieldBool bool + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "strconv" + "bytes" +) + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + fieldIntParsed, err := strconv.Atoi(request.FormValue("fieldInt")) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt = int(fieldIntParsed) + form.fieldInt64, err := strconv.ParseInt(request.FormValue("fieldInt64"), 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + fieldInt32Parsed, err := strconv.ParseInt(request.FormValue("fieldInt32"), 10, 32) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt32 = int32(fieldInt32Parsed) + fieldInt16Parsed, err := strconv.ParseInt(request.FormValue("fieldInt16"), 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt16 = int16(fieldInt16Parsed) + fieldInt8Parsed, err := strconv.ParseInt(request.FormValue("fieldInt8"), 10, 8) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt8 = int8(fieldInt8Parsed) + fieldUintParsed, err := strconv.ParseUint(request.FormValue("fieldUint"), 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint = uint(fieldUintParsed) + form.fieldUint64, err := strconv.ParseUint(request.FormValue("fieldUint64"), 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + fieldUint16Parsed, err := strconv.ParseUint(request.FormValue("fieldUint16"), 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint16 = uint16(fieldUint16Parsed) + fieldUint32Parsed, err := strconv.ParseUint(request.FormValue("fieldUint32"), 10, 32) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint32 = uint32(fieldUint32Parsed) + fieldUint16Parsed, err := strconv.ParseUint(request.FormValue("fieldUint16"), 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint16 = uint16(fieldUint16Parsed) + fieldUint8Parsed, err := strconv.ParseUint(request.FormValue("fieldUint8"), 10, 8) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint8 = uint8(fieldUint8Parsed) + form.fieldBool, err := strconv.ParseBool(request.FormValue("fieldBool")) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} +`, + }, + { + Name: "F is defined and form has two string fields", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + field1, field2 string + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + form.field1 = request.FormValue("field1") + form.field2 = request.FormValue("field2") + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} `, }, } { From 3b7a4909822d8f8c88d0143b6444d4fb2933fcdb Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:01:43 -0700 Subject: [PATCH 20/26] add error message for unsupported type --- generate.go | 2 +- generate_test.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/generate.go b/generate.go index cafc936..8980da2 100644 --- a/generate.go +++ b/generate.go @@ -215,7 +215,7 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F }, }, field.Type, token.ASSIGN, errCheck) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to generate parse statements for form field %s: %w", name.Name, err) } lit.Body.List = append(lit.Body.List, statements...) imports = append(imports, parseImports...) diff --git a/generate_test.go b/generate_test.go index f52f25e..8dbeabb 100644 --- a/generate_test.go +++ b/generate_test.go @@ -983,6 +983,25 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo } `, }, + { + Name: "F is defined and form has unsupported field type", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + ts time.Time + } +) + +func (T) F(form In) int { return 0 } +`, + Receiver: "T", + ExpectedError: "failed to generate parse statements for form field ts: unsupported type: time.Time", + }, } { t.Run(tt.Name, func(t *testing.T) { ts := template.Must(template.New(tt.Name).Parse(tt.Templates)) From 87c0586e223ae8247fc86c18af720c66ec6dad6d Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:52:50 -0700 Subject: [PATCH 21/26] consolidate happy path tests into higher level --- generate_internal_test.go | 104 ------------------ generate_test.go | 216 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 104 deletions(-) diff --git a/generate_internal_test.go b/generate_internal_test.go index ecf02e1..2c663da 100644 --- a/generate_internal_test.go +++ b/generate_internal_test.go @@ -6,112 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/crhntr/muxt/internal/source" ) -func TestTemplateName_funcLit(t *testing.T) { - for _, tt := range []struct { - Name string - In string - Out string - Imports []string - Method *ast.FuncType - }{ - { - Name: "get", - In: "GET /", - Out: `func(response http.ResponseWriter, request *http.Request) { - execute(response, request, true, "GET /", http.StatusOK, request) -}`, - }, - { - Name: "call F", - In: "GET / F()", - Out: `func(response http.ResponseWriter, request *http.Request) { - data := receiver.F() - execute(response, request, true, "GET / F()", http.StatusOK, data) -}`, - }, - { - Name: "call F with argument request", - In: "GET / F(request)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Type: httpRequestField().Type}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - Out: `func(response http.ResponseWriter, request *http.Request) { - data := receiver.F(request) - execute(response, request, true, "GET / F(request)", http.StatusOK, data) -}`, - }, - { - Name: "call F with argument response", - In: "GET / F(response)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Type: httpResponseField().Type, Names: []*ast.Ident{{Name: "res"}}}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - Out: `func(response http.ResponseWriter, request *http.Request) { - data := receiver.F(response) - execute(response, request, false, "GET / F(response)", http.StatusOK, data) -}`, - }, - { - Name: "call F with argument context", - In: "GET / F(ctx)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Type: contextContextField().Type, Names: []*ast.Ident{{Name: "reqCtx"}}}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - Out: `func(response http.ResponseWriter, request *http.Request) { - ctx := request.Context() - data := receiver.F(ctx) - execute(response, request, true, "GET / F(ctx)", http.StatusOK, data) -}`, - }, - { - Name: "call F with argument path param", - In: "GET /{param} F(param)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("string")}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - Out: `func(response http.ResponseWriter, request *http.Request) { - param := request.PathValue("param") - data := receiver.F(param) - execute(response, request, true, "GET /{param} F(param)", http.StatusOK, data) -}`, - }, - { - Name: "call F with multiple arguments", - In: "GET /{userName} F(ctx, userName)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: contextContextField().Type, Names: []*ast.Ident{{Name: "ctx"}}}, - {Type: ast.NewIdent("string"), Names: []*ast.Ident{{Name: "n"}}}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - Out: `func(response http.ResponseWriter, request *http.Request) { - ctx := request.Context() - userName := request.PathValue("userName") - data := receiver.F(ctx, userName) - execute(response, request, true, "GET /{userName} F(ctx, userName)", http.StatusOK, data) -}`, - }, - } { - t.Run(tt.Name, func(t *testing.T) { - pat, err, ok := NewTemplateName(tt.In) - require.True(t, ok) - require.NoError(t, err) - out, _, err := pat.funcLit(tt.Method, nil) - require.NoError(t, err) - assert.Equal(t, tt.Out, source.Format(out)) - }) - } -} - func TestTemplateName_HandlerFuncLit_err(t *testing.T) { for _, tt := range []struct { Name string diff --git a/generate_test.go b/generate_test.go index 8dbeabb..613cef4 100644 --- a/generate_test.go +++ b/generate_test.go @@ -1002,6 +1002,222 @@ func (T) F(form In) int { return 0 } Receiver: "T", ExpectedError: "failed to generate parse statements for form field ts: unsupported type: time.Time", }, + { + Name: "call F", + Templates: `{{define "GET / F()"}}Hello, world!{{end}}`, + Receiver: "T", + PackageName: "main", + ReceiverPackage: `-- in.go -- +package main + +type T struct{} +`, + ExpectedFile: `package main + +import ( + "net/http" + "bytes" +) + +type RoutesReceiver interface { + F() any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + data := receiver.F() + execute(response, request, true, "GET / F()", http.StatusOK, data) + }) +} +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { + buf := bytes.NewBuffer(nil) + if err := templates.ExecuteTemplate(buf, name, data); err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if writeHeader { + response.WriteHeader(code) + } + _, _ = buf.WriteTo(response) +} +`, + }, + { + Name: "no handler", + Templates: `{{define "GET /"}}Hello, world!{{end}}`, + Receiver: "T", + PackageName: "main", + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import "net/http" + +type RoutesReceiver interface { +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + execute(response, request, true, "GET /", http.StatusOK, request) + }) +} +`, + }, + { + Name: "no handler", + Templates: `{{define "GET /"}}Hello, world!{{end}}`, + Receiver: "T", + PackageName: "main", + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import "net/http" + +type RoutesReceiver interface { +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + execute(response, request, true, "GET /", http.StatusOK, request) + }) +} +`, + }, + { + Name: "call F with argument response", + Templates: `{{define "GET / F(response)"}}{{end}}`, + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func (T) F(http.ResponseWriter) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import "net/http" + +type RoutesReceiver interface { + F(response http.ResponseWriter) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + data := receiver.F(response) + execute(response, request, false, "GET / F(response)", http.StatusOK, data) + }) +} +`, + }, + { + Name: "call F with argument context", + Templates: `{{define "GET / F(ctx)"}}{{end}}`, + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func (T) F(ctx context.Context) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import ( + "context" + "net/http" +) + +type RoutesReceiver interface { + F(ctx context.Context) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + ctx := request.Context() + data := receiver.F(ctx) + execute(response, request, true, "GET / F(ctx)", http.StatusOK, data) + }) +} +`, + }, + { + Name: "call F with argument path param", + Templates: `{{define "GET /{param} F(param)"}}{{end}}`, + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func (T) F(param string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import "net/http" + +type RoutesReceiver interface { + F(param string) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /{param}", func(response http.ResponseWriter, request *http.Request) { + param := request.PathValue("param") + data := receiver.F(param) + execute(response, request, true, "GET /{param} F(param)", http.StatusOK, data) + }) +} +`, + }, + { + Name: "call F with multiple arguments", + Templates: `{{define "GET /{userName} F(ctx, userName)"}}{{end}}`, + ReceiverPackage: `-- in.go -- +package main + +import "context" + +type T struct{} + +func (T) F(ctx context.Context, userName string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedFile: `package main + +import ( + "context" + "net/http" +) + +type RoutesReceiver interface { + F(ctx context.Context, userName string) any +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /{userName}", func(response http.ResponseWriter, request *http.Request) { + ctx := request.Context() + userName := request.PathValue("userName") + data := receiver.F(ctx, userName) + execute(response, request, true, "GET /{userName} F(ctx, userName)", http.StatusOK, data) + }) +} +`, + }, } { t.Run(tt.Name, func(t *testing.T) { ts := template.Must(template.New(tt.Name).Parse(tt.Templates)) From 2015f94b8ba48f7eda3528a178f0c4e038ad8c55 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:42:39 -0700 Subject: [PATCH 22/26] test: consolidate error tests --- generate_internal_test.go | 114 ----------------------------- generate_test.go | 146 +++++++++++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 115 deletions(-) delete mode 100644 generate_internal_test.go diff --git a/generate_internal_test.go b/generate_internal_test.go deleted file mode 100644 index 2c663da..0000000 --- a/generate_internal_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package muxt - -import ( - "go/ast" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestTemplateName_HandlerFuncLit_err(t *testing.T) { - for _, tt := range []struct { - Name string - In string - ErrSub string - Method *ast.FuncType - }{ - { - Name: "missing arguments", - In: "GET / F()", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("string")}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "handler func F(string) any expects 1 arguments but call F() has 0", - }, - { - Name: "extra arguments", - In: "GET /{name} F(ctx, name)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: &ast.SelectorExpr{X: ast.NewIdent(contextPackageIdent), Sel: ast.NewIdent(contextContextTypeIdent)}}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "handler func F(context.Context) any expects 1 arguments but call F(ctx, name) has 2", - }, - { - Name: "wrong argument type request", - In: "GET / F(request)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: ast.NewIdent("string")}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method expects type string but request is *http.Request", - }, - { - Name: "wrong argument type ctx", - In: "GET / F(ctx)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: ast.NewIdent("string")}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method expects type string but ctx is context.Context", - }, - { - Name: "wrong argument type response", - In: "GET / F(response)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: ast.NewIdent("string")}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method expects type string but response is http.ResponseWriter", - }, - { - Name: "wrong argument type path value", - In: "GET /{name} F(name)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: ast.NewIdent("float64")}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method param type float64 not supported", - }, - { - Name: "wrong argument type request ptr", - In: "GET / F(request)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: &ast.StarExpr{X: ast.NewIdent("T")}}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method expects type *T but request is *http.Request", - }, - { - Name: "wrong argument type in field list", - In: "GET /post/{postID}/comment/{commentID} F(ctx, request, commentID)", - Method: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{ - {Type: contextContextField().Type, Names: []*ast.Ident{{Name: "ctx"}}}, - {Names: []*ast.Ident{ast.NewIdent("postID"), ast.NewIdent("commentID")}, Type: ast.NewIdent("string")}, - }}, - Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("any")}}}, - }, - ErrSub: "method expects type string but request is *http.Request", - }, - } { - t.Run(tt.Name, func(t *testing.T) { - pat, err, ok := NewTemplateName(tt.In) - require.True(t, ok) - require.NoError(t, err) - _, _, err = pat.funcLit(tt.Method, nil) - assert.ErrorContains(t, err, tt.ErrSub) - }) - } -} diff --git a/generate_test.go b/generate_test.go index 613cef4..3f66cfb 100644 --- a/generate_test.go +++ b/generate_test.go @@ -26,7 +26,6 @@ func TestGenerate(t *testing.T) { TemplatesVar string RoutesFunc string Imports []string - Method *ast.FuncType ExpectedError string ExpectedFile string @@ -1186,6 +1185,7 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { { Name: "call F with multiple arguments", Templates: `{{define "GET /{userName} F(ctx, userName)"}}{{end}}`, + Receiver: "T", ReceiverPackage: `-- in.go -- package main @@ -1218,6 +1218,150 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { } `, }, + { + Name: "missing arguments", + Templates: `{{define "GET / F()"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +type T struct{} + +func (T) F(string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + + ExpectedError: "handler func F(string) any expects 1 arguments but call F() has 0", + }, + { + Name: "extra arguments", + Templates: `{{define "GET /{name} F(ctx, name)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import ( + "context" + "net/html" +) + +type T struct{} + +func (T) F(context.Context) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "handler func F(context.Context) any expects 1 arguments but call F(ctx, name) has 2", + }, + { + Name: "wrong argument type request", + Templates: `{{define "GET / F(request)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import ( + "context" + "net/html" +) + +type T struct{} + +func (T) F(string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method expects type string but request is *http.Request", + }, + { + Name: "wrong argument type ctx", + Templates: `{{define "GET / F(ctx)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import "net/html" + +type T struct{} + +func (T) F(string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method expects type string but ctx is context.Context", + }, + { + Name: "wrong argument type response", + Templates: `{{define "GET / F(response)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import "net/html" + +type T struct{} + +func (T) F(string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method expects type string but response is http.ResponseWriter", + }, + { + Name: "wrong argument type path value", + Templates: `{{define "GET /{name} F(name)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import "net/html" + +type T struct{} + +func (T) F(float64) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method param type float64 not supported", + }, + { + Name: "wrong argument type request ptr", + Templates: `{{define "GET / F(request)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import "net/html" + +type T struct{} + +func (T) F(*T) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method expects type *T but request is *http.Request", + }, + { + Name: "wrong argument type in field list", + Templates: `{{define "GET /post/{postID}/comment/{commentID} F(ctx, request, commentID)"}}{{end}}`, + Receiver: "T", + ReceiverPackage: `-- in.go -- +package main + +import ( + "context" + "net/html" +) + +type T struct{} + +func (T) F(context.Context, string, string) any {return nil} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +`, + ExpectedError: "method expects type string but request is *http.Request", + }, } { t.Run(tt.Name, func(t *testing.T) { ts := template.Must(template.New(tt.Name).Parse(tt.Templates)) From 660b89fbb4b492f878b92133dbb57fec7a1af551 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:07:52 -0700 Subject: [PATCH 23/26] refactor: method nil checks --- generate.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/generate.go b/generate.go index 8980da2..cf6aeda 100644 --- a/generate.go +++ b/generate.go @@ -129,7 +129,7 @@ func (def TemplateName) callHandleFunc(handlerFuncLit *ast.FuncLit) *ast.ExprStm } func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.FuncLit, []*ast.ImportSpec, error) { - if def.handler == "" { + if method == nil { return def.httpRequestReceiverTemplateHandlerFunc(), nil, nil } lit := &ast.FuncLit{ @@ -137,18 +137,16 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F Body: &ast.BlockStmt{}, } call := &ast.CallExpr{Fun: callReceiverMethod(def.fun)} + if method.Params.NumFields() != len(def.call.Args) { + return nil, nil, errWrongNumberOfArguments(def, method) + } var formStruct *ast.StructType - if method != nil { - if method.Params.NumFields() != len(def.call.Args) { - return nil, nil, errWrongNumberOfArguments(def, method) + for pi, pt := range fieldListTypes(method.Params) { + if err := checkArgument(method, pi, def.call.Args[pi], pt, files); err != nil { + return nil, nil, err } - for pi, pt := range fieldListTypes(method.Params) { - if err := checkArgument(method, pi, def.call.Args[pi], pt, files); err != nil { - return nil, nil, err - } - if s, ok := findFormStruct(pt, files); ok { - formStruct = s - } + if s, ok := findFormStruct(pt, files); ok { + formStruct = s } } const errVarIdent = "err" @@ -258,7 +256,7 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F } const dataVarIdent = "data" - if method != nil && len(method.Results.List) > 1 { + if len(method.Results.List) > 1 { errVar := ast.NewIdent("err") lit.Body.List = append(lit.Body.List, From db2e1f670fc75f8104cb9aae5d6e1672dbd749c5 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:57:48 -0700 Subject: [PATCH 24/26] test: refactor constants to use use executeGo reduces test boilerplate also removed a duplicate tests --- generate_test.go | 259 +++++++++-------------------------------------- 1 file changed, 46 insertions(+), 213 deletions(-) diff --git a/generate_test.go b/generate_test.go index 3f66cfb..c1d9f98 100644 --- a/generate_test.go +++ b/generate_test.go @@ -184,14 +184,12 @@ package main type T struct{} func (*T) F(username string) int { return 30 } -`, + +` + executeGo, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(username string) int @@ -204,17 +202,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /age/{username} F(username)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -258,17 +245,17 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { -- receiver.go -- package main +import "net/http" + type T struct{} func (T) F(username string) (int, error) { return 30, error } -`, + +` + executeGo, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(username string) (int, error) @@ -285,17 +272,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /age/{username} F(username)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -308,7 +284,8 @@ package main type T struct{} func (T) F(ctx context.Context) int { return 30 } -`, + +` + executeGo, Receiver: "T", ExpectedError: "method expects type context.Context but request is *http.Request", }, @@ -337,14 +314,14 @@ func (T0) F(ctx context.Context) int { return 30 } func (T) F1(ctx context.Context, username string) int { return 30 } func (T) F(ctx context.Context, username string) int { return 30 } -`, + +` + executeGo, Receiver: "T", ExpectedFile: `package main import ( "context" "net/http" - "bytes" ) type RoutesReceiver interface { @@ -359,17 +336,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /age/{username} F(ctx, username)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -417,13 +383,12 @@ func (T) PassUint8(in uint8) uint8 { return in } func (T) PassBool(in bool) bool { return in } func (T) PassByte(in byte) byte { return in } func (T) PassRune(in rune) rune { return in } -`, +` + executeGo, ExpectedFile: `package main import ( "net/http" "strconv" - "bytes" ) type RoutesReceiver interface { @@ -549,17 +514,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /uint8/{value} PassUint8(value)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -574,14 +528,12 @@ type T struct{} type In struct{} func (T) F(form In) any { return nil } -`, + +` + executeGo, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(form In) any @@ -595,17 +547,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -616,14 +557,14 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo package main type T struct{} -`, + +` + executeGo, Receiver: "T", ExpectedFile: `package main import ( "net/http" "net/url" - "bytes" ) type RoutesReceiver interface { @@ -638,17 +579,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -666,64 +596,12 @@ type ( ) func (T) F(form In) int { return 0 } -`, - Receiver: "T", - ExpectedFile: `package main - -import ( - "net/http" - "bytes" -) -type RoutesReceiver interface { - F(form In) int -} - -func routes(mux *http.ServeMux, receiver RoutesReceiver) { - mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { - request.ParseForm() - var form In - form.field = request.FormValue("field") - data := receiver.F(form) - execute(response, request, true, "GET / F(form)", http.StatusOK, data) - }) -} -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} -`, - }, - { - Name: "F is defined and form field has an input tag", - Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, - ReceiverPackage: ` --- in.go -- -package main - -type ( - T struct{} - In struct{ - field string ` + "`input:\"some-field\"`" + ` - } -) - -func (T) F(form In) int { return 0 } -`, +` + executeGo, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(form In) int @@ -733,22 +611,11 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { request.ParseForm() var form In - form.field = request.FormValue("some-field") + form.field = request.FormValue("field") data := receiver.F(form) execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -766,14 +633,11 @@ type ( ) func (T) F(form In) int { return 0 } -`, +` + executeGo, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(form In) int @@ -788,17 +652,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -808,6 +661,8 @@ func execute(response http.ResponseWriter, request *http.Request, writeHeader bo -- in.go -- package main +import "net/http" + type ( T struct{} In struct{ @@ -827,6 +682,8 @@ type ( ) func (T) F(form In) int { return 0 } + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} `, Receiver: "T", ExpectedFile: `package main @@ -834,7 +691,6 @@ func (T) F(form In) int { return 0 } import ( "net/http" "strconv" - "bytes" ) type RoutesReceiver interface { @@ -918,26 +774,17 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { - Name: "F is defined and form has two string fields", + Name: "F is defined and form has two two names for a single field", Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, ReceiverPackage: ` -- in.go -- package main +import "net/http" + type ( T struct{} In struct{ @@ -946,14 +793,13 @@ type ( ) func (T) F(form In) int { return 0 } + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} `, Receiver: "T", ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F(form In) int @@ -969,17 +815,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -997,6 +832,8 @@ type ( ) func (T) F(form In) int { return 0 } + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} `, Receiver: "T", ExpectedError: "failed to generate parse statements for form field ts: unsupported type: time.Time", @@ -1010,13 +847,12 @@ func (T) F(form In) int { return 0 } package main type T struct{} + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} `, ExpectedFile: `package main -import ( - "net/http" - "bytes" -) +import "net/http" type RoutesReceiver interface { F() any @@ -1028,17 +864,6 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F()", http.StatusOK, data) }) } -func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) { - buf := bytes.NewBuffer(nil) - if err := templates.ExecuteTemplate(buf, name, data); err != nil { - http.Error(response, err.Error(), http.StatusInternalServerError) - return - } - if writeHeader { - response.WriteHeader(code) - } - _, _ = buf.WriteTo(response) -} `, }, { @@ -1392,3 +1217,11 @@ func methodFuncTypeLoader(t *testing.T, set *token.FileSet, in string) []*ast.Fi } return files } + +const executeGo = `-- execute.go -- +package main + +import "net/http" + +func execute(response http.ResponseWriter, request *http.Request, writeHeader bool, name string, code int, data any) {} +` From 8fa6a0c1afecc0669cf6de7d14a77671c5c3ed30 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:40:06 -0700 Subject: [PATCH 25/26] feat: support field lists --- generate.go | 222 +++++++++++++++++++++++++++-------------------- generate_test.go | 209 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 328 insertions(+), 103 deletions(-) diff --git a/generate.go b/generate.go index cf6aeda..f19d9dc 100644 --- a/generate.go +++ b/generate.go @@ -200,23 +200,59 @@ func (def TemplateName) funcLit(method *ast.FuncType, files []*ast.File) (*ast.F }, }}, &ast.ReturnStmt{}) - statements, parseImports, err := parseStringStatements(name.Name, fieldExpr, errVarIdent, &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), - Sel: ast.NewIdent("FormValue"), - }, - Args: []ast.Expr{ - &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(formInputName(field, name)), + assignment := singleAssignment(token.ASSIGN, fieldExpr) + if fieldType, ok := field.Type.(*ast.ArrayType); ok { + const valVar = "val" + assignment = appendAssignment(token.ASSIGN, &ast.SelectorExpr{ + X: ast.NewIdent(arg.Name), + Sel: ast.NewIdent(name.Name), + }) + statements, parseImports, err := parseStringStatements(name.Name, errVarIdent, ast.NewIdent(valVar), fieldType.Elt, errCheck, assignment) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate parse statements for form field %s: %w", name.Name, err) + } + + forLoop := &ast.RangeStmt{ + Key: ast.NewIdent("_"), + Value: ast.NewIdent(valVar), + Tok: token.DEFINE, + X: &ast.IndexExpr{ + X: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent("Form"), + }, + Index: &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(formInputName(field, name)), + }, }, - }, - }, field.Type, token.ASSIGN, errCheck) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate parse statements for form field %s: %w", name.Name, err) + Body: &ast.BlockStmt{ + List: statements, + }, + } + + lit.Body.List = append(lit.Body.List, forLoop) + imports = append(imports, parseImports...) + } else { + str := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(TemplateNameScopeIdentifierHTTPRequest), + Sel: ast.NewIdent("FormValue"), + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(formInputName(field, name)), + }, + }, + } + statements, parseImports, err := parseStringStatements(name.Name, errVarIdent, str, field.Type, errCheck, assignment) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate parse statements for form field %s: %w", name.Name, err) + } + lit.Body.List = append(lit.Body.List, statements...) + imports = append(imports, parseImports...) } - lit.Body.List = append(lit.Body.List, statements...) - imports = append(imports, parseImports...) } } } else { @@ -550,12 +586,36 @@ func httpPathValueAssignment(method *ast.FuncType, i int, arg *ast.Ident, errVar if typeIndex != i { continue } - return parseStringStatements(arg.Name, ast.NewIdent(arg.Name), errVarIdent, str, typeExp, assignTok, errCheck) + assignment := singleAssignment(assignTok, ast.NewIdent(arg.Name)) + return parseStringStatements(arg.Name, errVarIdent, str, typeExp, errCheck, assignment) } return nil, nil, fmt.Errorf("type for argumement %d not found", i) } -func parseStringStatements(name string, result ast.Expr, errVarIdent string, str, typeExp ast.Expr, assignTok token.Token, errCheck *ast.IfStmt) ([]ast.Stmt, []*ast.ImportSpec, error) { +func singleAssignment(assignTok token.Token, result ast.Expr) func(exp ast.Expr) ast.Stmt { + return func(exp ast.Expr) ast.Stmt { + return &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{exp}, + } + } +} + +func appendAssignment(assignTok token.Token, result ast.Expr) func(exp ast.Expr) ast.Stmt { + return func(exp ast.Expr) ast.Stmt { + return &ast.AssignStmt{ + Lhs: []ast.Expr{result}, + Tok: assignTok, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: ast.NewIdent("append"), + Args: []ast.Expr{result, exp}, + }}, + } + } +} + +func parseStringStatements(name string, errVarIdent string, str, typeExp ast.Expr, errCheck *ast.IfStmt, assignment func(ast.Expr) ast.Stmt) ([]ast.Stmt, []*ast.ImportSpec, error) { const parsedVarSuffix = "Parsed" paramTypeIdent, ok := typeExp.(*ast.Ident) if !ok { @@ -566,8 +626,10 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str default: return nil, nil, fmt.Errorf("method param type %s not supported", source.Format(typeExp)) case "bool": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, + tmp := name + parsedVarSuffix + + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -578,7 +640,9 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + assign := assignment(ast.NewIdent(tmp)) + + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int": tmp := name + parsedVarSuffix @@ -594,14 +658,7 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(ast.NewIdent(tmp)) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int16": @@ -619,14 +676,10 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int32": @@ -644,14 +697,10 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int8": @@ -669,19 +718,17 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "int64": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, + tmp := name + parsedVarSuffix + + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -692,7 +739,9 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + assign := assignment(ast.NewIdent(tmp)) + + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint": tmp := name + parsedVarSuffix @@ -708,14 +757,10 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint16": @@ -733,14 +778,10 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint32": @@ -758,20 +799,17 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint64": + tmp := name + parsedVarSuffix - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result, ast.NewIdent(errVarIdent)}, + parse := &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent(tmp), ast.NewIdent(errVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{ Fun: &ast.SelectorExpr{ @@ -782,7 +820,9 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - return []ast.Stmt{assign, errCheck}, []*ast.ImportSpec{importSpec("strconv")}, nil + assign := assignment(ast.NewIdent(tmp)) + + return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "uint8": tmp := name + parsedVarSuffix @@ -798,22 +838,14 @@ func parseStringStatements(name string, result ast.Expr, errVarIdent string, str }}, } - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: ast.NewIdent(paramTypeIdent.Name), - Args: []ast.Expr{ast.NewIdent(tmp)}, - }}, - } + assign := assignment(&ast.CallExpr{ + Fun: ast.NewIdent(paramTypeIdent.Name), + Args: []ast.Expr{ast.NewIdent(tmp)}, + }) return []ast.Stmt{parse, errCheck, assign}, []*ast.ImportSpec{importSpec("strconv")}, nil case "string": - assign := &ast.AssignStmt{ - Lhs: []ast.Expr{result}, - Tok: assignTok, - Rhs: []ast.Expr{str}, - } + assign := assignment(str) return []ast.Stmt{assign}, nil, nil } } diff --git a/generate_test.go b/generate_test.go index c1d9f98..de7c021 100644 --- a/generate_test.go +++ b/generate_test.go @@ -407,11 +407,12 @@ type RoutesReceiver interface { func routes(mux *http.ServeMux, receiver RoutesReceiver) { mux.HandleFunc("GET /bool/{value}", func(response http.ResponseWriter, request *http.Request) { - value, err := strconv.ParseBool(request.PathValue("value")) + valueParsed, err := strconv.ParseBool(request.PathValue("value")) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + value := valueParsed data := receiver.PassBool(value) execute(response, request, true, "GET /bool/{value} PassBool(value)", http.StatusOK, data) }) @@ -421,7 +422,7 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { http.Error(response, err.Error(), http.StatusBadRequest) return } - value := int(valueParsed) + value := valueParsed data := receiver.PassInt(value) execute(response, request, true, "GET /int/{value} PassInt(value)", http.StatusOK, data) }) @@ -446,11 +447,12 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /int32/{value} PassInt32(value)", http.StatusOK, data) }) mux.HandleFunc("GET /int64/{value}", func(response http.ResponseWriter, request *http.Request) { - value, err := strconv.ParseInt(request.PathValue("value"), 10, 64) + valueParsed, err := strconv.ParseInt(request.PathValue("value"), 10, 64) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + value := valueParsed data := receiver.PassInt64(value) execute(response, request, true, "GET /int64/{value} PassInt64(value)", http.StatusOK, data) }) @@ -495,11 +497,12 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET /uint32/{value} PassUint32(value)", http.StatusOK, data) }) mux.HandleFunc("GET /uint64/{value}", func(response http.ResponseWriter, request *http.Request) { - value, err := strconv.ParseUint(request.PathValue("value"), 10, 64) + valueParsed, err := strconv.ParseUint(request.PathValue("value"), 10, 64) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + value := valueParsed data := receiver.PassUint64(value) execute(response, request, true, "GET /uint64/{value} PassUint64(value)", http.StatusOK, data) }) @@ -706,12 +709,13 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { http.Error(response, err.Error(), http.StatusBadRequest) return } - form.fieldInt = int(fieldIntParsed) - form.fieldInt64, err := strconv.ParseInt(request.FormValue("fieldInt64"), 10, 64) + form.fieldInt = fieldIntParsed + fieldInt64Parsed, err := strconv.ParseInt(request.FormValue("fieldInt64"), 10, 64) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + form.fieldInt64 = fieldInt64Parsed fieldInt32Parsed, err := strconv.ParseInt(request.FormValue("fieldInt32"), 10, 32) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) @@ -736,11 +740,12 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { return } form.fieldUint = uint(fieldUintParsed) - form.fieldUint64, err := strconv.ParseUint(request.FormValue("fieldUint64"), 10, 64) + fieldUint64Parsed, err := strconv.ParseUint(request.FormValue("fieldUint64"), 10, 64) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + form.fieldUint64 = fieldUint64Parsed fieldUint16Parsed, err := strconv.ParseUint(request.FormValue("fieldUint16"), 10, 16) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) @@ -765,11 +770,12 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { return } form.fieldUint8 = uint8(fieldUint8Parsed) - form.fieldBool, err := strconv.ParseBool(request.FormValue("fieldBool")) + fieldBoolParsed, err := strconv.ParseBool(request.FormValue("fieldBool")) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } + form.fieldBool = fieldBoolParsed data := receiver.F(form) execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) @@ -815,6 +821,193 @@ func routes(mux *http.ServeMux, receiver RoutesReceiver) { execute(response, request, true, "GET / F(form)", http.StatusOK, data) }) } +`, + }, + { + Name: "F is defined and form slice field", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +import "net/http" + +type ( + T struct{} + In struct{ + field []string + } +) + +func (T) F(form In) int { return 0 } + +` + executeGo, + Receiver: "T", + ExpectedFile: `package main + +import "net/http" + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + for _, val := range request.Form["field"] { + form.field = append(form.field, val) + } + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} +`, + }, + { + Name: "F is defined and form has typed slice fields", + Templates: `{{define "GET / F(form)"}}Hello, {{.}}!{{end}}`, + ReceiverPackage: ` +-- in.go -- +package main + +type ( + T struct{} + In struct{ + fieldInt []int + fieldInt64 []int64 + fieldInt32 []int32 + fieldInt16 []int16 + fieldInt8 []int8 + fieldUint []uint + fieldUint64 []uint64 + fieldUint16 []uint16 + fieldUint32 []uint32 + fieldUint16 []uint16 + fieldUint8 []uint8 + fieldBool []bool + } +) + +func (T) F(form In) int { return 0 } + +` + executeGo, + Receiver: "T", + ExpectedFile: `package main + +import ( + "net/http" + "strconv" +) + +type RoutesReceiver interface { + F(form In) int +} + +func routes(mux *http.ServeMux, receiver RoutesReceiver) { + mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { + request.ParseForm() + var form In + for _, val := range request.Form["fieldInt"] { + fieldIntParsed, err := strconv.Atoi(val) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt = append(form.fieldInt, fieldIntParsed) + } + for _, val := range request.Form["fieldInt64"] { + fieldInt64Parsed, err := strconv.ParseInt(val, 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt64 = append(form.fieldInt64, fieldInt64Parsed) + } + for _, val := range request.Form["fieldInt32"] { + fieldInt32Parsed, err := strconv.ParseInt(val, 10, 32) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt32 = append(form.fieldInt32, int32(fieldInt32Parsed)) + } + for _, val := range request.Form["fieldInt16"] { + fieldInt16Parsed, err := strconv.ParseInt(val, 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt16 = append(form.fieldInt16, int16(fieldInt16Parsed)) + } + for _, val := range request.Form["fieldInt8"] { + fieldInt8Parsed, err := strconv.ParseInt(val, 10, 8) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldInt8 = append(form.fieldInt8, int8(fieldInt8Parsed)) + } + for _, val := range request.Form["fieldUint"] { + fieldUintParsed, err := strconv.ParseUint(val, 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint = append(form.fieldUint, uint(fieldUintParsed)) + } + for _, val := range request.Form["fieldUint64"] { + fieldUint64Parsed, err := strconv.ParseUint(val, 10, 64) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint64 = append(form.fieldUint64, fieldUint64Parsed) + } + for _, val := range request.Form["fieldUint16"] { + fieldUint16Parsed, err := strconv.ParseUint(val, 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint16 = append(form.fieldUint16, uint16(fieldUint16Parsed)) + } + for _, val := range request.Form["fieldUint32"] { + fieldUint32Parsed, err := strconv.ParseUint(val, 10, 32) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint32 = append(form.fieldUint32, uint32(fieldUint32Parsed)) + } + for _, val := range request.Form["fieldUint16"] { + fieldUint16Parsed, err := strconv.ParseUint(val, 10, 16) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint16 = append(form.fieldUint16, uint16(fieldUint16Parsed)) + } + for _, val := range request.Form["fieldUint8"] { + fieldUint8Parsed, err := strconv.ParseUint(val, 10, 8) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldUint8 = append(form.fieldUint8, uint8(fieldUint8Parsed)) + } + for _, val := range request.Form["fieldBool"] { + fieldBoolParsed, err := strconv.ParseBool(val) + if err != nil { + http.Error(response, err.Error(), http.StatusBadRequest) + return + } + form.fieldBool = append(form.fieldBool, fieldBoolParsed) + } + data := receiver.F(form) + execute(response, request, true, "GET / F(form)", http.StatusOK, data) + }) +} `, }, { From 14636ad2e35eef0a140c715f5affdb7d6cc69650 Mon Sep 17 00:00:00 2001 From: Chrstopher Hunter <8398225+crhntr@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:29:07 -0700 Subject: [PATCH 26/26] add feature level test --- cmd/muxt/testdata/generate/form.txtar | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 cmd/muxt/testdata/generate/form.txtar diff --git a/cmd/muxt/testdata/generate/form.txtar b/cmd/muxt/testdata/generate/form.txtar new file mode 100644 index 0000000..f7200ee --- /dev/null +++ b/cmd/muxt/testdata/generate/form.txtar @@ -0,0 +1,93 @@ +muxt generate --receiver-static-type=T + +cat template_routes.go + +exec go test -cover + +-- template.gohtml -- +{{define "POST / Method(form)" }}{{end}} + +-- go.mod -- +module server + +go 1.22 + +-- template.go -- +package server + +import ( + "embed" + "html/template" +) + +//go:embed *.gohtml +var formHTML embed.FS + +var templates = template.Must(template.ParseFS(formHTML, "*")) + +type Form struct { + Count []int `json:"count"` + Str string `input:"some-string" json:"str"` +} + +type T struct { + spy func(Form) Form +} + +func (t T) Method(form Form) Form { + return t.spy(form) +} +-- template_test.go -- +package server + +import ( + "io" + "net/http" + "net/http/httptest" + "net/url" + "slices" + "strings" + "testing" +) + +func Test(t *testing.T) { + mux := http.NewServeMux() + + var service T + + service.spy = func(form Form) Form { + if exp := []int{7, 14, 21, 29}; !slices.Equal(exp, form.Count) { + t.Errorf("exp %v, got %v", exp, form.Count) + } + if exp := "apple"; form.Str != exp { + t.Errorf("exp %v, got %v", exp, form.Str) + } + return form + } + + routes(mux, service) + + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(url.Values{ + "some-string": []string{"apple"}, + "Count": []string{"7", "14", "21", "29"}, + }.Encode())) + req.Header.Set("content-type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + + mux.ServeHTTP(rec, req) + + res := rec.Result() + + if res.StatusCode != http.StatusOK { + t.Error("expected OK") + } + + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + } + + if exp := ``; string(body) != exp { + t.Errorf("exp %v, got %v", exp, string(body)) + } +}