Skip to content

Commit acfa208

Browse files
author
Aashutosh
committed
update jsonutil
1 parent 73b5e2a commit acfa208

File tree

8 files changed

+198
-35
lines changed

8 files changed

+198
-35
lines changed

data_source.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/gookit/goutil/maputil"
1717
"github.com/gookit/goutil/reflects"
1818
"github.com/gookit/goutil/strutil"
19+
"github.com/guptaaashutosh/go_validate/jsonutil"
1920
)
2021

2122
const (
@@ -39,14 +40,21 @@ const (
3940
// data (Un)marshal func
4041
var (
4142
Marshal MarshalFunc = json.Marshal
42-
Unmarshal UnmarshalFunc = json.Unmarshal
43+
//original
44+
// Unmarshal UnmarshalFunc = json.Unmarshal
45+
Unmarshal UnmarshalFunc = jsonutil.Unmarshal
4346
)
4447

4548
type (
4649
// MarshalFunc define
4750
MarshalFunc func(v any) ([]byte, error)
51+
//original
4852
// UnmarshalFunc define
49-
UnmarshalFunc func(data []byte, ptr any) error
53+
// UnmarshalFunc func(data []byte, ptr any) error
54+
// Modified
55+
// Customization: use custom JSON Unmarshal to handle all unmarshalling errors.
56+
// UnmarshalFunc define
57+
UnmarshalFunc func(r *http.Request, data []byte, v interface{}) (int, error)
5058
)
5159

5260
// DataFace data source interface definition
@@ -133,13 +141,24 @@ func (d *MapData) Validation(err ...error) *Validation {
133141
return NewValidation(d)
134142
}
135143

144+
//original
145+
// BindJSON binds v to the JSON data in the request body.
146+
// It calls json.Unmarshal and sets the value of v.
147+
// func (d *MapData) BindJSON(ptr any) error {
148+
// if len(d.bodyJSON) == 0 {
149+
// return nil
150+
// }
151+
// return Unmarshal(d.bodyJSON, ptr)
152+
// }
153+
// Modified
154+
// Customization: use custom JSON Unmarshal to handle all unmarshalling errors.
136155
// BindJSON binds v to the JSON data in the request body.
137156
// It calls json.Unmarshal and sets the value of v.
138-
func (d *MapData) BindJSON(ptr any) error {
157+
func (d *MapData) BindJSON(ptr interface{}) (int, error) {
139158
if len(d.bodyJSON) == 0 {
140-
return nil
159+
return 0, nil
141160
}
142-
return Unmarshal(d.bodyJSON, ptr)
161+
return Unmarshal(nil, d.bodyJSON, ptr)
143162
}
144163

145164
/*************************************************************

filtering.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func (r *FilterRule) Apply(v *Validation) (err error) {
161161

162162
// dont need check default value
163163
if !v.CheckDefault {
164-
v.safeData[field] = newVal // save validated value.
164+
v.SaferData[field] = newVal // save validated value.
165165
continue
166166
}
167167
}
@@ -185,7 +185,11 @@ func (r *FilterRule) Apply(v *Validation) (err error) {
185185
if err != nil {
186186
return err
187187
}
188-
188+
// Customization: We need to overwrite original field value with filtered/converted value because filtered/converted value was not being loaded in struct while calling BindSafeData().
189+
if v.SaferData[field] != "" {
190+
// save filtered value.
191+
v.SaferData[field] = newVal
192+
}
189193
// save filtered value.
190194
v.filteredData[field] = newVal
191195
}

jsonutil/unmarshal.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Package jsonutil provides common utilities for properly handling JSON payloads in HTTP body.
2+
package jsonutil
3+
4+
import (
5+
"bytes"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"io"
10+
"io/ioutil"
11+
"mime"
12+
"net/http"
13+
"strings"
14+
)
15+
16+
// Unmarshal provides a common implementation of JSON unmarshalling
17+
// with well defined error handling.
18+
// Unmarshal parses the JSON-encoded data and stores the result
19+
// in the value pointed to by v. If v is nil or not a pointer,
20+
// Unmarshal returns an InvalidUnmarshalError.
21+
// It can unmarshal data available in request/data param.
22+
func Unmarshal(r *http.Request, data []byte, v interface{}) (int, error) {
23+
// ensure that some data is provided for unmarshalling
24+
if r == nil && data == nil {
25+
return http.StatusUnsupportedMediaType, fmt.Errorf("no data provided")
26+
} else if r != nil && data != nil {
27+
// if someone sends multiple data for unmarshalling then it gives below error
28+
return http.StatusUnsupportedMediaType, fmt.Errorf("multiple data provided for unmarshalling not supported")
29+
}
30+
31+
var d = &json.Decoder{}
32+
// if "r" request is not empty then it will read data from request body to unmarshal that data into object provided in "v".
33+
if r != nil && data == nil {
34+
// read request body as []byte.
35+
bodyBytes, err := ioutil.ReadAll(r.Body)
36+
if err != nil {
37+
return http.StatusBadRequest, fmt.Errorf("error reading request body")
38+
}
39+
// as we are only allowed to read request body once so we need to set request body after reading for further use of request data
40+
// it will set request body which we have got in "bodyBytes" above.
41+
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
42+
43+
// check if content type is valid or not.
44+
if !HasContentType(r, "application/json") {
45+
return http.StatusUnsupportedMediaType, fmt.Errorf("content-type is not application/json")
46+
}
47+
48+
d = json.NewDecoder(bytes.NewReader(bodyBytes))
49+
} else if data != nil && r == nil {
50+
d = json.NewDecoder(bytes.NewReader(data))
51+
}
52+
53+
// DisallowUnknownFields causes the Decoder to return an error when the destination
54+
// is a struct and the input contains object keys which do not match any
55+
// non-ignored, exported fields in the destination.
56+
// d.DisallowUnknownFields()
57+
58+
// handle errors returned while decoding data into object.
59+
if err := d.Decode(&v); err != nil {
60+
var syntaxErr *json.SyntaxError
61+
var unmarshalError *json.UnmarshalTypeError
62+
switch {
63+
case errors.As(err, &syntaxErr):
64+
return http.StatusBadRequest, fmt.Errorf("malformed json at position %v", syntaxErr.Offset)
65+
case errors.Is(err, io.ErrUnexpectedEOF):
66+
return http.StatusBadRequest, fmt.Errorf("malformed json")
67+
case errors.As(err, &unmarshalError):
68+
return http.StatusBadRequest, fmt.Errorf("invalid value %v at position %v", unmarshalError.Field, unmarshalError.Offset)
69+
case strings.HasPrefix(err.Error(), "json: unknown field"):
70+
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
71+
return http.StatusBadRequest, fmt.Errorf("unknown field %s", fieldName)
72+
case errors.Is(err, io.EOF):
73+
return http.StatusBadRequest, fmt.Errorf("body must not be empty")
74+
case err.Error() == "http: request body too large":
75+
return http.StatusRequestEntityTooLarge, err
76+
default:
77+
return http.StatusInternalServerError, fmt.Errorf("failed to decode json %v", err)
78+
}
79+
}
80+
if d.More() {
81+
return http.StatusBadRequest, fmt.Errorf("body must contain only one JSON object")
82+
}
83+
return http.StatusOK, nil
84+
}
85+
86+
// check for valid content type.
87+
func HasContentType(r *http.Request, mimetype string) bool {
88+
contentType := r.Header.Get("Content-type")
89+
if contentType == "" {
90+
return mimetype == "application/octet-stream"
91+
}
92+
93+
for _, v := range strings.Split(contentType, ",") {
94+
t, _, err := mime.ParseMediaType(v)
95+
if err != nil {
96+
break
97+
}
98+
if t == mimetype {
99+
return true
100+
}
101+
}
102+
return false
103+
}

util.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,15 @@ func CalcLength(val any) int {
229229
func valueCompare(srcVal, dstVal any, op string) (ok bool) {
230230
srcVal = indirectValue(srcVal)
231231

232-
// string compare
233-
if str1, ok := srcVal.(string); ok {
234-
str2, err := strutil.ToString(dstVal)
235-
if err != nil {
236-
return false
237-
}
238-
239-
return strutil.VersionCompare(str1, str2, op)
240-
}
232+
// Customization: unused process - String values cannot be compare like integers values
233+
// if str1, ok := srcVal.(string); ok {
234+
// str2, err := strutil.ToString(dstVal)
235+
// if err != nil {
236+
// return false
237+
// }
238+
239+
// return strutil.VersionCompare(str1, str2, op)
240+
// }
241241

242242
// as int or float to compare
243243
return mathutil.Compare(srcVal, dstVal, op)

validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func newEmpty() *Validation {
169169
// trans: StdTranslator,
170170
trans: NewTranslator(),
171171
// validated data
172-
safeData: make(map[string]any),
172+
SaferData: make(map[string]any),
173173
// validator names
174174
validators: make(map[string]int8, 16),
175175
// filtered data

validating.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (v *Validation) Validate(scene ...string) bool {
8888

8989
v.hasValidated = true
9090
if v.hasError { // clear safe data on error.
91-
v.safeData = make(map[string]any)
91+
v.SaferData = make(map[string]any)
9292
}
9393
return v.IsSuccess()
9494
}
@@ -149,7 +149,7 @@ func (r *Rule) Apply(v *Validation) (stop bool) {
149149
// dont need check default value
150150
if !v.CheckDefault {
151151
// save validated value.
152-
v.safeData[field] = val
152+
v.SaferData[field] = val
153153
continue
154154
}
155155

@@ -185,7 +185,7 @@ func (r *Rule) Apply(v *Validation) (stop bool) {
185185
// Todo: Update validation and filtering flow
186186
if val != nil{
187187
// Customization: We need to bind all data
188-
v.safeData[field] = val
188+
v.SaferData[field] = val
189189
}
190190
// empty value AND skip on empty.
191191
if r.skipEmpty && isNotRequired && IsEmpty(val) {
@@ -195,7 +195,7 @@ func (r *Rule) Apply(v *Validation) (stop bool) {
195195
// validate field value
196196
if r.valueValidate(field, name, val, v) {
197197
if val != nil {
198-
v.safeData[field] = val // save validated value.
198+
v.SaferData[field] = val // save validated value.
199199
}
200200
} else { // build and collect error message
201201
v.AddError(field, r.validator, r.errorMessage(field, r.validator, v))

validation.go

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ type Validation struct {
3838
data DataFace
3939
// all validated fields list
4040
// fields []string
41-
41+
//orginal
4242
// save filtered/validated safe data
43-
safeData M
43+
// safeData M
44+
SaferData M // Customization: We need to update the typo of safeData to SaferData to access the variable outside the package for manual validation.
4445
// filtered clean data
4546
filteredData M
4647
// save user custom set default values
@@ -116,7 +117,7 @@ func (v *Validation) ResetResult() {
116117
v.hasFiltered = false
117118
v.hasValidated = false
118119
// result data
119-
v.safeData = make(map[string]any)
120+
v.SaferData = make(map[string]any)
120121
v.filteredData = make(map[string]any)
121122
}
122123

@@ -418,7 +419,7 @@ func (v *Validation) tryGet(key string) (val any, exist, zero bool) {
418419
}
419420

420421
// find from validated data. (such as has default value)
421-
if val, ok := v.safeData[key]; ok {
422+
if val, ok := v.SaferData[key]; ok {
422423
return val, true, false
423424
}
424425

@@ -461,7 +462,7 @@ func (v *Validation) Safe(key string) (val any, ok bool) {
461462
if v.data == nil { // check input data
462463
return
463464
}
464-
val, ok = v.safeData[key]
465+
val, ok = v.SaferData[key]
465466
return
466467
}
467468

@@ -478,23 +479,46 @@ func (v *Validation) GetSafe(key string) any {
478479
}
479480

480481
// BindStruct binding safe data to an struct.
481-
func (v *Validation) BindStruct(ptr any) error {
482+
// func (v *Validation) BindStruct(ptr any) error {
483+
// return v.BindSafeData(ptr)
484+
// }
485+
// Modified
486+
// Customization: use custom JSON Unmarshal to handle all unmarshalling errors.
487+
// BindStruct binding safe data to an struct.
488+
func (v *Validation) BindStruct(ptr interface{}) (int, error) {
482489
return v.BindSafeData(ptr)
483490
}
484491

492+
//original
485493
// BindSafeData binding safe data to an struct.
486-
func (v *Validation) BindSafeData(ptr any) error {
487-
if len(v.safeData) == 0 { // no safe data.
488-
return nil
494+
// func (v *Validation) BindSafeData(ptr any) error {
495+
// if len(v.safeData) == 0 { // no safe data.
496+
// return nil
497+
// }
498+
499+
// // to json bytes
500+
// bts, err := Marshal(v.safeData)
501+
// if err != nil {
502+
// return err
503+
// }
504+
505+
// return Unmarshal(bts, ptr)
506+
// }
507+
// Modified
508+
// Customization: use custom JSON Unmarshal to handle all unmarshalling errors.
509+
// BindSafeData binding safe data to an struct.
510+
func (v *Validation) BindSafeData(ptr interface{}) (int, error) {
511+
if len(v.SaferData) == 0 { // no safe data.
512+
return 0, nil
489513
}
490514

491515
// to json bytes
492-
bts, err := Marshal(v.safeData)
516+
bts, err := Marshal(v.SaferData)
493517
if err != nil {
494-
return err
518+
return 0, err
495519
}
496520

497-
return Unmarshal(bts, ptr)
521+
return Unmarshal(nil, bts, ptr)
498522
}
499523

500524
// Set value by key
@@ -566,7 +590,7 @@ func (v *Validation) IsFail() bool { return v.hasError }
566590
func (v *Validation) IsSuccess() bool { return !v.hasError }
567591

568592
// SafeData get all validated safe data
569-
func (v *Validation) SafeData() M { return v.safeData }
593+
func (v *Validation) SafeData() M { return v.SaferData }
570594

571595
// FilteredData return filtered data.
572596
func (v *Validation) FilteredData() M {

validators.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,14 +901,27 @@ func IsCIDR(s string) bool {
901901
return err == nil
902902
}
903903

904+
//original
905+
// IsJSON check if the string is valid JSON (note: uses json.Unmarshal).
906+
// func IsJSON(s string) bool {
907+
// if s == "" {
908+
// return false
909+
// }
910+
911+
// var js json.RawMessage
912+
// return Unmarshal([]byte(s), &js) == nil
913+
// }
914+
// Modified
915+
// Customization: use custom JSON Unmarshal to handle all unmarshalling errors.
904916
// IsJSON check if the string is valid JSON (note: uses json.Unmarshal).
905917
func IsJSON(s string) bool {
906918
if s == "" {
907919
return false
908920
}
909921

910922
var js json.RawMessage
911-
return Unmarshal([]byte(s), &js) == nil
923+
_, err := Unmarshal(nil, []byte(s), &js)
924+
return err == nil
912925
}
913926

914927
// HasLowerCase check string has lower case

0 commit comments

Comments
 (0)