Skip to content

Commit

Permalink
add json validation
Browse files Browse the repository at this point in the history
  • Loading branch information
fredcarle committed Mar 5, 2024
1 parent eb872f5 commit f7a6a45
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 1 deletion.
17 changes: 16 additions & 1 deletion client/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func validateFieldSchema(val any, field SchemaFieldDescription) (any, error) {
}

switch field.Kind {
case FieldKind_DocID, FieldKind_NILLABLE_STRING, FieldKind_NILLABLE_BLOB, FieldKind_NILLABLE_JSON:
case FieldKind_DocID, FieldKind_NILLABLE_STRING, FieldKind_NILLABLE_BLOB:
return getString(val)

case FieldKind_STRING_ARRAY:
Expand Down Expand Up @@ -243,6 +243,9 @@ func validateFieldSchema(val any, field SchemaFieldDescription) (any, error) {

case FieldKind_FOREIGN_OBJECT_ARRAY:
return nil, NewErrFieldOrAliasToFieldNotExist(field.Name)

case FieldKind_NILLABLE_JSON:
return getJSON(val)
}

return nil, NewErrUnhandledType("FieldKind", field.Kind)
Expand Down Expand Up @@ -318,6 +321,18 @@ func getDateTime(v any) (time.Time, error) {
return time.Parse(time.RFC3339, s)
}

func getJSON(v any) (string, error) {
s, err := getString(v)
if err != nil {
return "", err
}
val, err := fastjson.Parse(s)
if err != nil {
return "", NewErrInvalidJSONPaylaod(s)
}
return val.String(), nil
}

func getArray[T any](
v any,
typeGetter func(any) (T, error),
Expand Down
55 changes: 55 additions & 0 deletions client/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ var (
Typ: LWW_REGISTER,
Kind: FieldKind_NILLABLE_INT,
},
{
Name: "Custom",
Typ: LWW_REGISTER,
Kind: FieldKind_NILLABLE_JSON,
},
},
},
}
Expand Down Expand Up @@ -135,3 +140,53 @@ func TestNewDocsFromJSON_WithObjectInsteadOfArray_Error(t *testing.T) {
_, err := NewDocsFromJSON(testJSONObj, schemaDescriptions[0])
require.ErrorContains(t, err, "value doesn't contain array; it contains object")
}

func TestNewFromJSON_WithValidJSONFieldValue_NoError(t *testing.T) {
objWithJSONField := []byte(`{
"Name": "John",
"Age": 26,
"Custom": "{\"tree\":\"maple\", \"age\": 260}"
}`)
doc, err := NewDocFromJSON(objWithJSONField, schemaDescriptions[0])
if err != nil {
t.Error("Error creating new doc from JSON:", err)
return
}

// check field/value
// fields
assert.Equal(t, doc.fields["Name"].Name(), "Name")
assert.Equal(t, doc.fields["Name"].Type(), LWW_REGISTER)
assert.Equal(t, doc.fields["Age"].Name(), "Age")
assert.Equal(t, doc.fields["Age"].Type(), LWW_REGISTER)
assert.Equal(t, doc.fields["Custom"].Name(), "Custom")
assert.Equal(t, doc.fields["Custom"].Type(), LWW_REGISTER)

//values
assert.Equal(t, doc.values[doc.fields["Name"]].Value(), "John")
assert.Equal(t, doc.values[doc.fields["Name"]].IsDocument(), false)
assert.Equal(t, doc.values[doc.fields["Age"]].Value(), int64(26))
assert.Equal(t, doc.values[doc.fields["Age"]].IsDocument(), false)
assert.Equal(t, doc.values[doc.fields["Custom"]].Value(), "{\"tree\":\"maple\",\"age\":260}")
assert.Equal(t, doc.values[doc.fields["Custom"]].IsDocument(), false)
}

func TestNewFromJSON_WithInvalidJSONFieldValue_Error(t *testing.T) {
objWithJSONField := []byte(`{
"Name": "John",
"Age": 26,
"Custom": "{\"tree\":\"maple, \"age\": 260}"
}`)
_, err := NewDocFromJSON(objWithJSONField, schemaDescriptions[0])
require.ErrorContains(t, err, "invalid JSON payload. Payload: {\"tree\":\"maple, \"age\": 260}")
}

func TestNewFromJSON_WithInvalidJSONFieldValueSimpleString_Error(t *testing.T) {
objWithJSONField := []byte(`{
"Name": "John",
"Age": 26,
"Custom": "blah"
}`)
_, err := NewDocFromJSON(objWithJSONField, schemaDescriptions[0])
require.ErrorContains(t, err, "invalid JSON payload. Payload: blah")
}
6 changes: 6 additions & 0 deletions client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
errInvalidCRDTType string = "CRDT type not supported"
errFailedToUnmarshalCollection string = "failed to unmarshal collection json"
errOperationNotPermittedOnNamelessCols string = "operation not permitted on nameless collection"
errInvalidJSONPayload string = "invalid JSON payload"
)

// Errors returnable from this package.
Expand All @@ -49,6 +50,7 @@ var (
ErrInvalidDeleteTarget = errors.New("the target document to delete is of invalid type")
ErrMalformedDocID = errors.New("malformed document ID, missing either version or cid")
ErrInvalidDocIDVersion = errors.New("invalid document ID version")
ErrInvalidJSONPayload = errors.New(errInvalidJSONPayload)
)

// NewErrFieldNotExist returns an error indicating that the given field does not exist.
Expand Down Expand Up @@ -149,3 +151,7 @@ func NewErrInvalidCRDTType(name, crdtType string) error {
func NewErrCRDTKindMismatch(cType, kind string) error {
return errors.New(fmt.Sprintf(errCRDTKindMismatch, cType, kind))
}

func NewErrInvalidJSONPaylaod(payload string) error {
return errors.New(errInvalidJSONPayload, errors.NewKV("Payload", payload))
}
109 changes: 109 additions & 0 deletions tests/integration/mutation/create/field_kinds/field_kind_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package field_kinds

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/tests/integration"
)

func TestMutationCreate_WithJSONFieldGivenValidJSON_NoError(t *testing.T) {
test := testUtils.TestCase{
Description: "Create mutation with JSON field given a valid JSON string.",
Actions: []any{
testUtils.SchemaUpdate{
Schema: `
type Users {
name: String
custom: JSON
}
`,
},
testUtils.Request{
Request: `mutation {
create_Users(input: {name: "John", custom: "{\"tree\": \"maple\", \"age\": 250}"}) {
_docID
name
custom
}
}`,
Results: []map[string]any{
{
"_docID": "bae-b2dff82c-ab26-5d06-a29a-02aa4807dde2",
"custom": "{\"tree\":\"maple\",\"age\":250}",
"name": "John",
},
},
},
},
}

testUtils.ExecuteTestCase(t, test)
}

func TestMutationCreate_WithJSONFieldGivenInvalidJSON_Error(t *testing.T) {
test := testUtils.TestCase{
Description: "Create mutation with JSON field given a valid JSON string.",
Actions: []any{
testUtils.SchemaUpdate{
Schema: `
type Users {
name: String
custom: JSON
}
`,
},
testUtils.Request{
Request: `mutation {
create_Users(input: {name: "John", custom: "{\"tree\": \"maple, \"age\": 250}"}) {
_docID
name
custom
}
}`,
ExpectedError: `Argument "input" has invalid value {name: "John", custom: "{\"tree\": \"maple, \"age\": 250}"}.
In field "custom": Expected type "JSON", found "{\"tree\": \"maple, \"age\": 250}".`,
},
},
}

testUtils.ExecuteTestCase(t, test)
}

func TestMutationCreate_WithJSONFieldGivenSimpleString_Error(t *testing.T) {
test := testUtils.TestCase{
Description: "Create mutation with JSON field given a valid JSON string.",
Actions: []any{
testUtils.SchemaUpdate{
Schema: `
type Users {
name: String
custom: JSON
}
`,
},
testUtils.Request{
Request: `mutation {
create_Users(input: {name: "John", custom: "blah"}) {
_docID
name
custom
}
}`,
ExpectedError: `Argument "input" has invalid value {name: "John", custom: "blah"}.
In field "custom": Expected type "JSON", found "blah".`,
},
},
}

testUtils.ExecuteTestCase(t, test)
}

0 comments on commit f7a6a45

Please sign in to comment.