Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Underlying type fix #79

Merged
merged 44 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
77d354c
start
mfleader Feb 9, 2024
4fd462e
add tests
mfleader Feb 10, 2024
683d3bd
add test for inline discriminator
mfleader Feb 10, 2024
772951d
add checker for inline field
mfleader Feb 13, 2024
b940938
add jareds tests
mfleader Feb 13, 2024
d455ff8
fix inline discriminator check
mfleader Feb 13, 2024
c04786b
add test for mismatched discriminator types
mfleader Feb 14, 2024
5ed77ff
fix error message
mfleader Feb 14, 2024
74ed3be
fix one-of and subtype discriminator mismatch detection for ints
mfleader Feb 14, 2024
c674375
refactor conditionals
mfleader Feb 14, 2024
d9a17a2
remove dead code
mfleader Feb 14, 2024
37ecda5
try to panic
mfleader Feb 15, 2024
e5b6c98
move validation call and refactor tests
mfleader Feb 15, 2024
f74c4fa
rename tests
mfleader Feb 15, 2024
581c159
upgrade go-assert
mfleader Feb 15, 2024
d280530
tidy up
mfleader Feb 15, 2024
fe31d42
debug
mfleader Feb 16, 2024
3b9a48c
fixed it
mfleader Feb 16, 2024
40937d1
ineffectual err
mfleader Feb 16, 2024
5142636
rm dead code
mfleader Feb 16, 2024
aeb6228
whitespace
mfleader Feb 16, 2024
1b207dc
rm dead code
mfleader Feb 16, 2024
de3831c
rm unused
mfleader Feb 16, 2024
b8e870c
refactor to switch
mfleader Feb 16, 2024
8bc2099
make function private
mfleader Feb 16, 2024
5239cf6
use panicscontains
mfleader Feb 16, 2024
e7a5f5c
small change
mfleader Feb 16, 2024
2aba5c8
add map change to validate type
mfleader Feb 16, 2024
c24d1ee
hopefully fix interface type access
mfleader Feb 17, 2024
68d8078
also lint
mfleader Feb 17, 2024
6551c0d
add oneof scope scope schema
mfleader Feb 19, 2024
2794142
dry out test
mfleader Feb 19, 2024
0eba29f
nolint function length on test
mfleader Feb 19, 2024
fa78446
add inline default
mfleader Feb 19, 2024
690ed39
remove import parens
mfleader Feb 19, 2024
ac7fd1c
finish function refactor
mfleader Feb 20, 2024
9415a48
rm unnecessary lines
mfleader Feb 20, 2024
5464419
improve coverage
mfleader Feb 20, 2024
a82b000
fix lint
mfleader Feb 20, 2024
df18e50
rm comment
mfleader Feb 20, 2024
0635d61
Merge branch 'main' into underlying-type-fix
mfleader Feb 20, 2024
f2fefed
use a string discriminator for test
mfleader Feb 21, 2024
8fd95f0
clarify scope scope schema
mfleader Feb 21, 2024
3c27f11
rm whitespace
mfleader Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 17 additions & 25 deletions schema/oneof.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error)
if reflectedValue.Kind() != reflect.Map {
return result, &ConstraintError{
Message: fmt.Sprintf(
"Invalid type for one-of type: '%s'. Expected map.",
"Invalid type for one-of type: %q. Expected map.",
reflect.TypeOf(data).Name(),
),
}
Expand Down Expand Up @@ -114,28 +114,23 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error)
}
return result, &ConstraintError{
Message: fmt.Sprintf(
"Invalid value for '%s', expected one of: %s",
"Invalid value for %q, expected one of: %s",
o.DiscriminatorFieldNameValue,
strings.Join(validDiscriminators, ", "),
),
}
}

cloneData := maps.Clone(typedData)
if !o.DiscriminatorInlined {
delete(cloneData, o.DiscriminatorFieldNameValue)
}
cloneData := o.deleteDiscriminator(typedData)
unserializedData, err := selectedType.Unserialize(cloneData)
if err != nil {
return result, err
}

unserializedMap, ok := unserializedData.(map[string]any)
if ok {
unserializedMap[o.DiscriminatorFieldNameValue] = discriminator
return unserializedMap, nil
}

return saveConvertTo(unserializedData, o.ReflectedType())
}

Expand All @@ -146,11 +141,7 @@ func (o OneOfSchema[KeyType]) ValidateType(data any) error {
}
dataMap, ok := data.(map[string]any)
if ok {
cloneData := maps.Clone(dataMap)
if !o.DiscriminatorInlined {
delete(cloneData, o.DiscriminatorFieldNameValue)
}
data = cloneData
data = o.deleteDiscriminator(dataMap)
}
if err := underlyingType.Validate(data); err != nil {
return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue))
Expand All @@ -165,11 +156,7 @@ func (o OneOfSchema[KeyType]) SerializeType(data any) (any, error) {
}
dataMap, ok := data.(map[string]any)
if ok {
cloneData := maps.Clone(dataMap)
if !o.DiscriminatorInlined {
delete(cloneData, o.DiscriminatorFieldNameValue)
}
data = cloneData
data = o.deleteDiscriminator(dataMap)
}
serializedData, err := underlyingType.Serialize(data)
if err != nil {
Expand Down Expand Up @@ -280,11 +267,7 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object,
selectedTypeIDAsserted, o.getTypeValues()),
}
}
cloneData := maps.Clone(data)
if !o.DiscriminatorInlined { // Check to see if the discriminator is part of the sub-object.
delete(cloneData, o.DiscriminatorFieldNameValue) // The discriminator isn't part of the object.
}

cloneData := o.deleteDiscriminator(data)
err := selectedSchema.ValidateCompatibility(cloneData)
if err != nil {
return nilKey, nil, &ConstraintError{
Expand Down Expand Up @@ -367,7 +350,7 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err

return nilKey, nil, &ConstraintError{
Message: fmt.Sprintf(
"Invalid type for one-of type: '%s' expected struct or map.",
"Invalid type for one-of type: %q expected struct or map.",
reflect.TypeOf(data).Name(),
),
}
Expand All @@ -381,7 +364,6 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err
}
return myKey, mySchemaObj, nil
}
// else
for key, ref := range o.TypesValue {
underlyingReflectedType := ref.ReflectedType()
if underlyingReflectedType == reflectedType {
Expand Down Expand Up @@ -435,3 +417,13 @@ func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error {
}
return nil
}

func (o OneOfSchema[KeyType]) deleteDiscriminator(mymap map[string]any) map[string]any {
// the discriminator is not a property of the subtype
if !o.DiscriminatorInlined {
cloneData := maps.Clone(mymap)
delete(cloneData, o.DiscriminatorFieldNameValue)
return cloneData
}
return mymap
}
4 changes: 1 addition & 3 deletions schema/oneof_string.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package schema

import (
"reflect"
)
import "reflect"

// OneOfString holds the definition of variable types with an integer discriminator. This type acts as a split for a
// case where multiple possible object types can be present in a field. This type requires that there be a common field
Expand Down
100 changes: 25 additions & 75 deletions schema/oneof_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"go.arcalot.io/assert"
"reflect"
"testing"

"go.flow.arcalot.io/pluginsdk/schema"
Expand Down Expand Up @@ -145,11 +146,11 @@ var oneOfStringTestObjectAType = schema.NewScopeSchema(

func TestOneOfStringUnserialization(t *testing.T) {
data := `{
"s": {
"_type": "B",
"message": "Hello world!"
}
}`
"s": {
"_type": "B",
"message": "Hello world!"
}
}`
var input any
assert.NoError(t, json.Unmarshal([]byte(data), &input))
unserializedData, err := oneOfStringTestObjectAType.Unserialize(input)
Expand All @@ -163,7 +164,6 @@ func TestOneOfStringUnserialization(t *testing.T) {

// Not explicitly using a struct mapped object, but the type is inferred
// by the compiler when the oneOfTestBMappedSchema is in the test suite.
assert.NoError(t, json.Unmarshal([]byte(data), &input))
unserializedData, err = oneOfStringTestObjectASchema.Unserialize(input)
assert.NoError(t, err)
assert.Equals(t, unserializedData.(map[string]any)["s"].(oneOfTestObjectB).Message, "Hello world!")
Expand Down Expand Up @@ -247,75 +247,6 @@ func TestOneOfStringCompatibilityMapValidation(t *testing.T) {
assert.Error(t, oneOfStringTestObjectASchema.ValidateCompatibility(combinedMapAndInvalidSchema))
}

var oneOfStringTestInlineObjectAProperties = map[string]*schema.PropertySchema{
"s": schema.NewPropertySchema(
schema.NewOneOfStringSchema[any](
map[string]schema.Object{
"B": schema.NewRefSchema("B", nil),
"C": schema.NewRefSchema("C", nil),
},
"choice",
true,
),
nil,
true,
nil,
nil,
nil,
nil,
nil,
),
}

var oneOfStringTestInlineObjectAType = schema.NewScopeSchema(
schema.NewStructMappedObjectSchema[oneOfTestObjectA](
"A",
oneOfStringTestInlineObjectAProperties,
),
oneOfTestInlineBMappedSchema,
oneOfTestInlineCMappedSchema,
)

var oneOfStringTestInlineObjectASchema = schema.NewScopeSchema(
schema.NewObjectSchema(
"A",
oneOfStringTestInlineObjectAProperties,
),
oneOfTestInlineBSchema,
oneOfTestInlineCSchema,
)

func TestOneOfStringInline_Unserialization(t *testing.T) {
data := `{
"s": {
"choice": "B",
"message": "Hello world!"
}
}`
var input any
assert.NoError(t, json.Unmarshal([]byte(data), &input))
unserializedData, err := oneOfStringTestInlineObjectAType.Unserialize(input)
assert.NoError(t, err)
assert.Equals(t, unserializedData.(oneOfTestObjectA).S.(oneOfTestInlineObjectB).Message, "Hello world!")
serialized, err := oneOfStringTestInlineObjectAType.Serialize(unserializedData)
assert.NoError(t, err)
unserialized2, err := oneOfStringTestInlineObjectAType.Unserialize(serialized)
assert.NoError(t, err)
assert.Equals(t, unserialized2, unserializedData)

// Not explicitly using a struct mapped object, but the type is inferred
// by the compiler when the oneOfTestBMappedSchema is in the test suite.
assert.NoError(t, json.Unmarshal([]byte(data), &input))
unserializedData, err = oneOfStringTestInlineObjectASchema.Unserialize(input)
assert.NoError(t, err)
assert.Equals(t, unserializedData.(map[string]any)["s"].(oneOfTestInlineObjectB).Message, "Hello world!")
serialized, err = oneOfStringTestInlineObjectASchema.Serialize(unserializedData)
assert.NoError(t, err)
unserialized2, err = oneOfStringTestInlineObjectASchema.Unserialize(serialized)
assert.NoError(t, err)
assert.Equals(t, unserialized2, unserializedData)
}

type inlinedTestObjectA struct {
DType string `json:"d_type"`
OtherFieldA string `json:"other_field_a"`
Expand Down Expand Up @@ -505,6 +436,25 @@ func TestOneOf_NonInlinedNonStructMapped(t *testing.T) {
reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData))
assert.Equals[any](t, reserializedData, serializedData)

var input_mismatched_type any = struct{}{}
err := oneofSchema.Validate(input_mismatched_type)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid type for one-of schema")

var input_invalid_type any = true
error_msg := fmt.Sprintf("Invalid type for one-of type: %q. Expected map.", reflect.TypeOf(input_invalid_type).Kind())
_, err = oneofSchema.Unserialize(input_invalid_type)
assert.Error(t, err)
assert.Contains(t, err.Error(), error_msg)
error_msg = fmt.Sprintf("Invalid type for one-of type: %q expected struct or map.", reflect.TypeOf(input_invalid_type).Kind())
err = oneofSchema.Validate(input_invalid_type)
assert.Error(t, err)
assert.Contains(t, err.Error(), error_msg)

var input_nil any = nil
_, err = oneofSchema.Unserialize(input_nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "bug: data is nil")
}

type inlinedTestIntDiscriminatorA struct {
Expand Down
Loading
Loading