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


e43d4e20 Peter John

4 years ago
get params working
Files changed (5) hide show
  1. api_explorer.go +42 -13
  2. cmd/gromer/main.go +54 -57
  3. example/main.go +17 -2
  4. example/pages/api/todos/get.go +11 -8
  5. http.go +16 -15
api_explorer.go CHANGED
@@ -31,10 +31,10 @@ func Section(title string) *Element {
31
31
  }
32
32
 
33
33
  type ApiDefinition struct {
34
- Method string `json:"method"`
34
+ Method string `json:"method"`
35
- Path string `json:"path"`
35
+ Path string `json:"path"`
36
- PathParams []string `json:"pathParams"`
36
+ PathParams []string `json:"pathParams"`
37
- QueryParams map[string]string `json:"queryParams"`
37
+ Params map[string]interface{} `json:"params"`
38
38
  }
39
39
 
40
40
  func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int, error) {
@@ -233,22 +233,39 @@ func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int
233
233
  }
234
234
  }
235
235
 
236
- const updateQueryParams = (apiCall) => {
236
+ const updateParams = (apiCall) => {
237
237
  const table = document.getElementById("queryParamsTable");
238
- if (!apiCall.queryParams) {
238
+ if (!apiCall.params) {
239
239
  table.innerHTML = "<div style='background-color: rgb(245, 245, 245); padding: 0.25rem; text-align: center; color: gray;'>NONE</div>";
240
240
  } else {
241
241
  table.innerHTML = "";
242
242
  }
243
+ if (apiCall.method === "GET" || apiCall.method === "DELETE") {
244
+ for(const key of Object.keys(apiCall.params)) {
245
+ const row = table.insertRow(0);
246
+ const cell1 = row.insertCell(0);
247
+ const cell2 = row.insertCell(1);
248
+ cell1.style = "width: 30%; border-left: 0px;";
249
+ cell1.class = "text-gray-700";
250
+ cell2.style = "width: 70%;";
251
+ cell1.innerHTML = "<div class='p-1'>" + key + "</div>";
252
+ cell2.innerHTML = "<input id='query-param-" + key + "' class='w-full p-1'>";
253
+ }
254
+ }
243
255
  }
244
256
 
245
257
  const updateBody = (apiCall) => {
258
+ if (apiCall.method !== "GET" && apiCall.method !== "DELETE") {
259
+ window.codeLeft.setValue(JSON.stringify(apiCall.params, 2, 2));
260
+ } else {
246
- const editor = document.getElementById("left");
261
+ window.codeLeft.setValue("");
262
+ }
247
263
  }
248
264
 
249
265
  const init = () => {
250
266
  updatePathParams(window.apiDefs[0]);
251
- updateQueryParams(window.apiDefs[0]);
267
+ updateParams(window.apiDefs[0]);
268
+ updateBody(window.apiDefs[0]);
252
269
  const headersJson = localStorage.getItem("headers");
253
270
  if (headersJson) {
254
271
  const table = document.getElementById("headersTable");
@@ -274,7 +291,7 @@ func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int
274
291
  document.getElementById("api-select").onchange = () => {
275
292
  const apiCall = getCurrentApiCall();
276
293
  updatePathParams(apiCall);
277
- updateQueryParams(apiCall);
294
+ updateParams(apiCall);
278
295
  updateBody(apiCall);
279
296
  }
280
297
 
@@ -293,10 +310,22 @@ func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int
293
310
  const bodyParams = {};
294
311
  if (apiCall.method !== "GET" && apiCall.method != "DELETE") {
295
312
  bodyParams["body"] = window.codeLeft.getValue();
313
+ } else {
314
+ for(const param of apiCall.pathParams) {
315
+ const value = document.getElementById('path-param-' + param).value;
316
+ path = path.replace('{' + param + '}', value);
296
- }
317
+ }
297
- for(const param of apiCall.pathParams) {
318
+ const paramsKeys = Object.keys(apiCall.params);
319
+ if (paramsKeys.length > 0) {
320
+ path += "?";
321
+ paramsKeys.forEach((key, i) => {
298
- const value = document.getElementById('path-param-' + param).value;
322
+ const value = document.getElementById('query-param-' + key).value;
299
- path = path.replace('{' + param + '}', value);
323
+ path += key+"="+value;
324
+ if (i !== paramsKeys.length - 1) {
325
+ path += "&";
326
+ }
327
+ });
328
+ }
300
329
  }
301
330
  localStorage.setItem("headers", JSON.stringify(headers));
302
331
  try {
cmd/gromer/main.go CHANGED
@@ -4,11 +4,15 @@ import (
4
4
  "bytes"
5
5
  "flag"
6
6
  "fmt"
7
+ "go/ast"
8
+ "go/parser"
9
+ "go/token"
7
10
  "io/ioutil"
8
11
  "log"
9
12
  "os"
10
13
  "path/filepath"
11
14
  "strings"
15
+ "unicode"
12
16
 
13
17
  "github.com/gobuffalo/velvet"
14
18
  "github.com/pyros2097/gromer"
@@ -79,7 +83,7 @@ func rewritePkg(pkg string) string {
79
83
  return lastItem
80
84
  }
81
85
 
82
- func getApiFunc(method, route string, params []string) gromer.ApiDefinition {
86
+ func getApiFunc(method, route string, pathParams []string, params map[string]interface{}) gromer.ApiDefinition {
83
87
  muxRoute := bytes.NewBuffer(nil)
84
88
  foundStart := false
85
89
  for _, v := range route {
@@ -95,64 +99,18 @@ func getApiFunc(method, route string, params []string) gromer.ApiDefinition {
95
99
  }
96
100
  return gromer.ApiDefinition{
97
101
  Method: method,
98
- PathParams: params,
99
102
  Path: muxRoute.String(),
103
+ PathParams: pathParams,
104
+ Params: params,
100
105
  }
101
106
  }
102
107
 
103
- // "io/ioutil"
104
- // func migrate() {
108
+ func lowerFirst(s string) string {
105
- // db := context.InitDB()
106
- // ctx := c.Background()
107
- // tx, err := context.BeginTransaction(db, ctx)
108
- // if err != nil {
109
- // panic(err)
110
- // }
111
- // files, err := ioutil.ReadDir("./migrations")
112
- // if err != nil {
113
- // panic(err)
114
- // }
115
- // for _, f := range files {
109
+ for i, v := range s {
116
- // data, err := ioutil.ReadFile("./migrations/" + f.Name())
117
- // if err != nil {
118
- // panic(err)
119
- // }
120
- // tx.MustExec(string(data))
121
- // }
122
- // err = tx.Commit()
123
- // if err != nil {
124
- // panic(err)
125
- // }
126
- // }
127
-
128
- // "github.com/bxcodec/faker/v3"
129
- // func seed() {
130
- // db := context.InitDB()
131
- // ctx := c.Background()
132
- // tx, err := context.BeginTransaction(db, ctx)
133
- // if err != nil {
134
- // panic(err)
135
- // }
136
- // reqContext := context.ReqContext{
110
+ return string(unicode.ToLower(v)) + s[i+1:]
137
- // Tx: tx,
111
+ }
138
- // UserID: "123",
112
+ return ""
139
- // }
140
- // for i := 0; i < 20; i++ {
141
- // ti := todos.TodoInput{}
142
- // err := faker.FakeData(&ti)
143
- // if err != nil {
144
- // panic(err)
145
- // }
146
- // _, _, err = todos.POST(reqContext, ti)
147
- // if err != nil {
148
- // panic(err)
149
- // }
150
- // }
151
- // err = tx.Commit()
152
- // if err != nil {
153
- // panic(err)
154
- // }
155
- // }
113
+ }
156
114
 
157
115
  func main() {
158
116
  moduleName := ""
@@ -190,14 +148,50 @@ func main() {
190
148
  pkg = "pages"
191
149
  }
192
150
  routePath := rewritePath(path)
193
- params := gromer.GetRouteParams(routePath)
151
+ pathParams := gromer.GetRouteParams(routePath)
194
152
  routes = append(routes, &Route{
195
153
  Method: method,
196
154
  Path: routePath,
197
155
  Pkg: rewritePkg(pkg),
198
156
  })
199
157
  if strings.Contains(path, "/api/") {
158
+ data, err := ioutil.ReadFile(filesrc)
159
+ if err != nil {
160
+ panic(err)
161
+ }
162
+ fset := token.NewFileSet()
163
+ f, err := parser.ParseFile(fset, "", string(data), parser.AllErrors)
164
+ if err != nil {
165
+ panic(err)
166
+ }
167
+ var params map[string]interface{}
168
+ mapsOfInputParams := map[string]map[string]interface{}{}
169
+ for _, d := range f.Decls {
170
+ if decl, ok := d.(*ast.GenDecl); ok {
171
+ for _, spec := range decl.Specs {
172
+ if typeSpec, ok := spec.(*ast.TypeSpec); ok {
173
+ if st, ok := typeSpec.Type.(*ast.StructType); ok {
174
+ mapsOfInputParams[typeSpec.Name.Name] = map[string]interface{}{}
175
+ for _, f := range st.Fields.List {
176
+ fieldName := lowerFirst(f.Names[0].Name)
177
+ mapsOfInputParams[typeSpec.Name.Name][fieldName] = fmt.Sprintf("%+s", f.Type)
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ if decl, ok := d.(*ast.FuncDecl); ok {
184
+ if decl.Name.Name == method {
185
+ list := decl.Type.Params.List
186
+ lastParam := list[len(list)-1]
187
+ lastParamTypeName := fmt.Sprintf("%+s", lastParam.Type)
188
+ if v, ok := mapsOfInputParams[lastParamTypeName]; ok {
189
+ params = v
190
+ }
191
+ }
192
+ }
193
+ }
200
- apiDefs = append(apiDefs, getApiFunc(method, path, params))
194
+ apiDefs = append(apiDefs, getApiFunc(method, path, pathParams, params))
201
195
  }
202
196
  }
203
197
  return nil
@@ -299,6 +293,9 @@ func apiDefinitions() []gromer.ApiDefinition {
299
293
  Method: "{{api.Method}}",
300
294
  Path: "{{api.Path}}",
301
295
  PathParams: []string{ {{#each api.PathParams as |param| }}"{{param}}", {{/each}} },
296
+ Params: map[string]interface{}{
297
+ {{#each api.Params }}"{{ @key }}": "{{ @value }}", {{/each}}
298
+ },
302
299
  },{{/each}}
303
300
  }
304
301
  }
example/main.go CHANGED
@@ -14,10 +14,10 @@ import (
14
14
  "gocloud.dev/server"
15
15
 
16
16
  "github.com/pyros2097/gromer/example/context"
17
- "github.com/pyros2097/gromer/example/pages/about"
18
- "github.com/pyros2097/gromer/example/pages/api/todos/_todoId_"
19
17
  "github.com/pyros2097/gromer/example/pages/api/todos"
20
18
  "github.com/pyros2097/gromer/example/pages"
19
+ "github.com/pyros2097/gromer/example/pages/about"
20
+ "github.com/pyros2097/gromer/example/pages/api/todos/_todoId_"
21
21
 
22
22
  )
23
23
 
@@ -92,26 +92,41 @@ func apiDefinitions() []gromer.ApiDefinition {
92
92
  Method: "DELETE",
93
93
  Path: "/api/todos/{todoId}",
94
94
  PathParams: []string{ "todoId", },
95
+ Params: map[string]interface{}{
96
+
97
+ },
95
98
  },
96
99
  {
97
100
  Method: "GET",
98
101
  Path: "/api/todos/{todoId}",
99
102
  PathParams: []string{ "todoId", },
103
+ Params: map[string]interface{}{
104
+ "show": "string",
105
+ },
100
106
  },
101
107
  {
102
108
  Method: "PUT",
103
109
  Path: "/api/todos/{todoId}",
104
110
  PathParams: []string{ "todoId", },
111
+ Params: map[string]interface{}{
112
+ "completed": "bool",
113
+ },
105
114
  },
106
115
  {
107
116
  Method: "GET",
108
117
  Path: "/api/todos",
109
118
  PathParams: []string{ },
119
+ Params: map[string]interface{}{
120
+ "limit": "int", "offset": "int",
121
+ },
110
122
  },
111
123
  {
112
124
  Method: "POST",
113
125
  Path: "/api/todos",
114
126
  PathParams: []string{ },
127
+ Params: map[string]interface{}{
128
+ "text": "string",
129
+ },
115
130
  },
116
131
  }
117
132
  }
example/pages/api/todos/get.go CHANGED
@@ -6,16 +6,19 @@ import (
6
6
  "github.com/pyros2097/gromer/example/db"
7
7
  )
8
8
 
9
- // type GetParams struct {
9
+ type GetParams struct {
10
- // Limit int `json:"limit"`
10
+ Limit int `json:"limit"`
11
- // Offset int `json:"limit"`
11
+ Offset int `json:"offset"`
12
- // }
12
+ }
13
- // , params GetParams
14
13
 
15
- func GET(ctx context.Context) ([]*db.Todo, int, error) {
14
+ func GET(ctx context.Context, params GetParams) ([]*db.Todo, int, error) {
15
+ limit := params.Limit
16
+ if limit == 0 {
17
+ limit = 10
18
+ }
16
19
  todos, err := db.Query.ListTodos(ctx, db.ListTodosParams{
17
- Limit: int32(10),
20
+ Limit: int32(limit),
18
- Offset: int32(0),
21
+ Offset: int32(params.Offset),
19
22
  })
20
23
  if err != nil {
21
24
  return nil, 500, err
http.go CHANGED
@@ -58,8 +58,8 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
58
58
  if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
59
59
  err := json.NewDecoder(r.Body).Decode(instance.Interface())
60
60
  if err != nil {
61
- RespondError(w, 500, err)
61
+ RespondError(w, 400, err)
62
- return 500, err
62
+ return 400, err
63
63
  }
64
64
  } else if r.Method == "GET" {
65
65
  rv := instance.Elem()
@@ -69,22 +69,23 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
69
69
  jsonValue := r.URL.Query().Get(jsonName)
70
70
  if f.Kind() == reflect.String {
71
71
  f.SetString(jsonValue)
72
+ } else if f.Kind() == reflect.Int64 || f.Kind() == reflect.Int32 || f.Kind() == reflect.Int {
73
+ base := 64
72
- } else if f.Kind() == reflect.Int64 {
74
+ if f.Kind() == reflect.Int32 {
73
- v, err := strconv.ParseInt(jsonValue, 10, 64)
74
- if err != nil {
75
+ base = 32
75
- RespondError(w, 500, err)
76
- return 500, err
77
76
  }
77
+ if jsonValue == "" {
78
+ f.SetInt(0)
79
+ } else {
80
+ v, err := strconv.ParseInt(jsonValue, 10, base)
81
+ if err != nil {
82
+ RespondError(w, 400, err)
83
+ return 400, err
84
+ }
78
- f.SetInt(v)
85
+ f.SetInt(v)
79
- } else if f.Kind() == reflect.Int32 {
80
- v, err := strconv.ParseInt(jsonValue, 10, 32)
81
- if err != nil {
82
- RespondError(w, 500, err)
83
- return 500, err
84
86
  }
85
- f.SetInt(v)
86
87
  } else {
87
- panic("Uknown query param: " + jsonValue)
88
+ panic("Uknown query param: " + jsonName + " " + jsonValue)
88
89
  }
89
90
  }
90
91
  }