~repos /gromer

#golang#htmx#ssr

git clone https://pyrossh.dev/repos/gromer.git

gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.


51190669 Peter John

3 years ago
fix error handling
_example/components/Status.go CHANGED
@@ -1,59 +1,35 @@
1
- package not_found_404
1
+ package components
2
2
 
3
3
  import (
4
4
  . "github.com/pyros2097/gromer/gsx"
5
5
  )
6
6
 
7
- // var (
8
- // Meta = M{
9
- // "title": "Oops something wen't wrong",
10
- // }
11
- // Styles = M{
12
- // "notfound": "flex flex-1 items-center justify-center w-screen h-screen",
13
- // "back": "mt-6",
14
- // "link": "underlined",
15
- // }
16
- // )
17
-
18
- // var (
19
- // Meta = M{
20
- // "title": "Page Not Found",
21
- // }
22
- // Styles = M{
23
- // "notfound": "flex flex-1 items-center justify-center w-screen h-screen",
24
- // "back": "mt-6",
25
- // "link": "underlined",
26
- // }
27
- // )
28
-
29
- // func GET(c *Context) ([]*Tag, int, error) {
30
- // return c.Render(`
31
- // <div id="error" class="notfound " hx-swap-oob="true">
32
- // <h1>"Page Not Found"</h1>
33
- // <h2 class="mt-6">
34
- // <a class="link" href="/">"Go Back"</a>
35
- // </h2>
36
- // </div>
37
- // `), 404, nil
38
- // }
39
-
40
7
  var StatusStyles = M{
41
- "container": "border-t-2 border-gray-100 text-2xl",
8
+ "container": "flex flex-1 flex-col items-center justify-center w-screen h-screen",
42
- "row": "flex flex-row group",
9
+ "title": "text-xl font-bold",
43
- "button-1": "ml-4 text-gray-400",
10
+ "back-container": "mt-6 text-lg",
44
- "label": "flex-1 min-w-0 flex items-center break-all ml-2 p-2 text-gray-800",
45
- "striked": "text-gray-500 line-through",
46
- "button-2": "mr-4 text-red-700 opacity-0 hover:opacity-100",
47
- "unchecked": "text-gray-200",
11
+ "link": "underline",
48
12
  }
49
13
 
50
14
  func Status(c *Context, status int, err error) []*Tag {
15
+ if status == 404 {
16
+ c.AddMeta("title", "Gromer | Page Not Found")
17
+ return c.Render(`
18
+ <div id="error" class="Status" hx-swap-oob="true">
19
+ <h1 class="title">"Page Not Found"</h1>
20
+ <h2 class="back-container">
21
+ <a class="link" href="/">"Go back"</a>
22
+ </h2>
23
+ </div>
24
+ `)
25
+ }
26
+ c.AddMeta("title", "Gromer | Oop's something went wrong")
51
27
  return c.Render(`
52
- <div id="error" class="notfound " hx-swap-oob="true">
28
+ <div id="error" class="Status" hx-swap-oob="true">
53
- <h1>"Oops something wen't wrong"</h1>
29
+ <h1 class="title">"Oop's something went wrong"</h1>
54
- <h2 class="mt-6">
30
+ <h2 class="back-container">
55
- <a class="link" href="/">"Go Back"</a>
31
+ <a class="link" href="/">"Go back"</a>
56
32
  </h2>
57
33
  </div>
58
- `), 404, nil
34
+ `)
59
35
  }
_example/components/Todo.go CHANGED
@@ -42,6 +42,6 @@ func Todo(c *Context, todo *todos.Todo) []*Tag {
42
42
  </button>
43
43
  </form>
44
44
  </div>
45
- </div>
45
+ </div>
46
46
  `)
47
47
  }
_example/main.go CHANGED
@@ -20,6 +20,7 @@ import (
20
20
  func init() {
21
21
  gsx.RegisterComponent(components.Todo, components.TodoStyles, "todo")
22
22
  gsx.RegisterComponent(components.Checkbox, nil, "value")
23
+ gsx.RegisterComponent(components.Status, components.StatusStyles, "status", "error")
23
24
  gsx.RegisterComponent(containers.TodoCount, nil, "filter")
24
25
  gsx.RegisterComponent(containers.TodoList, containers.TodoListStyles, "page", "filter")
25
26
  }
@@ -27,7 +28,7 @@ func init() {
27
28
  func main() {
28
29
  baseRouter := mux.NewRouter()
29
30
  baseRouter.Use(gromer.LogMiddleware)
30
- // baseRouter.NotFoundHandler = gromer.StatusHandler(404, not_found_404.GET, not_found_404.Meta, not_found_404.Styles)
31
+ gromer.RegisterStatusHandler(baseRouter, components.Status, components.StatusStyles)
31
32
 
32
33
  staticRouter := baseRouter.NewRoute().Subrouter()
33
34
  staticRouter.Use(gromer.CacheMiddleware)
http.go CHANGED
@@ -6,6 +6,7 @@ import (
6
6
  "embed"
7
7
  "encoding/json"
8
8
  "fmt"
9
+ "net"
9
10
  "net/http"
10
11
  "net/url"
11
12
  "os"
@@ -19,7 +20,6 @@ import (
19
20
  "time"
20
21
 
21
22
  "github.com/felixge/httpsnoop"
22
- "github.com/go-playground/validator/v10"
23
23
  "github.com/google/uuid"
24
24
  "github.com/gorilla/handlers"
25
25
  "github.com/gorilla/mux"
@@ -38,12 +38,15 @@ const (
38
38
  )
39
39
 
40
40
  var (
41
- info *debug.BuildInfo
41
+ info *debug.BuildInfo
42
- IsCloundRun bool
42
+ IsCloundRun bool
43
- routeDefs []RouteDefinition
43
+ routeDefs []RouteDefinition
44
- pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
44
+ pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
45
+ globalStatusComponent StatusComponent = nil
45
46
  )
46
47
 
48
+ type StatusComponent func(c *gsx.Context, status int, err error) []*gsx.Tag
49
+
47
50
  type RouteDefinition struct {
48
51
  Pkg string `json:"pkg"`
49
52
  PkgPath string `json:"pkgPath"`
@@ -79,14 +82,10 @@ func getFunctionName(temp interface{}) string {
79
82
  return strs[len(strs)-1]
80
83
  }
81
84
 
82
- func RespondError(w http.ResponseWriter, status int, err error) {
85
+ func RespondError(w http.ResponseWriter, r *http.Request, status int, err error) {
83
86
  w.Header().Set("Content-Type", "text/html")
84
87
  w.WriteHeader(status) // always write status last
85
- merror := map[string]interface{}{
86
- "error": err.Error(),
87
- }
88
88
  if status >= 500 {
89
- merror["error"] = "Internal Server Error"
90
89
  formattedStr := eris.ToCustomString(err, eris.StringFormat{
91
90
  Options: eris.FormatOptions{
92
91
  WithTrace: true,
@@ -100,18 +99,19 @@ func RespondError(w http.ResponseWriter, status int, err error) {
100
99
  })
101
100
  log.Error().Msg(err.Error() + "\n" + formattedStr)
102
101
  }
103
- validationErrors, ok := err.(validator.ValidationErrors)
104
- if ok {
105
- merror["error"] = GetValidationError(validationErrors)
102
+ c := createCtx(r, "Status", "Status", gsx.M{}, gsx.M{})
106
- }
107
- c := gsx.NewContext(context.TODO(), &gsx.HX{})
108
103
  c.Set("funcName", "error")
109
104
  c.Set("error", err.Error())
105
+ if r.Header.Get("HX-Request") == "true" || globalStatusComponent == nil {
110
- tags := c.Render(`
106
+ tags := c.Render(`
111
- <div style="color: red;">
107
+ <div style="color: red;">
112
- <h1>{error}</h1>
108
+ <h1>{error}</h1>
113
- </div>
109
+ </div>
114
- `)
110
+ `)
111
+ gsx.Write(c, w, tags)
112
+ return
113
+ }
114
+ tags := globalStatusComponent(c, status, err)
115
115
  gsx.Write(c, w, tags)
116
116
  }
117
117
 
@@ -144,7 +144,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
144
144
  if method == "GET" || ((method == "POST" || method == "PUT" || method == "PATCH") && contentType == "application/x-www-form-urlencoded") {
145
145
  err := r.ParseForm()
146
146
  if err != nil {
147
- RespondError(w, 400, err)
147
+ RespondError(w, r, 400, err)
148
148
  return
149
149
  }
150
150
  rv := instance.Elem()
@@ -169,7 +169,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
169
169
  } else {
170
170
  v, err := strconv.ParseInt(jsonValue, 10, base)
171
171
  if err != nil {
172
- RespondError(w, 400, err)
172
+ RespondError(w, r, 400, err)
173
173
  return
174
174
  }
175
175
  f.SetInt(v)
@@ -180,7 +180,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
180
180
  } else {
181
181
  v, err := time.Parse(time.RFC3339, jsonValue)
182
182
  if err != nil {
183
- RespondError(w, 400, err)
183
+ RespondError(w, r, 400, err)
184
184
  return
185
185
  }
186
186
  f.Set(reflect.ValueOf(v))
@@ -193,11 +193,11 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
193
193
  } else if (method == "POST" || method == "PUT" || method == "PATCH") && contentType == "application/json" {
194
194
  err := json.NewDecoder(r.Body).Decode(instance.Interface())
195
195
  if err != nil {
196
- RespondError(w, 400, err)
196
+ RespondError(w, r, 400, err)
197
197
  return
198
198
  }
199
199
  } else {
200
- RespondError(w, 400, eris.Errorf("Illegal Content-Type tag found %s", contentType))
200
+ RespondError(w, r, 400, eris.Errorf("Illegal Content-Type tag found %s", contentType))
201
201
  return
202
202
  }
203
203
  c.Set("params", instance.Elem().Interface())
@@ -208,7 +208,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
208
208
  responseStatus := values[1].Interface().(int)
209
209
  responseError := values[2].Interface()
210
210
  if responseError != nil {
211
- RespondError(w, responseStatus, eris.Wrap(responseError.(error), "Render failed"))
211
+ RespondError(w, r, responseStatus, eris.Wrap(responseError.(error), "Render failed"))
212
212
  return
213
213
  }
214
214
  w.Header().Set("Content-Type", "text/html")
@@ -225,15 +225,15 @@ func LogMiddleware(next http.Handler) http.Handler {
225
225
  if r.URL.RawQuery != "" {
226
226
  url += "?" + r.URL.RawQuery
227
227
  }
228
- // ip, _, _ := net.SplitHostPort(r.RemoteAddr)
228
+ ip, _, _ := net.SplitHostPort(r.RemoteAddr)
229
- // if len(ip) > 0 && ip[0] == '[' {
229
+ if len(ip) > 0 && ip[0] == '[' {
230
- // ip = ip[1 : len(ip)-1]
230
+ ip = ip[1 : len(ip)-1]
231
- // }
231
+ }
232
232
  ua := useragent.Parse(r.UserAgent()).Name
233
233
  defer func() {
234
234
  if err := recover(); err != nil {
235
235
  log.Error().Msgf("%s 599 %s %s", r.Method, ua, url)
236
- RespondError(w, 599, eris.Errorf("%+v", err))
236
+ RespondError(w, r, 599, eris.Errorf("%+v", err))
237
237
  }
238
238
  }()
239
239
  m := httpsnoop.CaptureMetrics(next, w, r)
@@ -263,12 +263,12 @@ func IconsRoute(router *mux.Router, path string, fs embed.FS) {
263
263
  router.PathPrefix(path).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264
264
  err := r.ParseForm()
265
265
  if err != nil {
266
- RespondError(w, 400, err)
266
+ RespondError(w, r, 400, err)
267
267
  return
268
268
  }
269
269
  data, err := fs.ReadFile(strings.TrimPrefix(r.URL.Path, "/"))
270
270
  if err != nil {
271
- RespondError(w, 404, err)
271
+ RespondError(w, r, 404, err)
272
272
  return
273
273
  }
274
274
  fill := r.Form.Get("fill")
@@ -284,7 +284,7 @@ func PageStylesRoute(router *mux.Router, route string) {
284
284
  router.Path(route).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
285
285
  err := r.ParseForm()
286
286
  if err != nil {
287
- RespondError(w, 400, err)
287
+ RespondError(w, r, 400, err)
288
288
  return
289
289
  }
290
290
  key := r.Form.Get("key")
@@ -328,13 +328,17 @@ func createCtx(r *http.Request, route, key string, meta, styles gsx.M) *gsx.Cont
328
328
  return c
329
329
  }
330
330
 
331
- func StatusHandler(status int, h interface{}, meta, styles gsx.M) http.Handler {
331
+ func RegisterStatusHandler(router *mux.Router, comp StatusComponent, styles gsx.M) {
332
- route := fmt.Sprintf("%d", status)
332
+ key := "Status"
333
- gsx.SetClasses(route, styles)
333
+ gsx.SetClasses(key, styles)
334
+ globalStatusComponent = comp
334
- return LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
335
+ router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
335
- c := createCtx(r, route, route, meta, styles)
336
+ c := createCtx(r, key, key, gsx.M{}, styles)
336
- PerformRequest(route, h, c, w, r)
337
+ tags := comp(c, 404, nil)
338
+ w.Header().Set("Content-Type", "text/html")
337
- })).(http.Handler)
339
+ w.WriteHeader(404)
340
+ gsx.Write(c, w, tags)
341
+ })
338
342
  }
339
343
 
340
344
  func Handle(router *mux.Router, method, route string, h interface{}, meta, styles gsx.M) {