~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.


87943fb6 Peter John

3 years ago
improve stuff
_example/components/Status.go ADDED
@@ -0,0 +1,59 @@
1
+ package not_found_404
2
+
3
+ import (
4
+ . "github.com/pyros2097/gromer/gsx"
5
+ )
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
+ var StatusStyles = M{
41
+ "container": "border-t-2 border-gray-100 text-2xl",
42
+ "row": "flex flex-row group",
43
+ "button-1": "ml-4 text-gray-400",
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",
48
+ }
49
+
50
+ func Status(c *Context, status int, err error) []*Tag {
51
+ return c.Render(`
52
+ <div id="error" class="notfound " hx-swap-oob="true">
53
+ <h1>"Oops something wen't wrong"</h1>
54
+ <h2 class="mt-6">
55
+ <a class="link" href="/">"Go Back"</a>
56
+ </h2>
57
+ </div>
58
+ `), 404, nil
59
+ }
_example/main.go CHANGED
@@ -12,7 +12,6 @@ import (
12
12
  "github.com/pyros2097/gromer/_example/assets"
13
13
  "github.com/pyros2097/gromer/_example/components"
14
14
  "github.com/pyros2097/gromer/_example/containers"
15
- "github.com/pyros2097/gromer/_example/routes/404"
16
15
  "github.com/pyros2097/gromer/_example/routes"
17
16
  "github.com/pyros2097/gromer/_example/routes/about"
18
17
 
@@ -28,7 +27,7 @@ func init() {
28
27
  func main() {
29
28
  baseRouter := mux.NewRouter()
30
29
  baseRouter.Use(gromer.LogMiddleware)
31
- baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
30
+ // baseRouter.NotFoundHandler = gromer.StatusHandler(404, not_found_404.GET, not_found_404.Meta, not_found_404.Styles)
32
31
 
33
32
  staticRouter := baseRouter.NewRoute().Subrouter()
34
33
  staticRouter.Use(gromer.CacheMiddleware)
_example/makefile CHANGED
@@ -1,6 +1,8 @@
1
1
  setup:
2
2
  go install github.com/mitranim/gow@latest
3
3
  go install github.com/pyros2097/gromer/cmd/gromer@latest
4
+ go install github.com/playwright-community/playwright-go/cmd/playwright@latest
5
+ playwright install --with-deps
4
6
 
5
7
  update:
6
8
  gromer -pkg github.com/pyros2097/gromer/_example
_example/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Example
2
2
 
3
- This example demonstrates gromer with a simple web app.
3
+ This example demonstrates gromer with a simple todo web app.
4
4
 
5
5
  # Requirements
6
6
 
_example/routes/404/get.go DELETED
@@ -1,23 +0,0 @@
1
- package not_found_404
2
-
3
- import (
4
- . "github.com/pyros2097/gromer/gsx"
5
- )
6
-
7
- var (
8
- Meta = M{
9
- "title": "Page Not Found",
10
- }
11
- Styles = M{}
12
- )
13
-
14
- func GET(c *Context) ([]*Tag, int, error) {
15
- return c.Render(`
16
- <main class="box center">
17
- <h1>Page Not Found</h1>
18
- <h2 class="mt-6">
19
- <a class="is-underlined" href="/">Go Back</a>
20
- </h2>
21
- </main>
22
- `), 404, nil
23
- }
_example/routes/get.go CHANGED
@@ -105,6 +105,8 @@ func GET(c *Context, params GetParams) ([]*Tag, int, error) {
105
105
  </div>
106
106
  </div>
107
107
  </main>
108
+ <div id="error">
109
+ </div>
108
110
  <footer class="footer">
109
111
  <span class="subtitle">"Written by "
110
112
  <a class="link" href="https://github.com/pyrossh/">"pyrossh"</a>
_example/routes/post.go CHANGED
@@ -1,11 +1,10 @@
1
1
  package routes
2
2
 
3
3
  import (
4
- "fmt"
5
-
6
4
  _ "github.com/pyros2097/gromer/_example/components"
7
5
  "github.com/pyros2097/gromer/_example/services/todos"
8
6
  . "github.com/pyros2097/gromer/gsx"
7
+ "github.com/rotisserie/eris"
9
8
  )
10
9
 
11
10
  type PostParams struct {
@@ -93,5 +92,5 @@ func POST(c *Context, params PostParams) ([]*Tag, int, error) {
93
92
  <Todo />
94
93
  `), 200, nil
95
94
  }
96
- return nil, 404, fmt.Errorf("Intent not specified: %s", params.Intent)
95
+ return nil, 404, eris.Errorf("Intent not specified: %s", params.Intent)
97
96
  }
_example/services/todos/todo.go CHANGED
@@ -2,10 +2,10 @@ package todos
2
2
 
3
3
  import (
4
4
  "context"
5
- "errors"
6
5
  "time"
7
6
 
8
7
  "github.com/google/uuid"
8
+ "github.com/rotisserie/eris"
9
9
  )
10
10
 
11
11
  type Todo struct {
@@ -48,7 +48,7 @@ func UpdateTodo(ctx context.Context, id string, params UpdateTodoParams) (*Todo,
48
48
  globalTodos[updateIndex].UpdatedAt = time.Now()
49
49
  return globalTodos[updateIndex], nil
50
50
  }
51
- return nil, errors.New("Todo not found")
51
+ return nil, eris.New("Todo not found")
52
52
  }
53
53
 
54
54
  func DeleteTodo(ctx context.Context, id string) (string, error) {
@@ -62,7 +62,7 @@ func DeleteTodo(ctx context.Context, id string) (string, error) {
62
62
  globalTodos = append(globalTodos[:deleteIndex], globalTodos[deleteIndex+1:]...)
63
63
  return id, nil
64
64
  }
65
- return "", errors.New("Todo not found")
65
+ return "", eris.New("Todo not found")
66
66
  }
67
67
 
68
68
  func GetTodo(ctx context.Context, id string) (*Todo, error) {
@@ -71,7 +71,7 @@ func GetTodo(ctx context.Context, id string) (*Todo, error) {
71
71
  return todo, nil
72
72
  }
73
73
  }
74
- return nil, errors.New("Todo not found")
74
+ return nil, eris.New("Todo not found")
75
75
  }
76
76
 
77
77
  type GetAllTodoParams struct {
_example/tests/todos_test.go ADDED
@@ -0,0 +1,47 @@
1
+ package tests
2
+
3
+ import (
4
+ "fmt"
5
+ "testing"
6
+
7
+ "github.com/playwright-community/playwright-go"
8
+ )
9
+
10
+ func TestTodo(t *testing.T) {
11
+ pw, err := playwright.Run()
12
+ if err != nil {
13
+ t.Fatalf("could not start playwright: %v", err)
14
+ }
15
+ browser, err := pw.Chromium.Launch()
16
+ if err != nil {
17
+ t.Fatalf("could not launch browser: %v", err)
18
+ }
19
+ page, err := browser.NewPage()
20
+ if err != nil {
21
+ t.Fatalf("could not create page: %v", err)
22
+ }
23
+ if _, err = page.Goto("https://news.ycombinator.com"); err != nil {
24
+ t.Fatalf("could not goto: %v", err)
25
+ }
26
+ entries, err := page.QuerySelectorAll(".athing")
27
+ if err != nil {
28
+ t.Fatalf("could not get entries: %v", err)
29
+ }
30
+ for i, entry := range entries {
31
+ titleElement, err := entry.QuerySelector("td.title > a")
32
+ if err != nil {
33
+ t.Fatalf("could not get title element: %v", err)
34
+ }
35
+ title, err := titleElement.TextContent()
36
+ if err != nil {
37
+ t.Fatalf("could not get text content: %v", err)
38
+ }
39
+ fmt.Printf("%d: %s\n", i+1, title)
40
+ }
41
+ if err = browser.Close(); err != nil {
42
+ t.Fatalf("could not close browser: %v", err)
43
+ }
44
+ if err = pw.Stop(); err != nil {
45
+ t.Fatalf("could not stop Playwright: %v", err)
46
+ }
47
+ }
collection/collection.go CHANGED
@@ -6,6 +6,7 @@ import (
6
6
  "io"
7
7
  "reflect"
8
8
 
9
+ "github.com/rotisserie/eris"
9
10
  "github.com/rs/zerolog/log"
10
11
  "gocloud.dev/docstore"
11
12
  _ "gocloud.dev/docstore/gcpfirestore"
@@ -44,7 +45,7 @@ func (q *Query[S]) One(ctx context.Context) (S, int, error) {
44
45
  }
45
46
  arr := reflect.ValueOf(results)
46
47
  if arr.Len() == 0 {
47
- return *new(S), 404, fmt.Errorf("%s not found", q.Parent.Type.Name())
48
+ return *new(S), 404, eris.Errorf("%s not found", q.Parent.Type.Name())
48
49
  }
49
50
  return arr.Index(0).Interface().(S), 200, nil
50
51
  }
go.mod CHANGED
@@ -4,9 +4,7 @@ go 1.18
4
4
 
5
5
  require (
6
6
  github.com/alecthomas/participle/v2 v2.0.0-beta.3
7
- github.com/alecthomas/repr v0.1.0
8
7
  github.com/felixge/httpsnoop v1.0.1
9
- github.com/go-errors/errors v1.4.2
10
8
  github.com/go-playground/validator/v10 v10.9.0
11
9
  github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
12
10
  github.com/goneric/stack v0.0.0-20220131052059-5990ae324dbd
@@ -14,8 +12,9 @@ require (
14
12
  github.com/gorilla/handlers v1.5.1
15
13
  github.com/gorilla/mux v1.8.0
16
14
  github.com/imdario/mergo v0.3.12
17
- github.com/jinzhu/copier v0.3.5
15
+ github.com/rotisserie/eris v0.5.4
18
16
  github.com/rs/zerolog v1.26.1
17
+ github.com/samber/lo v1.21.0
19
18
  github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734
20
19
  github.com/stretchr/testify v1.7.0
21
20
  gocloud.dev v0.24.0
@@ -46,7 +45,6 @@ require (
46
45
  github.com/pkg/errors v0.9.1 // indirect
47
46
  github.com/pmezard/go-difflib v1.0.0 // indirect
48
47
  github.com/russross/blackfriday v1.5.2 // indirect
49
- github.com/samber/lo v1.21.0 // indirect
50
48
  github.com/sergi/go-diff v1.2.0 // indirect
51
49
  github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 // indirect
52
50
  github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect
go.sum CHANGED
@@ -103,7 +103,6 @@ github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJz
103
103
  github.com/alecthomas/participle/v2 v2.0.0-beta.3 h1:9HnyNuDsqOG8sl63Dz+KubqHhU8aWqsrjKdecim8GW0=
104
104
  github.com/alecthomas/participle/v2 v2.0.0-beta.3/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
105
105
  github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
106
- github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
107
106
  github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
108
107
  github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
109
108
  github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
@@ -174,8 +173,6 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
174
173
  github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
175
174
  github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
176
175
  github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
177
- github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
178
- github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
179
176
  github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
180
177
  github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
181
178
  github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -308,8 +305,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
308
305
  github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
309
306
  github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
310
307
  github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
311
- github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
312
- github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
313
308
  github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
314
309
  github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
315
310
  github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -358,6 +353,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
358
353
  github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
359
354
  github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
360
355
  github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
356
+ github.com/rotisserie/eris v0.5.4 h1:Il6IvLdAapsMhvuOahHWiBnl1G++Q0/L5UIkI5mARSk=
357
+ github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9G1A4g9s=
361
358
  github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
362
359
  github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
363
360
  github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
@@ -397,6 +394,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
397
394
  github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
398
395
  github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
399
396
  github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
397
+ github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
400
398
  github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
401
399
  github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
402
400
  github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
gsx/gsx.go CHANGED
@@ -9,6 +9,7 @@ import (
9
9
  "strconv"
10
10
  "strings"
11
11
 
12
+ "github.com/rotisserie/eris"
12
13
  "github.com/samber/lo"
13
14
  )
14
15
 
@@ -84,7 +85,7 @@ func (comp ComponentFunc) Render(c *Context, tag *Tag) []*Tag {
84
85
  } else {
85
86
  s, ok := data.(string)
86
87
  if !ok {
87
- panic(fmt.Errorf("expected component %s: prop %s to be of type string but got %+v ", comp.Name, arg, data))
88
+ panic(eris.Errorf("expected component %s: prop %s to be of type string but got %+v ", comp.Name, arg, data))
88
89
  }
89
90
  value, _ = strconv.Atoi(s)
90
91
  }
@@ -97,7 +98,7 @@ func (comp ComponentFunc) Render(c *Context, tag *Tag) []*Tag {
97
98
  } else {
98
99
  s, ok := data.(string)
99
100
  if !ok {
100
- panic(fmt.Errorf("expected component %s: prop %s to be of type string but got %+v ", comp.Name, arg, data))
101
+ panic(eris.Errorf("expected component %s: prop %s to be of type string but got %+v ", comp.Name, arg, data))
101
102
  }
102
103
  value, _ = strconv.ParseBool(s)
103
104
  }
@@ -141,7 +142,7 @@ func Write(c *Context, w io.Writer, tags []*Tag) {
141
142
  w.Write([]byte(fmt.Sprintf(`<script src="%s"></script>`, src)))
142
143
  }
143
144
  }
144
- w.Write([]byte(`</head><body>`))
145
+ w.Write([]byte(`</head><body _="on htmx:error(errorInfo) put errorInfo.xhr.response into #error">`))
145
146
  }
146
147
  out := RenderString(tags)
147
148
  w.Write([]byte(out))
gsx/parser.go CHANGED
@@ -1,12 +1,12 @@
1
1
  package gsx
2
2
 
3
3
  import (
4
- "fmt"
5
4
  "strings"
6
5
 
7
6
  "github.com/alecthomas/participle/v2"
8
7
  "github.com/alecthomas/participle/v2/lexer"
9
8
  "github.com/goneric/stack"
9
+ "github.com/rotisserie/eris"
10
10
  )
11
11
 
12
12
  type Module struct {
@@ -205,7 +205,7 @@ func processTree(nodes []*AstNode) []*Tag {
205
205
  if n.Close.Name == prevTag.Name {
206
206
  prevTag, _ = stack.Pop()
207
207
  } else {
208
- panic(fmt.Errorf("Brackets not matching for tag %s in line %d:%d, prevTag: %s", n.Close.Name, n.Close.Pos.Line, n.Close.Pos.Column, prevTag.Name))
208
+ panic(eris.Errorf("Brackets not matching for tag %s in line %d:%d, prevTag: %s", n.Close.Name, n.Close.Pos.Line, n.Close.Pos.Column, prevTag.Name))
209
209
  }
210
210
  } else if n.Content != nil {
211
211
  newTag := &Tag{
gsx/twx.go CHANGED
@@ -419,8 +419,8 @@ var twClassLookup = MS{
419
419
  "select-text": "user-select: text;",
420
420
  "select-all": "user-select: all;",
421
421
  "select-auto": "user-select: auto;",
422
- "w-screen": "100vw",
422
+ "w-screen": "width: 100vw;",
423
- "h-screen": "100vh",
423
+ "h-screen": "height: 100vh;",
424
424
  "static": "position: static;",
425
425
  "fixed": "position: fixed;",
426
426
  "absolute": "position: absolute;",
http.go CHANGED
@@ -19,15 +19,14 @@ import (
19
19
  "time"
20
20
 
21
21
  "github.com/felixge/httpsnoop"
22
- "github.com/go-errors/errors"
23
22
  "github.com/go-playground/validator/v10"
24
23
  "github.com/google/uuid"
25
24
  "github.com/gorilla/handlers"
26
25
  "github.com/gorilla/mux"
27
26
  "github.com/pyros2097/gromer/gsx"
27
+ "github.com/rotisserie/eris"
28
28
  "github.com/rs/zerolog"
29
29
  "github.com/rs/zerolog/log"
30
- "github.com/rs/zerolog/pkgerrors"
31
30
  "github.com/segmentio/go-camelcase"
32
31
  "xojoc.pw/useragent"
33
32
  )
@@ -57,7 +56,9 @@ type RouteDefinition struct {
57
56
  func init() {
58
57
  IsCloundRun = os.Getenv("K_REVISION") != ""
59
58
  info, _ = debug.ReadBuildInfo()
60
- zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
59
+ zerolog.ErrorStackMarshaler = func(err error) interface{} {
60
+ return eris.ToJSON(err, true)
61
+ }
61
62
  if IsCloundRun {
62
63
  zerolog.LevelFieldName = "severity"
63
64
  zerolog.TimestampFieldName = "timestamp"
@@ -79,22 +80,39 @@ func getFunctionName(temp interface{}) string {
79
80
  }
80
81
 
81
82
  func RespondError(w http.ResponseWriter, status int, err error) {
82
- w.Header().Set("Content-Type", "application/json")
83
+ w.Header().Set("Content-Type", "text/html")
83
84
  w.WriteHeader(status) // always write status last
84
85
  merror := map[string]interface{}{
85
86
  "error": err.Error(),
86
87
  }
87
88
  if status >= 500 {
88
89
  merror["error"] = "Internal Server Error"
90
+ formattedStr := eris.ToCustomString(err, eris.StringFormat{
89
- sterr, _ := err.(*errors.Error)
91
+ Options: eris.FormatOptions{
92
+ WithTrace: true,
93
+ InvertOutput: true,
94
+ InvertTrace: true,
95
+ },
96
+ MsgStackSep: "\n",
97
+ PreStackSep: "\t",
98
+ StackElemSep: " | ",
99
+ ErrorSep: "\n",
100
+ })
90
- log.Error().Msg(err.Error() + "\n" + sterr.ErrorStack())
101
+ log.Error().Msg(err.Error() + "\n" + formattedStr)
91
102
  }
92
103
  validationErrors, ok := err.(validator.ValidationErrors)
93
104
  if ok {
94
105
  merror["error"] = GetValidationError(validationErrors)
95
106
  }
107
+ c := gsx.NewContext(context.TODO(), &gsx.HX{})
108
+ c.Set("funcName", "error")
96
- data, _ := json.Marshal(merror)
109
+ c.Set("error", err.Error())
110
+ tags := c.Render(`
111
+ <div style="color: red;">
112
+ <h1>{error}</h1>
113
+ </div>
114
+ `)
97
- w.Write(data)
115
+ gsx.Write(c, w, tags)
98
116
  }
99
117
 
100
118
  func GetRouteParams(route string) []string {
@@ -179,7 +197,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
179
197
  return
180
198
  }
181
199
  } else {
182
- RespondError(w, 400, fmt.Errorf("Illegal Content-Type tag found %s", contentType))
200
+ RespondError(w, 400, eris.Errorf("Illegal Content-Type tag found %s", contentType))
183
201
  return
184
202
  }
185
203
  c.Set("params", instance.Elem().Interface())
@@ -190,7 +208,7 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
190
208
  responseStatus := values[1].Interface().(int)
191
209
  responseError := values[2].Interface()
192
210
  if responseError != nil {
193
- RespondError(w, responseStatus, responseError.(error))
211
+ RespondError(w, responseStatus, eris.Wrap(responseError.(error), "Render failed"))
194
212
  return
195
213
  }
196
214
  w.Header().Set("Content-Type", "text/html")
@@ -215,7 +233,7 @@ func LogMiddleware(next http.Handler) http.Handler {
215
233
  defer func() {
216
234
  if err := recover(); err != nil {
217
235
  log.Error().Msgf("%s 599 %s %s", r.Method, ua, url)
218
- RespondError(w, 599, errors.Errorf(fmt.Sprintf("%+v", err)))
236
+ RespondError(w, 599, eris.Errorf("%+v", err))
219
237
  }
220
238
  }()
221
239
  m := httpsnoop.CaptureMetrics(next, w, r)
@@ -237,37 +255,6 @@ func CacheMiddleware(next http.Handler) http.Handler {
237
255
  })
238
256
  }
239
257
 
240
- func StatusHandler(h interface{}) http.Handler {
241
- return LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
242
- ctx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
243
- var hx *gsx.HX
244
- if r.Header.Get("HX-Request") == "true" {
245
- hx = &gsx.HX{
246
- Boosted: r.Header.Get("HX-Boosted") == "true",
247
- CurrentUrl: r.Header.Get("HX-Current-URL"),
248
- Prompt: r.Header.Get("HX-Prompt"),
249
- Target: r.Header.Get("HX-Target"),
250
- TriggerName: r.Header.Get("HX-Trigger-Name"),
251
- TriggerID: r.Header.Get("HX-Trigger"),
252
- }
253
- }
254
- renderContext := gsx.NewContext(ctx, hx)
255
- values := reflect.ValueOf(h).Call([]reflect.Value{reflect.ValueOf(renderContext)})
256
- response := values[0].Interface()
257
- responseStatus := values[1].Interface().(int)
258
- responseError := values[2].Interface()
259
- if responseError != nil {
260
- RespondError(w, responseStatus, responseError.(error))
261
- return
262
- }
263
- w.Header().Set("Content-Type", "text/html")
264
-
265
- // This has to be at end always after headers are set
266
- w.WriteHeader(responseStatus)
267
- gsx.Write(renderContext, w, response.([]*gsx.Tag))
268
- })).(http.Handler)
269
- }
270
-
271
258
  func StaticRoute(router *mux.Router, path string, fs embed.FS) {
272
259
  router.PathPrefix(path).Methods("GET").Handler(http.StripPrefix(path, http.FileServer(http.FS(fs))))
273
260
  }
@@ -315,32 +302,46 @@ func ComponentStylesRoute(router *mux.Router, route string) {
315
302
  })
316
303
  }
317
304
 
305
+ func createCtx(r *http.Request, route, key string, meta, styles gsx.M) *gsx.Context {
306
+ newCtx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
307
+ var hx *gsx.HX
308
+ if r.Header.Get("HX-Request") == "true" {
309
+ hx = &gsx.HX{
310
+ Boosted: r.Header.Get("HX-Boosted") == "true",
311
+ CurrentUrl: r.Header.Get("HX-Current-URL"),
312
+ Prompt: r.Header.Get("HX-Prompt"),
313
+ Target: r.Header.Get("HX-Target"),
314
+ TriggerName: r.Header.Get("HX-Trigger-Name"),
315
+ TriggerID: r.Header.Get("HX-Trigger"),
316
+ }
317
+ }
318
+ c := gsx.NewContext(newCtx, hx)
319
+ c.Set("funcName", route)
320
+ c.Set("requestId", uuid.NewString())
321
+ c.Link("stylesheet", GetPageStylesUrl(key), "", "")
322
+ c.Link("stylesheet", GetComponentsStylesUrl(), "", "")
323
+ c.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
324
+ c.Script("/gromer/js/htmx@1.7.0.js", false)
325
+ c.Script("/gromer/js/hyperscript@0.9.6.js", false)
326
+ // c.Script("/gromer/js/alpinejs@3.9.6.js", true)
327
+ c.Meta(meta)
328
+ return c
329
+ }
330
+
331
+ func StatusHandler(status int, h interface{}, meta, styles gsx.M) http.Handler {
332
+ route := fmt.Sprintf("%d", status)
333
+ gsx.SetClasses(route, styles)
334
+ return LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
335
+ c := createCtx(r, route, route, meta, styles)
336
+ PerformRequest(route, h, c, w, r)
337
+ })).(http.Handler)
338
+ }
339
+
318
340
  func Handle(router *mux.Router, method, route string, h interface{}, meta, styles gsx.M) {
319
341
  key := camelcase.Camelcase(route)
320
342
  gsx.SetClasses(key, styles)
321
343
  router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
322
- newCtx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
323
- var hx *gsx.HX
324
- if r.Header.Get("HX-Request") == "true" {
325
- hx = &gsx.HX{
326
- Boosted: r.Header.Get("HX-Boosted") == "true",
327
- CurrentUrl: r.Header.Get("HX-Current-URL"),
328
- Prompt: r.Header.Get("HX-Prompt"),
329
- Target: r.Header.Get("HX-Target"),
330
- TriggerName: r.Header.Get("HX-Trigger-Name"),
331
- TriggerID: r.Header.Get("HX-Trigger"),
332
- }
333
- }
334
- c := gsx.NewContext(newCtx, hx)
344
+ c := createCtx(r, route, key, meta, styles)
335
- c.Set("funcName", route)
336
- c.Set("requestId", uuid.NewString())
337
- c.Link("stylesheet", GetPageStylesUrl(key), "", "")
338
- c.Link("stylesheet", GetComponentsStylesUrl(), "", "")
339
- c.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
340
- c.Script("/gromer/js/htmx@1.7.0.js", false)
341
- c.Script("/gromer/js/hyperscript@0.9.6.js", false)
342
- // c.Script("/gromer/js/alpinejs@3.9.6.js", true)
343
- c.Meta(meta)
344
345
  PerformRequest(route, h, c, w, r)
345
346
  }).Methods(method)
346
347
  }