diff --git a/check_valid.go b/check_valid.go new file mode 100644 index 0000000..d900714 --- /dev/null +++ b/check_valid.go @@ -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 +} diff --git a/check_valid_test.go b/check_valid_test.go new file mode 100644 index 0000000..d7489a7 --- /dev/null +++ b/check_valid_test.go @@ -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") + } +} diff --git a/go.mod b/go.mod index 03cd6ba..cdc2352 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module git.gsuntres.com/general/commons 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 +) diff --git a/go.sum b/go.sum index 65a6c8c..7c17640 100644 --- a/go.sum +++ b/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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 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/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= diff --git a/struct.go b/struct.go index 3378d27..8420822 100644 --- a/struct.go +++ b/struct.go @@ -4,10 +4,50 @@ import ( "log" "reflect" "strings" - // "encoding/json" - + "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. func MapToStruct(m map[string]any, o any) error { b, err := bson.Marshal(m) diff --git a/struct_test.go b/struct_test.go index 79124b4..f145485 100644 --- a/struct_test.go +++ b/struct_test.go @@ -4,6 +4,42 @@ import ( "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) { o := map[string]any { "name": "name1",