diff options
author | Mathias Magnusson <mathias@magnusson.space> | 2025-04-10 21:33:41 +0200 |
---|---|---|
committer | Mathias Magnusson <mathias@magnusson.space> | 2025-04-10 21:33:41 +0200 |
commit | 49bd3e0e0117768138f47f0c97accece15605025 (patch) | |
tree | 531306398969bf38fbaff946e5d67d122fa49c6b | |
parent | b9bf8a23c75db82e1aff8295a97dcfdf789735f3 (diff) | |
download | hh-49bd3e0e0117768138f47f0c97accece15605025.tar.gz |
extract value extracting and convertion to functions; support uuid
-rw-r--r-- | cmd/generate/main.go | 58 | ||||
-rw-r--r-- | cmd/generate/templates.go.tmpl | 92 | ||||
-rw-r--r-- | examples/basic.go | 21 | ||||
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | hh.go | 43 |
6 files changed, 148 insertions, 72 deletions
diff --git a/cmd/generate/main.go b/cmd/generate/main.go index 06cf8fd..2135320 100644 --- a/cmd/generate/main.go +++ b/cmd/generate/main.go @@ -8,8 +8,10 @@ import ( "go/format" "go/parser" "go/token" + "math" "os" "reflect" + "slices" "strings" "text/template" ) @@ -29,6 +31,7 @@ func slice(fileContents string, fset *token.FileSet, start token.Pos, end token. type Package struct { PackageName string + Imports []string Functions []Function } @@ -56,6 +59,12 @@ func run() error { "error": func(msg string) struct{} { panic("error in template: " + msg) }, + "extractorName": func(s string) string { + return "hh.ExtractFrom" + strings.ToUpper(s[:1]) + s[1:] + }, + "converterName": func(s string) string { + return "hh.ConvertTo" + strings.ToUpper(s[:1]) + strings.ReplaceAll(s[1:], ".", "") + }, }).Parse(fileTemplateString) if err != nil { return err @@ -66,31 +75,42 @@ func run() error { return err } - parsedPackage := Package{} + parsedPackage := Package{ + Imports: []string{ + "codeberg.org/foodelevator/hh", + "log/slog", + "net/http", + "github.com/google/uuid", + }, + } for _, ent := range dirEntries { if !strings.HasSuffix(ent.Name(), ".go") { continue } + if ent.Name() == "hh.gen.go" { + continue + } + fileBytes, err := os.ReadFile(ent.Name()) if err != nil { return err } fileContents := string(fileBytes) var fset token.FileSet - f, err := parser.ParseFile(&fset, ent.Name(), fileBytes, parser.ParseComments|parser.SkipObjectResolution) + file, err := parser.ParseFile(&fset, ent.Name(), fileBytes, parser.ParseComments|parser.SkipObjectResolution) if err != nil { return err } - if strings.HasSuffix(f.Name.String(), "_test") { + if strings.HasSuffix(file.Name.String(), "_test") { continue } - if parsedPackage.PackageName != "" && parsedPackage.PackageName != f.Name.String() { - return errors.New("Found two different package names in directory: " + parsedPackage.PackageName + " and " + f.Name.String()) + if parsedPackage.PackageName != "" && parsedPackage.PackageName != file.Name.String() { + return errors.New("Found two different package names in directory: " + parsedPackage.PackageName + " and " + file.Name.String()) } - parsedPackage.PackageName = f.Name.String() + parsedPackage.PackageName = file.Name.String() - for _, decl := range f.Decls { + for _, decl := range file.Decls { f, ok := decl.(*ast.FuncDecl) if !ok { continue @@ -121,6 +141,30 @@ func run() error { Optional: false, TypeDef: slice(fileContents, &fset, typ.Pos(), typ.End()), } + if selector, ok := typ.(*ast.SelectorExpr); ok { + sel := slice(fileContents, &fset, selector.X.Pos(), selector.X.End()) + least := math.MaxInt + var winner string + for _, imp := range file.Imports { + path := imp.Path.Value[1 : len(imp.Path.Value)-1] + idx := strings.LastIndex(path, sel) + if idx == -1 { + continue + } + ridx := len(path) - idx + if ridx < least { + least = ridx + winner = path + } + } + if winner == "" { + panic("Counld not find import for " + parsedField.TypeDef) + } + parsedPackage.Imports = append( + slices.DeleteFunc(parsedPackage.Imports, func(i string) bool { return i == winner }), + winner, + ) + } if parsedField.TypeDef == "*http.Request" { parsedFunction.RequestTypeFields = append(parsedFunction.RequestTypeFields, parsedField) continue diff --git a/cmd/generate/templates.go.tmpl b/cmd/generate/templates.go.tmpl index 0e684e3..d33e069 100644 --- a/cmd/generate/templates.go.tmpl +++ b/cmd/generate/templates.go.tmpl @@ -1,19 +1,21 @@ // WARNING: this file has been automatically generated by -// codeberg.org/fooelevator/hh. DO NOT EDIT MANUALLY! +// codeberg.org/foodelevator/hh. DO NOT EDIT MANUALLY! package {{ .PackageName }} import ( - "net/http" + {{ range $_, $i := .Imports -}} + "{{ $i }}" + {{ end }} ) -func hhMountRoutes[S any](s S, mux *http.ServeMux) { +func hhMountRoutes(mux *http.ServeMux) { if mux == nil { mux = http.DefaultServeMux } - wrapper := func(handler func(s S, w http.ResponseWriter, r *http.Request)) http.Handler { + wrapper := func(handler func(w http.ResponseWriter, r *http.Request)) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { - handler(s, w, r) + handler(w, r) }) } @@ -23,73 +25,47 @@ func hhMountRoutes[S any](s S, mux *http.ServeMux) { } {{ range $_, $fn := .Functions }} -func hh_{{ $fn.Name }}[S any](s S, w http.ResponseWriter, r *http.Request) { +func hh_{{ $fn.Name }}(w http.ResponseWriter, r *http.Request) { + defer func() { + err := recover() + if err == nil { + return + } + w.WriteHeader(http.StatusInternalServerError) + id := uuid.New() + w.Write([]byte("Internal server error. id = " + id.String())) + slog.Error("Panic in handler", "handler", {{ quote $fn.Name }}, "id", id, "error", err) + }() {{- if $fn.DoParseForm }} if err := r.ParseForm(); err != nil { panic("todo: Bad request") } {{ end }} + var parsed {{ $fn.RequestTypeDef }} {{- range $_, $f := $fn.RequestTypeFields }} {{ if eq $f.TypeDef "*http.Request" }} {{ continue }} {{ end }} - var {{ $f.Name }}0 string - {{- if $f.Optional }} - {{ $f.Name }}Skipped := false - {{- end }} - {{- if eq $f.Extractor "form" }} - {{ $f.Name }}1 := r.Form[{{ $f.NameInReq | quote }}] - if len({{ $f.Name }}1) != 0 { - {{ $f.Name }}0 = {{ $f.Name }}1[0] - } else { - {{- if not $f.Optional }} - panic("todo: Bad request: form value " + {{ $f.NameInReq | quote }} + " missing") - {{- else }} - {{ $f.Name }}Skipped = true - {{- end }} - } - {{ else if eq $f.Extractor "path" }} - {{ $f.Name }}1 := r.PathValue({{ $f.NameInReq | quote }}) - if {{ $f.Name }}1 != "" { - {{ $f.Name }}0 = {{ $f.Name }}1 - } else { - {{- if not $f.Optional }} - panic("todo: Bad request: path value " + {{ $f.NameInReq | quote }} + " missing") - {{- else }} - {{ $f.Name }}Skipped = true - {{- end }} - } - {{ else if eq $f.Extractor "cookie" }} - {{ $f.Name }}1, _ := r.Cookie({{ $f.NameInReq | quote }}) - if {{ $f.Name }}1 != nil { - {{ $f.Name }}0 = {{ $f.Name }}1.Value - } else { - {{- if not $f.Optional }} - panic("todo: Bad request: cookie " + {{ $f.NameInReq | quote }} + " missing") - {{- else }} - {{ $f.Name }}Skipped = true - {{- end }} + {{ $f.Name }}, {{ $f.Name }}Skipped := {{ extractorName $f.Extractor }}(r, {{ $f.NameInReq | quote }}) + {{ if not $f.Optional }} + if {{ $f.Name }}Skipped { + panic("todo: Bad request: " + {{ $f.Extractor | quote }} + " value " + {{ $f.NameInReq | quote }} + " missing") } - {{ else }} - {{ error "unknown extractor" }} - {{ end -}} - {{ if eq $f.TypeDef "string" -}} - {{ $f.Name }} := {{ $f.Name }}0 - {{ else if eq $f.TypeDef "int" -}} - var {{ $f.Name }} int - {{ if $f.Optional }} if !{{ $f.Name }}Skipped { {{ end -}} - var err error - {{ $f.Name }}, err = strconv.Atoi({{ $f.Name }}0) - if err != nil { - panic("todo: Bad request: " + {{ $f.NameInReq | quote }} + " must be a valid int") - } - {{ if $f.Optional }} } {{ end }} {{ end }} + + {{ if $f.Optional }} if !{{ $f.Name }}Skipped {{ end -}} { + var err error + parsed.{{ $f.Name }}, err = {{ converterName $f.TypeDef }}({{ $f.Name }}) + if err != nil { + panic("todo: Bad request: " + {{ $f.NameInReq | quote }} + " must be a valid " + {{ $f.TypeDef | quote }}) + } + } {{ end }} - {{ $fn.Name }}(w, {{ $fn.RequestTypeDef }}{ + {{ $fn.Name }}(w, parsed) + {{ if false }} {{ range $_, $f := $fn.RequestTypeFields -}} {{ $f.Name }}: {{ $f.Name }}, {{ end }} - }) + {{ end }} } {{ end }} diff --git a/examples/basic.go b/examples/basic.go index 856d682..9feaacd 100644 --- a/examples/basic.go +++ b/examples/basic.go @@ -1,20 +1,29 @@ -package examples +package main import ( "log/slog" "net/http" + + "github.com/google/uuid" ) // Big bungus function here! // -//hh:route GET /admin/users +//hh:route GET /org/{orgID}/users func adminUsersForm(w http.ResponseWriter, r struct { r *http.Request - search string `hh:"form"` - year int `hh:"optional,form"` - offset int `hh:"form"` - nextURL string `hh:"cookie,logout_next_url"` + search string `hh:"form"` + year int `hh:"optional,form"` + offset int `hh:"form"` + orgID uuid.UUID `hh:"path"` + banana uuid.UUID `hh:"optional,form"` + nextURL string `hh:"optional,cookie,logout_next_url"` }) { _, _ = w.Write([]byte("ahahaha")) slog.Info("get admin users form", "search", r.search, "offset", r.offset, "next-url", r.nextURL) } + +func main() { + hhMountRoutes(nil) + http.ListenAndServe(":http", nil) +} @@ -1,3 +1,5 @@ module codeberg.org/foodelevator/hh -go 1.22.6 +go 1.23.6 + +require github.com/google/uuid v1.6.0 @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1 +1,44 @@ package hh + +import ( + "net/http" + "strconv" + + "github.com/google/uuid" +) + +func ExtractFromForm(r *http.Request, name string) (string, bool) { + value := r.Form[name] + if len(value) == 0 { + return "", true + } + return value[0], false +} + +func ExtractFromPath(r *http.Request, name string) (string, bool) { + value := r.PathValue(name) + if value == "" { + return "", true + } + return value, false +} + +func ExtractFromCookie(r *http.Request, name string) (string, bool) { + value, err := r.Cookie(name) + if err != nil { + return "", true + } + return value.Value, false +} + +func ConvertToInt(value string) (int, error) { + return strconv.Atoi(value) +} + +func ConvertToString(value string) (string, error) { + return value, nil +} + +func ConvertToUuidUUID(value string) (uuid.UUID, error) { + return uuid.Parse(value) +} |