Add jsonschema Validate
This commit is contained in:
34
check_valid.go
Normal file
34
check_valid.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/ianlancetaylor/jsonschema"
|
||||||
|
"github.com/ianlancetaylor/jsonschema/draft7"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Validate(schema string, dataAny any) error {
|
||||||
|
data := StructToMapRecursive(dataAny)
|
||||||
|
|
||||||
|
var v any
|
||||||
|
if err := json.Unmarshal([]byte(schema), &v); err != nil {
|
||||||
|
log.Printf("Failed to decode json %#v", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vldtor, err := jsonschema.SchemaFromJSON(draft7.SchemaID, nil, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate
|
||||||
|
valid := vldtor.Validate(data)
|
||||||
|
|
||||||
|
if valid != nil {
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
46
check_valid_test.go
Normal file
46
check_valid_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sampleSchema = `
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["id", "name"]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestRequiredName (t *testing.T) {
|
||||||
|
data := map[string]any {
|
||||||
|
"id": "myid",
|
||||||
|
"age": 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Validate(sampleSchema, data); err == nil || err.Error() != `required/name: missing required field "name"` {
|
||||||
|
t.Fatalf("Should throw `%s` but got %s", "required/name: missing required field \"name\"", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassingValidate (t *testing.T) {
|
||||||
|
data := map[string]any {
|
||||||
|
"id": "myid",
|
||||||
|
"name": "Hey",
|
||||||
|
"age": 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Validate(sampleSchema, data); err != nil {
|
||||||
|
t.Fatal("Should have not thrown an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
5
go.mod
5
go.mod
@@ -2,4 +2,7 @@ module git.gsuntres.com/general/commons
|
|||||||
|
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require go.mongodb.org/mongo-driver/v2 v2.5.0
|
require (
|
||||||
|
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||||
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -2,5 +2,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5 h1:x2QxKV4w/sBEwwUUmBH/8cFjeOBZqwaB5dz5rcuFspU=
|
||||||
|
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5/go.mod h1:KtN3dTgXsLnC5GJBRNmOPd/HUInNcQ84lUCrKJPrvDc=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
|||||||
42
struct.go
42
struct.go
@@ -4,10 +4,50 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
// "encoding/json"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StructToMapRecursive given a struct or a primitive will return the equivalent
|
||||||
|
// map[string]any of the struct or the primitive as is.
|
||||||
|
func StructToMapRecursive(obj any) any {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
|
||||||
|
// Handle pointers by getting the underlying element
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() { return nil }
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
result := make(map[string]any)
|
||||||
|
t := v.Type()
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
// Skip unexported fields (private fields)
|
||||||
|
if field.PkgPath != "" { continue }
|
||||||
|
|
||||||
|
// Recurse into the field's value
|
||||||
|
result[field.Name] = StructToMapRecursive(v.Field(i).Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
result := make([]any, v.Len())
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
result[i] = StructToMapRecursive(v.Index(i).Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Return basic types (int, string, etc.) as is
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MapToStruct will convert a map[string]any to a struct.
|
// MapToStruct will convert a map[string]any to a struct.
|
||||||
func MapToStruct(m map[string]any, o any) error {
|
func MapToStruct(m map[string]any, o any) error {
|
||||||
b, err := bson.Marshal(m)
|
b, err := bson.Marshal(m)
|
||||||
|
|||||||
@@ -4,6 +4,42 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestStructToMapRecursive(t *testing.T) {
|
||||||
|
type O struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &O{
|
||||||
|
Name: "Nick",
|
||||||
|
Age: 15,
|
||||||
|
Active: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
mAny := StructToMapRecursive(o)
|
||||||
|
|
||||||
|
switch mAny.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
m := mAny.(map[string]any)
|
||||||
|
if v, ok := m["Name"]; !ok || v != "Nick" {
|
||||||
|
t.Fatalf("Unexpected map %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := m["Age"]; !ok || v != 15 {
|
||||||
|
t.Fatalf("Unexpected map %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := m["Active"]; !ok || v != true {
|
||||||
|
t.Fatalf("Unexpected map %v", v)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatal("is not map")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestMapToStruct(t *testing.T) {
|
func TestMapToStruct(t *testing.T) {
|
||||||
o := map[string]any {
|
o := map[string]any {
|
||||||
"name": "name1",
|
"name": "name1",
|
||||||
|
|||||||
Reference in New Issue
Block a user