~repos /gromer

#golang#htmx#ssr

git clone https://pyrossh.dev/repos/gromer.git
Discussions: https://groups.google.com/g/rust-embed-devs

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


37285004 Peter John

4 years ago
add utils
Files changed (4) hide show
  1. go.mod +1 -0
  2. go.sum +3 -0
  3. utils.go +76 -0
  4. utils_test.go +105 -0
go.mod CHANGED
@@ -12,6 +12,7 @@ require (
12
12
  github.com/google/uuid v1.3.0
13
13
  github.com/gorilla/mux v1.8.0
14
14
  github.com/iancoleman/strcase v0.2.0
15
+ github.com/imdario/mergo v0.3.12 // indirect
15
16
  github.com/lib/pq v1.10.4
16
17
  github.com/markbates/inflect v1.0.4
17
18
  github.com/microcosm-cc/bluemonday v1.0.15 // indirect
go.sum CHANGED
@@ -283,6 +283,8 @@ github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHL
283
283
  github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
284
284
  github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
285
285
  github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
286
+ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
287
+ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
286
288
  github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
287
289
  github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
288
290
  github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
@@ -795,6 +797,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
795
797
  gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
796
798
  gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
797
799
  gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
800
+ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
798
801
  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
799
802
  gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
800
803
  gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
utils.go ADDED
@@ -0,0 +1,76 @@
1
+ package gromer
2
+
3
+ import (
4
+ "reflect"
5
+ "regexp"
6
+ "strings"
7
+ "time"
8
+
9
+ "github.com/go-playground/validator/v10"
10
+ "github.com/iancoleman/strcase"
11
+ "github.com/imdario/mergo"
12
+ )
13
+
14
+ var Validator = validator.New()
15
+ var ValidatorErrorMap = map[string]string{
16
+ "required": "is required",
17
+ }
18
+ var upperRegex = regexp.MustCompile("^[^a-z]*$")
19
+
20
+ type timeTransformer struct {
21
+ }
22
+
23
+ func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
24
+ if typ == reflect.TypeOf(time.Time{}) {
25
+ return func(dst, src reflect.Value) error {
26
+ if dst.CanSet() {
27
+ srcResult := src.MethodByName("IsZero").Call([]reflect.Value{})
28
+ if !srcResult[0].Bool() {
29
+ dst.Set(src)
30
+ }
31
+ }
32
+ return nil
33
+ }
34
+ }
35
+ return nil
36
+ }
37
+
38
+ func Merge(dst interface{}, src interface{}) error {
39
+ err := mergo.Merge(dst, src, mergo.WithOverwriteWithEmptyValue)
40
+ if err != nil {
41
+ return err
42
+ }
43
+ return Validate(dst)
44
+ }
45
+
46
+ func Validate(dst interface{}) error {
47
+ return Validator.Struct(dst)
48
+ }
49
+
50
+ func RegisterValidation(k, msg string, validate func(fl validator.FieldLevel) bool) {
51
+ ValidatorErrorMap[k] = msg
52
+ Validator.RegisterValidation(k, validate)
53
+ }
54
+
55
+ func GetValidationError(err validator.ValidationErrors) map[string]string {
56
+ emap := map[string]string{}
57
+ for _, e := range err {
58
+ parts := strings.Split(e.StructNamespace(), ".")
59
+ lowerParts := []string{}
60
+ for _, p := range parts[1:] {
61
+ if upperRegex.MatchString(p) {
62
+ lowerParts = append(lowerParts, strings.ToLower(p))
63
+ } else {
64
+ lowerParts = append(lowerParts, strcase.ToLowerCamel(p))
65
+ }
66
+ }
67
+ k := strings.Join(lowerParts, ".")
68
+ errorMsg, ok := ValidatorErrorMap[e.Tag()]
69
+ if ok {
70
+ emap[k] = errorMsg
71
+ } else {
72
+ emap[k] = "is not valid" // e.Error()
73
+ }
74
+ }
75
+ return emap
76
+ }
utils_test.go ADDED
@@ -0,0 +1,105 @@
1
+ package gromer
2
+
3
+ import (
4
+ "regexp"
5
+ "testing"
6
+ "time"
7
+
8
+ "github.com/go-playground/validator/v10"
9
+ "github.com/stretchr/testify/assert"
10
+ )
11
+
12
+ func init() {
13
+ pinCodeRegex := regexp.MustCompile("^[1-9]{1}[0-9]{2}\\s{0,1}[0-9]{3}$")
14
+ RegisterValidation("pincode", "is not in valid format", func(fl validator.FieldLevel) bool {
15
+ return pinCodeRegex.MatchString(fl.Field().String())
16
+ })
17
+ }
18
+
19
+ func TestUpperRegex(t *testing.T) {
20
+ assert.Equal(t, true, upperRegex.MatchString("PAN"))
21
+ assert.Equal(t, false, upperRegex.MatchString("PaN"))
22
+ }
23
+
24
+ type Todo struct {
25
+ ID string `json:"id" validate:"required"`
26
+ Pincode string `json:"pincode" validate:"required,pincode"`
27
+ PAN string `json:"pan" validate:"required"`
28
+ CreatedAt time.Time `json:"createdAt"`
29
+ }
30
+
31
+ func TestValidator(t *testing.T) {
32
+ assert.NoError(t, Validator.Var("560001", "pincode"))
33
+ assert.EqualError(t, Validator.Var("ABCD", "pincode"), "Key: '' Error:Field validation for '' failed on the 'pincode' tag")
34
+ }
35
+
36
+ func TestValidate(t *testing.T) {
37
+ todo := &Todo{
38
+ ID: "123",
39
+ Pincode: "",
40
+ PAN: "",
41
+ }
42
+ err := Validate(todo)
43
+ assert.EqualError(t, err, "Key: 'Todo.Pincode' Error:Field validation for 'Pincode' failed on the 'required' tag\nKey: 'Todo.PAN' Error:Field validation for 'PAN' failed on the 'required' tag")
44
+ validationErrors := err.(validator.ValidationErrors)
45
+ assert.Equal(t, GetValidationError(validationErrors), map[string]string{
46
+ "pincode": "is required",
47
+ "pan": "is required",
48
+ })
49
+
50
+ todo.Pincode = "AWS"
51
+ err = Validate(todo)
52
+ assert.EqualError(t, err, "Key: 'Todo.Pincode' Error:Field validation for 'Pincode' failed on the 'pincode' tag\nKey: 'Todo.PAN' Error:Field validation for 'PAN' failed on the 'required' tag")
53
+ validationErrors = err.(validator.ValidationErrors)
54
+ assert.EqualValues(t, map[string]string{
55
+ "pincode": "is not in valid format",
56
+ "pan": "is required",
57
+ }, GetValidationError(validationErrors))
58
+ }
59
+
60
+ type Note struct {
61
+ ID string `json:"id"`
62
+ Text string `json:"text"`
63
+ CreatedAt time.Time `json:"createdAt"`
64
+ }
65
+
66
+ func TestMerge(t *testing.T) {
67
+ note := &Note{
68
+ ID: "",
69
+ Text: "",
70
+ CreatedAt: time.Time{},
71
+ }
72
+ err := Merge(note, &Note{
73
+ ID: "1",
74
+ Text: "1",
75
+ CreatedAt: time.Date(2020, 10, 10, 0, 0, 0, 0, time.UTC),
76
+ })
77
+ assert.NoError(t, err)
78
+ assert.EqualValues(t, &Note{
79
+ ID: "1",
80
+ Text: "1",
81
+ CreatedAt: time.Date(2020, 10, 10, 0, 0, 0, 0, time.UTC),
82
+ }, note)
83
+ err = Merge(note, &Note{
84
+ ID: "2",
85
+ Text: "2",
86
+ CreatedAt: time.Date(2020, 11, 11, 0, 0, 0, 0, time.UTC),
87
+ })
88
+ assert.NoError(t, err)
89
+ assert.EqualValues(t, &Note{
90
+ ID: "2",
91
+ Text: "2",
92
+ CreatedAt: time.Date(2020, 11, 11, 0, 0, 0, 0, time.UTC),
93
+ }, note)
94
+ err = Merge(note, &Note{
95
+ ID: "2",
96
+ Text: "",
97
+ CreatedAt: time.Time{},
98
+ })
99
+ assert.NoError(t, err)
100
+ assert.EqualValues(t, &Note{
101
+ ID: "2",
102
+ Text: "",
103
+ CreatedAt: time.Time{},
104
+ }, note)
105
+ }