From 77d354cb4fe1883ce576463a55c1144de8347ef0 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 9 Feb 2024 17:20:02 -0500 Subject: [PATCH 01/43] start --- schema/oneof.go | 53 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 9a71d66..d034e32 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -2,6 +2,7 @@ package schema import ( "fmt" + "maps" "reflect" "strings" ) @@ -246,10 +247,11 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) error { selectedTypeIDAsserted, o.getTypeValues()), } } + cloneData := maps.Clone(data) if selectedSchema.Properties()[o.DiscriminatorFieldNameValue] == nil { // Check to see if the discriminator is part of the sub-object. - delete(data, o.DiscriminatorFieldNameValue) // The discriminator isn't part of the object. + delete(cloneData, o.DiscriminatorFieldNameValue) // The discriminator isn't part of the object. } - err := selectedSchema.ValidateCompatibility(data) + err := selectedSchema.ValidateCompatibility(cloneData) if err != nil { return &ConstraintError{ Message: fmt.Sprintf( @@ -342,12 +344,12 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } var foundKey *KeyType - for key, ref := range o.TypesValue { - underlyingReflectedType := ref.ReflectedType() - if underlyingReflectedType == reflectedType { - keyValue := key - foundKey = &keyValue + if reflectedType.Kind() == reflect.Map { + myKey, mySchemaObj, err := o.mapUnderlyingType(data.(map[string]any)) + if err != nil { + return *foundKey, nil, err } + return myKey, mySchemaObj, nil } if foundKey == nil { var defaultValue KeyType @@ -371,3 +373,40 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } return *foundKey, o.TypesValue[*foundKey], nil } + +func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, Object, error) { + // Validate that it has the discriminator field. + // If it doesn't, fail + // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object + + var foundKey KeyType + selectedTypeID := data[o.DiscriminatorFieldNameValue] + if selectedTypeID == nil { + return foundKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), + } + } + // Ensure it's the correct type + selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) + if !ok { + return foundKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", + o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), + } + } + foundKey = selectedTypeIDAsserted + + // Find the object that's associated with the selected type + selectedSchema := o.TypesValue[selectedTypeIDAsserted] + if selectedSchema == nil { + return foundKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", + selectedTypeIDAsserted, o.getTypeValues()), + } + } + + return foundKey, selectedSchema, nil +} From 4fd462e00825285c924b69c2b13b267024088f0b Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 9 Feb 2024 19:09:57 -0500 Subject: [PATCH 02/43] add tests --- schema/oneof.go | 66 +++++++++++++++-- schema/oneof_int.go | 2 + schema/oneof_int_test.go | 4 ++ schema/oneof_string.go | 2 + schema/oneof_string_test.go | 138 ++++++++++++++++++++++++++++++++++++ schema/schema_schema.go | 4 ++ 6 files changed, 209 insertions(+), 7 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index d034e32..6310a16 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -19,6 +19,8 @@ type OneOfSchema[KeyType int64 | string] struct { interfaceType reflect.Type TypesValue map[KeyType]Object `json:"types"` DiscriminatorFieldNameValue string `json:"discriminator_field_name"` + // whether or not the discriminator is inlined in the underlying objects' schema + DiscriminatorInlined bool `json:"discriminator_inlined"` } func (o OneOfSchema[KeyType]) TypeID() TypeID { @@ -110,14 +112,21 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) } } - if _, ok := selectedType.Properties()[o.DiscriminatorFieldNameValue]; !ok { - delete(typedData, o.DiscriminatorFieldNameValue) + cloneData := maps.Clone(typedData) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) } - unserializedData, err := selectedType.Unserialize(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 + } if o.interfaceType == nil { return unserializedData, nil } @@ -140,6 +149,22 @@ func (o OneOfSchema[KeyType]) SerializeType(data any) (any, error) { if err != nil { return nil, err } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } + //reflectedType := reflect.TypeOf(data) + //if reflectedType.Kind() == reflect.Map { + // cloneData := maps.Clone(data.(map[string]any)) + // if !o.DiscriminatorInlined { + // delete(cloneData, o.DiscriminatorFieldNameValue) + // } + // data = cloneData + //} serializedData, err := underlyingType.Serialize(data) if err != nil { return nil, err @@ -330,11 +355,12 @@ func (o OneOfSchema[KeyType]) getTypedDiscriminator(discriminator any) (KeyType, } func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, error) { + var defaultValue KeyType reflectedType := reflect.TypeOf(data) if reflectedType.Kind() != reflect.Struct && reflectedType.Kind() != reflect.Map && (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { - var defaultValue KeyType + return defaultValue, nil, &ConstraintError{ Message: fmt.Sprintf( "Invalid type for one-of type: '%s' expected struct or map.", @@ -347,12 +373,36 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err if reflectedType.Kind() == reflect.Map { myKey, mySchemaObj, err := o.mapUnderlyingType(data.(map[string]any)) if err != nil { - return *foundKey, nil, err + return defaultValue, nil, err } return myKey, mySchemaObj, nil - } + } else if reflectedType.Kind() == reflect.Struct { + fmt.Printf("%v\n", reflectedType) + for key, ref := range o.TypesValue { + underlyingReflectedType := ref.ReflectedType() + if underlyingReflectedType == reflectedType { + keyValue := key + foundKey = &keyValue + } + } + //obj, ok := data.(Object) + //if !ok { + // return defaultValue, nil, fmt.Errorf("finding underlying type asserting data is an object") + //} + //var objID any = obj.ID() + //objKeyType, ok := objID.(KeyType) + //if !ok { + // return defaultValue, nil, fmt.Errorf("finding underlying type asserting key type") + //} + //selectedSchema := o.TypesValue[objKeyType] + //if selectedSchema == nil { + // return defaultValue, nil, fmt.Errorf("finding underlying type for selected struct mapped schema") + //} + //return *foundKey, selectedSchema, nil + } + + // probably don't need this if foundKey == nil { - var defaultValue KeyType dataType := reflect.TypeOf(data) values := make([]string, len(o.TypesValue)) i := 0 @@ -410,3 +460,5 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O return foundKey, selectedSchema, nil } + +// TODO: validate underlying type(s) for inline field diff --git a/schema/oneof_int.go b/schema/oneof_int.go index cc8c72c..fa29336 100644 --- a/schema/oneof_int.go +++ b/schema/oneof_int.go @@ -13,11 +13,13 @@ type OneOfInt interface { func NewOneOfIntSchema[ItemsInterface any]( types map[int64]Object, discriminatorFieldName string, + discriminatorInlined bool, ) *OneOfSchema[int64] { var defaultValue ItemsInterface return &OneOfSchema[int64]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, + discriminatorInlined, } } diff --git a/schema/oneof_int_test.go b/schema/oneof_int_test.go index 04bb1a2..b9e29c3 100644 --- a/schema/oneof_int_test.go +++ b/schema/oneof_int_test.go @@ -19,6 +19,7 @@ var oneOfIntTestObjectAProperties = map[string]*schema.PropertySchema{ 2: schema.NewRefSchema("C", nil), }, "_type", + false, ), nil, true, @@ -39,6 +40,7 @@ var oneOfIntTestObjectAbProperties = map[string]*schema.PropertySchema{ 2: schema.NewRefSchema("C", nil), }, "_difftype", + false, ), nil, true, @@ -59,6 +61,7 @@ var oneOfIntTestObjectAcProperties = map[string]*schema.PropertySchema{ 3: schema.NewRefSchema("C", nil), }, "_type", + false, ), nil, true, @@ -79,6 +82,7 @@ var oneOfIntTestObjectAdProperties = map[string]*schema.PropertySchema{ 2: schema.NewRefSchema("D", nil), }, "_type", + false, ), nil, true, diff --git a/schema/oneof_string.go b/schema/oneof_string.go index fffb80a..52ce02e 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -13,11 +13,13 @@ type OneOfString interface { func NewOneOfStringSchema[ItemsInterface any]( types map[string]Object, discriminatorFieldName string, + discriminatorInlined bool, ) *OneOfSchema[string] { var defaultValue ItemsInterface return &OneOfSchema[string]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, + discriminatorInlined, } } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 019f74a..6efe05b 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -19,6 +19,7 @@ var oneOfStringTestObjectAProperties = map[string]*schema.PropertySchema{ "C": schema.NewRefSchema("C", nil), }, "_type", + false, ), nil, true, @@ -39,6 +40,7 @@ var oneOfStringTestObjectAbProperties = map[string]*schema.PropertySchema{ "C": schema.NewRefSchema("C", nil), }, "_difftype", + false, ), nil, true, @@ -59,6 +61,7 @@ var oneOfStringTestObjectAcProperties = map[string]*schema.PropertySchema{ "D": schema.NewRefSchema("C", nil), }, "_type", + false, ), nil, true, @@ -79,6 +82,7 @@ var oneOfStringTestObjectAdProperties = map[string]*schema.PropertySchema{ "C": schema.NewRefSchema("D", nil), }, "_type", + false, ), nil, true, @@ -148,6 +152,23 @@ func TestOneOfStringUnserialization(t *testing.T) { unserializedData, err := oneOfStringTestObjectAType.Unserialize(input) assert.NoError(t, err) assert.Equals(t, unserializedData.(oneOfTestObjectA).S.(oneOfTestObjectB).Message, "Hello world!") + serialized, err := oneOfStringTestObjectAType.Serialize(unserializedData) + assert.NoError(t, err) + unserialized2, err := oneOfStringTestObjectAType.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 = oneOfStringTestObjectASchema.Unserialize(input) + assert.NoError(t, err) + assert.Equals(t, unserializedData.(map[string]any)["s"].(oneOfTestObjectB).Message, "Hello world!") + serialized, err = oneOfStringTestObjectASchema.Serialize(unserializedData) + assert.NoError(t, err) + unserialized2, err = oneOfStringTestObjectASchema.Unserialize(serialized) + assert.NoError(t, err) + assert.Equals(t, unserialized2, unserializedData) } func TestOneOfStringCompatibilityValidation(t *testing.T) { @@ -222,3 +243,120 @@ func TestOneOfStringCompatibilityMapValidation(t *testing.T) { assert.NoError(t, oneOfStringTestObjectASchema.ValidateCompatibility(combinedMapAndSchema)) assert.Error(t, oneOfStringTestObjectASchema.ValidateCompatibility(combinedMapAndInvalidSchema)) } + +var oneOfNamePropertiesNoRefs = map[string]*schema.PropertySchema{ + "name": schema.NewPropertySchema( + schema.NewOneOfStringSchema[any]( + map[string]schema.Object{ + "fullname": fullnameSchema, + "nickname": nicknameSchema, + }, + "_type", + false, + ), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var fullnameProperties = map[string]*schema.PropertySchema{ + "first_name": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("first_name"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "last_name": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("last_name"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "middle": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("middle"), nil, nil), + false, + nil, + nil, + nil, + nil, + nil, + ), +} + +var nicknameProperties = map[string]*schema.PropertySchema{ + "nick": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("nick"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var fullnameSchema = schema.NewObjectSchema( + "FullName", + fullnameProperties, +) + +var nicknameSchema = schema.NewObjectSchema( + "Nickname", + nicknameProperties, +) + +var oneOfNameNoRefsRootScope = schema.NewScopeSchema( + schema.NewObjectSchema( + "RootObject", + oneOfNamePropertiesNoRefs, + ), + fullnameSchema, + nicknameSchema, +) + +func TestOneOfString_Nickname(t *testing.T) { + var input any = map[string]any{ + "name": map[string]any{ + "_type": "nickname", + "nick": "ArcaLot", + }, + } + unserialized, err := oneOfNameNoRefsRootScope.Unserialize(input) + assert.NoError(t, err) + serialized, err := oneOfNameNoRefsRootScope.Serialize(unserialized) + assert.NoError(t, err) + unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) + assert.NoError(t, err) + assert.Equals(t, unserialized2, unserialized) +} + +func TestOneOfString_Fullname(t *testing.T) { + var input_full any = map[string]any{ + "name": map[string]any{ + "_type": "fullname", + "first_name": "Arca", + "last_name": "Lot", + }, + } + unserialized, err := oneOfNameNoRefsRootScope.Unserialize(input_full) + assert.NoError(t, err) + serialized, err := oneOfNameNoRefsRootScope.Serialize(unserialized) + assert.NoError(t, err) + unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) + assert.Equals(t, unserialized2, unserialized) +} diff --git a/schema/schema_schema.go b/schema/schema_schema.go index fa0bb65..743d5ba 100644 --- a/schema/schema_schema.go +++ b/schema/schema_schema.go @@ -50,6 +50,7 @@ var mapKeyType = NewOneOfStringSchema[any]( ), }, "type_id", + false, ) var displayType = NewDisplayValue( PointerTo("Display"), @@ -194,6 +195,7 @@ var valueType = NewOneOfStringSchema[any]( ), }, "type_id", + false, ) var scopeObject = NewStructMappedObjectSchema[*ScopeSchema]( "Scope", @@ -555,6 +557,7 @@ var basicObjects = []*ObjectSchema{ string(TypeIDObject): NewRefSchema("Object", nil), }, "type_id", + false, ), nil, nil, @@ -601,6 +604,7 @@ var basicObjects = []*ObjectSchema{ string(TypeIDObject): NewRefSchema("Object", nil), }, "type_id", + false, ), nil, nil, From 683d3bddc2befb34f22a6016f6bda593253634ca Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 9 Feb 2024 21:16:13 -0500 Subject: [PATCH 03/43] add test for inline discriminator --- schema/oneof_string_test.go | 192 ++++++++++++++++++++++++++++++++++++ schema/oneof_test.go | 80 +++++++++++++++ 2 files changed, 272 insertions(+) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 6efe05b..e6e6685 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -360,3 +360,195 @@ func TestOneOfString_Fullname(t *testing.T) { unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) assert.Equals(t, unserialized2, unserialized) } + +var discriminatorInline = "_type" + +var fullnameInlineProperties = map[string]*schema.PropertySchema{ + discriminatorInline: schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo(discriminatorInline), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "first_name": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("first_name"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "last_name": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("last_name"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "middle": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("middle"), nil, nil), + false, + nil, + nil, + nil, + nil, + nil, + ), +} + +var nicknameInlineProperties = map[string]*schema.PropertySchema{ + discriminatorInline: schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo(discriminatorInline), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), + "nick": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("nick"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var fullnameInlineSchema = schema.NewObjectSchema( + "FullName", + fullnameInlineProperties, +) + +var nicknameInlineSchema = schema.NewObjectSchema( + "Nickname", + nicknameInlineProperties, +) + +var oneOfNameInlineProperties = map[string]*schema.PropertySchema{ + "name": schema.NewPropertySchema( + schema.NewOneOfStringSchema[any]( + map[string]schema.Object{ + "fullname": fullnameInlineSchema, + "nickname": nicknameInlineSchema, + }, + "_type", + true, + ), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var oneOfNameInlineRootScope = schema.NewScopeSchema( + schema.NewObjectSchema( + "RootObject", + oneOfNameInlineProperties, + ), + fullnameInlineSchema, + nicknameInlineSchema, +) + +func TestOneOfString_FullnameInline(t *testing.T) { + var input_full any = map[string]any{ + "name": map[string]any{ + "_type": "fullname", + "first_name": "Arca", + "last_name": "Lot", + }, + } + unserialized, err := oneOfNameInlineRootScope.Unserialize(input_full) + assert.NoError(t, err) + serialized, err := oneOfNameInlineRootScope.Serialize(unserialized) + assert.NoError(t, err) + unserialized2, err := oneOfNameInlineRootScope.Unserialize(serialized) + assert.Equals(t, unserialized2, unserialized) +} + +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) +} diff --git a/schema/oneof_test.go b/schema/oneof_test.go index 00ba273..63f1c54 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -23,6 +23,20 @@ type oneOfTestObjectA struct { S any `json:"s"` } +type oneOfTestInlineObjectB struct { + Message string `json:"message"` + Choice string `json:"choice"` +} + +func (o oneOfTestInlineObjectB) String() string { + return o.Message +} + +type oneOfTestInlineObjectC struct { + M string `json:"m"` + Choice string `json:"choice"` +} + func TestOneOfTypeID(t *testing.T) { assert.Equals( t, @@ -125,3 +139,69 @@ var oneOfTestCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestObjectC "C", oneOfTestObjectCProperties, ) + +var oneOfTestInlineObjectBProperties = map[string]*schema.PropertySchema{ + "message": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), + "choice": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var oneOfTestInlineObjectCProperties = map[string]*schema.PropertySchema{ + "m": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), + "choice": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var oneOfTestInlineBSchema = schema.NewObjectSchema( + "B", + oneOfTestInlineObjectBProperties, +) + +var oneOfTestInlineCSchema = schema.NewObjectSchema( + "C", + oneOfTestInlineObjectCProperties, +) + +var oneOfTestInlineBMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestInlineObjectB]( + "B", + oneOfTestInlineObjectBProperties, +) + +var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestInlineObjectC]( + "C", + oneOfTestInlineObjectCProperties, +) From 772951dc2a73d7880a1f7fe29384035567e525db Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 13 Feb 2024 16:29:23 -0500 Subject: [PATCH 04/43] add checker for inline field --- schema/oneof.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/schema/oneof.go b/schema/oneof.go index 6310a16..fe3d524 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -461,4 +461,41 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O return foundKey, selectedSchema, nil } -// TODO: validate underlying type(s) for inline field +func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error { + if !o.DiscriminatorInlined { + for key, typeValue := range o.Types() { + _, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + if hasDiscriminator { + return fmt.Errorf( + "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + } + } + } else { + typedDiscriminator, err := o.getTypedDiscriminator(o.DiscriminatorFieldNameValue) + if err != nil { + return err + } + for key, typeValue := range o.Types() { + typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + // discriminator field is in subtype + if !hasDiscriminator { + return fmt.Errorf( + "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + } + // check key type matches discriminator field name value type + if reflect.TypeOf(typeValueDiscriminatorValue).Elem() != reflect.TypeOf(typedDiscriminator).Elem() { + return fmt.Errorf( + "object id %q discriminator %q type %T does not match OneOfSchema discriminator type %T", + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue, typedDiscriminator) + } + } + } + + //for key, typeValue := range o.Types() { + // + //} + + return nil +} From b94093871423e8c3c061b3c6387043b984c7eb84 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 13 Feb 2024 16:42:13 -0500 Subject: [PATCH 05/43] add jareds tests --- schema/oneof.go | 2 +- schema/oneof_string_test.go | 192 ++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/schema/oneof.go b/schema/oneof.go index fe3d524..6c98ead 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -461,7 +461,7 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O return foundKey, selectedSchema, nil } -func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error { +func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { if !o.DiscriminatorInlined { for key, typeValue := range o.Types() { _, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index e6e6685..11265fc 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -552,3 +552,195 @@ func TestOneOfStringInline_Unserialization(t *testing.T) { assert.NoError(t, err) assert.Equals(t, unserialized2, unserializedData) } + +type inlinedTestObjectA struct { + DType string `json:"d_type"` + OtherFieldA string `json:"other_field_a"` +} + +type inlinedTestObjectB struct { + DType string `json:"d_type"` + OtherFieldB string `json:"other_field_b"` +} + +type nonInlinedTestObjectA struct { + OtherFieldA string `json:"other_field_a"` +} + +type nonInlinedTestObjectB struct { + OtherFieldB string `json:"other_field_b"` +} + +var inlinedTestObjectAProperties = map[string]*schema.PropertySchema{ + "d_type": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), + "other_field_a": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var inlinedTestObjectBProperties = map[string]*schema.PropertySchema{ + "d_type": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), + "other_field_b": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var nonInlinedTestObjectAProperties = map[string]*schema.PropertySchema{ + "other_field_a": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var nonInlinedTestObjectBProperties = map[string]*schema.PropertySchema{ + "other_field_b": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + true, + nil, + nil, + nil, + nil, + nil, + ), +} + +var inlinedTestObjectAMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestObjectA]( + "inlined_A", + inlinedTestObjectAProperties, +) + +var inlinedTestObjectBMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestObjectB]( + "inlined_B", + inlinedTestObjectBProperties, +) + +var nonInlinedTestObjectAMappedSchema = schema.NewStructMappedObjectSchema[nonInlinedTestObjectA]( + "non_inlined_A", + nonInlinedTestObjectAProperties, +) + +var nonInlinedTestObjectBMappedSchema = schema.NewStructMappedObjectSchema[nonInlinedTestObjectB]( + "non_inlined_B", + nonInlinedTestObjectBProperties, +) + +var inlinedTestObjectASchema = schema.NewObjectSchema( + "inlined_A", + inlinedTestObjectAProperties, +) + +var inlinedTestObjectBSchema = schema.NewObjectSchema( + "inlined_B", + inlinedTestObjectBProperties, +) + +var nonInlinedTestObjectASchema = schema.NewObjectSchema( + "non_inlined_A", + nonInlinedTestObjectAProperties, +) + +var nonInlinedTestObjectBSchema = schema.NewObjectSchema( + "non_inlined_B", + nonInlinedTestObjectBProperties, +) + +func TestOneOf_InlinedStructMapped(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestObjectAMappedSchema, + "B": inlinedTestObjectBMappedSchema, + }, "d_type", true) + serializedData := map[string]any{ + "d_type": "A", + "other_field_a": "test", + } + // Since this is struct-mapped, unserializedData is a struct. + unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) + reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) + assert.Equals[any](t, reserializedData, serializedData) +} + +func TestOneOf_NonInlinedStructMapped(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": nonInlinedTestObjectAMappedSchema, + "B": nonInlinedTestObjectBMappedSchema, + }, "d_type", false) + serializedData := map[string]any{ + "d_type": "A", + "other_field_a": "test", + } + // Since this is struct-mapped, unserializedData is a struct. + unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) + reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) + assert.Equals[any](t, reserializedData, serializedData) +} + +func TestOneOf_InlinedNonStructMapped(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestObjectASchema, + "B": inlinedTestObjectBSchema, + }, "d_type", true) + serializedData := map[string]any{ + "d_type": "A", + "other_field_a": "test", + } + // Since this is not struct-mapped, unserializedData is a map. + unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) + reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) + assert.Equals[any](t, reserializedData, serializedData) +} + +func TestOneOf_NonInlinedNonStructMapped(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": nonInlinedTestObjectASchema, + "B": nonInlinedTestObjectBSchema, + }, "d_type", false) + assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) + serializedData := map[string]any{ + "d_type": "A", + "other_field_a": "test", + } + // Since this is not struct-mapped, unserializedData is a map. + unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) + reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) + assert.Equals[any](t, reserializedData, serializedData) + +} From d455ff8115d41ebc478a6b2234c3e438ea98228a Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 13 Feb 2024 17:52:22 -0500 Subject: [PATCH 06/43] fix inline discriminator check --- schema/oneof.go | 18 +++++++++++++++++- schema/oneof_string_test.go | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 6c98ead..6b7f627 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -479,13 +479,29 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { for key, typeValue := range o.Types() { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] // discriminator field is in subtype + + //refDiscValue := reflect.TypeOf(typeValueDiscriminatorValue) + //fmt.Printf("type value discr value %v", refDiscValue) + //refDiscValueElem := reflect.TypeOf(typeValueDiscriminatorValue).Elem() + //fmt.Printf("type value discr value elem %v", refDiscValueElem) + //refDiscValueKind := reflect.TypeOf(typeValueDiscriminatorValue).Kind() + //fmt.Printf("%v", refDiscValueKind) + //refTypedDisc := reflect.TypeOf(typedDiscriminator).Kind() + //fmt.Printf("type of typed discriminator %v", refTypedDisc) + //mytype := typeValueDiscriminatorValue.TypeValue + //fmt.Printf("%v\n", mytype) + //eqlss := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) + //fmt.Printf("eqlss %v", eqlss) + if !hasDiscriminator { return fmt.Errorf( "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) } // check key type matches discriminator field name value type - if reflect.TypeOf(typeValueDiscriminatorValue).Elem() != reflect.TypeOf(typedDiscriminator).Elem() { + err := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) + //if reflect.TypeOf(typeValueDiscriminatorValue).Elem() != reflect.TypeOf(typedDiscriminator) { + if err != nil { return fmt.Errorf( "object id %q discriminator %q type %T does not match OneOfSchema discriminator type %T", typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue, typedDiscriminator) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 11265fc..b891376 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -688,6 +688,7 @@ func TestOneOf_InlinedStructMapped(t *testing.T) { "A": inlinedTestObjectAMappedSchema, "B": inlinedTestObjectBMappedSchema, }, "d_type", true) + assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) serializedData := map[string]any{ "d_type": "A", "other_field_a": "test", @@ -733,7 +734,6 @@ func TestOneOf_NonInlinedNonStructMapped(t *testing.T) { "A": nonInlinedTestObjectASchema, "B": nonInlinedTestObjectBSchema, }, "d_type", false) - assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) serializedData := map[string]any{ "d_type": "A", "other_field_a": "test", From c04786bb81ed47ff2a6372f9aae1f6b7545b63cc Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 14 Feb 2024 13:26:12 -0500 Subject: [PATCH 07/43] add test for mismatched discriminator types --- schema/oneof.go | 27 +++----------- schema/oneof_string_test.go | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 6b7f627..625a332 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -478,29 +478,17 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { } for key, typeValue := range o.Types() { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - // discriminator field is in subtype - - //refDiscValue := reflect.TypeOf(typeValueDiscriminatorValue) - //fmt.Printf("type value discr value %v", refDiscValue) - //refDiscValueElem := reflect.TypeOf(typeValueDiscriminatorValue).Elem() - //fmt.Printf("type value discr value elem %v", refDiscValueElem) - //refDiscValueKind := reflect.TypeOf(typeValueDiscriminatorValue).Kind() - //fmt.Printf("%v", refDiscValueKind) - //refTypedDisc := reflect.TypeOf(typedDiscriminator).Kind() - //fmt.Printf("type of typed discriminator %v", refTypedDisc) - //mytype := typeValueDiscriminatorValue.TypeValue - //fmt.Printf("%v\n", mytype) - //eqlss := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) - //fmt.Printf("eqlss %v", eqlss) - if !hasDiscriminator { return fmt.Errorf( "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) } - // check key type matches discriminator field name value type + + // When the one-of's discriminator type (key type) and the + // subtype's discriminator do not match, the subtype's + // discriminator schema will return an error upon validating + // the one-of's discriminator as data. err := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) - //if reflect.TypeOf(typeValueDiscriminatorValue).Elem() != reflect.TypeOf(typedDiscriminator) { if err != nil { return fmt.Errorf( "object id %q discriminator %q type %T does not match OneOfSchema discriminator type %T", @@ -508,10 +496,5 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { } } } - - //for key, typeValue := range o.Types() { - // - //} - return nil } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index b891376..cb7604c 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -744,3 +744,75 @@ func TestOneOf_NonInlinedNonStructMapped(t *testing.T) { assert.Equals[any](t, reserializedData, serializedData) } + +type inlinedTestIntDiscriminatorA struct { + DType int `json:"d_type"` + OtherFieldA string `json:"other_field_a"` +} + +type inlinedTestIntDiscriminatorB struct { + DType int `json:"d_type"` + OtherFieldB string `json:"other_field_b"` +} + +var inlinedTestIntDiscriminatorAProperties = map[string]*schema.PropertySchema{ + "d_type": schema.NewPropertySchema( + schema.NewIntSchema(nil, nil, nil), + nil, true, nil, nil, nil, + nil, nil, + ), + "other_field_a": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, true, nil, nil, nil, + nil, nil, + ), +} + +var inlinedTestIntDiscriminatorBProperties = map[string]*schema.PropertySchema{ + "d_type": schema.NewPropertySchema( + schema.NewIntSchema(nil, nil, nil), + nil, true, nil, nil, nil, + nil, nil, + ), + "other_field_b": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, true, nil, nil, nil, + nil, nil, + ), +} + +var inlinedTestIntDiscriminatorAMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestIntDiscriminatorA]( + "inlined_int_A", + inlinedTestIntDiscriminatorAProperties, +) + +var inlinedTestIntDiscriminatorBMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestIntDiscriminatorB]( + "inlined_int_B", + inlinedTestIntDiscriminatorBProperties, +) + +var inlinedTestIntDiscriminatorASchema = schema.NewObjectSchema( + "inlined_int_A", + inlinedTestIntDiscriminatorAProperties, +) + +var inlinedTestIntDiscriminatorBSchema = schema.NewObjectSchema( + "inlined_int_B", + inlinedTestIntDiscriminatorBProperties, +) + +func TestOneOf_Error_InvalidDiscriminatorTypeInSubtype(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestIntDiscriminatorAMappedSchema, + "B": inlinedTestIntDiscriminatorBMappedSchema, + }, "d_type", true) + assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) + //serializedData := map[string]any{ + // "d_type": "A", + // "other_field_a": "test", + //} + //// Since this is struct-mapped, unserializedData is a struct. + //unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) + //reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) + //assert.Equals[any](t, reserializedData, serializedData) +} From 5ed77fffbb1dcd4879b12979f533b5a3c1103599 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 14 Feb 2024 13:31:57 -0500 Subject: [PATCH 08/43] fix error message --- schema/oneof.go | 4 ++-- schema/oneof_string_test.go | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 625a332..787a6fd 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -491,8 +491,8 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { err := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) if err != nil { return fmt.Errorf( - "object id %q discriminator %q type %T does not match OneOfSchema discriminator type %T", - typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue, typedDiscriminator) + "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), typedDiscriminator) } } } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index cb7604c..ac188c9 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -804,15 +804,17 @@ var inlinedTestIntDiscriminatorBSchema = schema.NewObjectSchema( func TestOneOf_Error_InvalidDiscriminatorTypeInSubtype(t *testing.T) { oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": inlinedTestIntDiscriminatorAMappedSchema, - "B": inlinedTestIntDiscriminatorBMappedSchema, + "B": inlinedTestObjectBMappedSchema, }, "d_type", true) assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) - //serializedData := map[string]any{ - // "d_type": "A", - // "other_field_a": "test", - //} - //// Since this is struct-mapped, unserializedData is a struct. - //unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) - //reserializedData := assert.NoErrorR[any](t)(oneofSchema.Serialize(unserializedData)) - //assert.Equals[any](t, reserializedData, serializedData) + +} + +func TestOneOf_Error_InvalidDiscriminatorTypeInSubtypeForSchema(t *testing.T) { + oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestIntDiscriminatorASchema, + "B": inlinedTestObjectBMappedSchema, + }, "d_type", true) + assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) + } From 74ed3bee7a1924da94437b0a2dcd9f15bf35bdb3 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 14 Feb 2024 14:20:30 -0500 Subject: [PATCH 09/43] fix one-of and subtype discriminator mismatch detection for ints --- schema/oneof.go | 14 ++------------ schema/oneof_string_test.go | 14 +++++++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 787a6fd..64f12f8 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -472,10 +472,6 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { } } } else { - typedDiscriminator, err := o.getTypedDiscriminator(o.DiscriminatorFieldNameValue) - if err != nil { - return err - } for key, typeValue := range o.Types() { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] if !hasDiscriminator { @@ -483,16 +479,10 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) } - - // When the one-of's discriminator type (key type) and the - // subtype's discriminator do not match, the subtype's - // discriminator schema will return an error upon validating - // the one-of's discriminator as data. - err := typeValueDiscriminatorValue.TypeValue.Validate(typedDiscriminator) - if err != nil { + if typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind() { return fmt.Errorf( "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", - typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), typedDiscriminator) + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) } } } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index ac188c9..42cb187 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -807,14 +807,14 @@ func TestOneOf_Error_InvalidDiscriminatorTypeInSubtype(t *testing.T) { "B": inlinedTestObjectBMappedSchema, }, "d_type", true) assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) - + // check error message } -func TestOneOf_Error_InvalidDiscriminatorTypeInSubtypeForSchema(t *testing.T) { - oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ - "A": inlinedTestIntDiscriminatorASchema, - "B": inlinedTestObjectBMappedSchema, +func TestOneOf_Error_OneOfInt_InvalidDiscriminatorType(t *testing.T) { + oneofSchema := schema.NewOneOfIntSchema[any](map[int64]schema.Object{ + 1: inlinedTestIntDiscriminatorASchema, + 2: inlinedTestObjectBMappedSchema, }, "d_type", true) - assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) - + assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) + // check error message } From c674375dadb5f13195c1524570af2988c2b2fe58 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 14 Feb 2024 14:43:37 -0500 Subject: [PATCH 10/43] refactor conditionals --- schema/oneof.go | 61 +++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 64f12f8..0e6b353 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -462,28 +462,45 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O } func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { - if !o.DiscriminatorInlined { - for key, typeValue := range o.Types() { - _, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - if hasDiscriminator { - return fmt.Errorf( - "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - } - } - } else { - for key, typeValue := range o.Types() { - typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - if !hasDiscriminator { - return fmt.Errorf( - "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - } - if typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind() { - return fmt.Errorf( - "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", - typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) - } + //if !o.DiscriminatorInlined { + // for key, typeValue := range o.Types() { + // _, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + // if hasDiscriminator { + // return fmt.Errorf( + // "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", + // typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + // } + // } + //} else { + // for key, typeValue := range o.Types() { + // typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + // if !hasDiscriminator { + // return fmt.Errorf( + // "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", + // typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + // } + // if typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind() { + // return fmt.Errorf( + // "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", + // typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) + // } + // } + //} + + for key, typeValue := range o.TypesValue { + typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + if !o.DiscriminatorInlined && hasDiscriminator { + return fmt.Errorf( + "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + } else if o.DiscriminatorInlined && !hasDiscriminator { + return fmt.Errorf( + "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + } else if o.DiscriminatorInlined && hasDiscriminator && (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()) { + return fmt.Errorf( + "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) } } return nil From d9a17a2a84f5dad3996f4e17ec73fc63ecd00b4c Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 14 Feb 2024 14:43:54 -0500 Subject: [PATCH 11/43] remove dead code --- schema/oneof.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 0e6b353..7beb428 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -462,31 +462,6 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O } func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { - //if !o.DiscriminatorInlined { - // for key, typeValue := range o.Types() { - // _, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - // if hasDiscriminator { - // return fmt.Errorf( - // "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", - // typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - // } - // } - //} else { - // for key, typeValue := range o.Types() { - // typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - // if !hasDiscriminator { - // return fmt.Errorf( - // "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", - // typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - // } - // if typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind() { - // return fmt.Errorf( - // "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", - // typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) - // } - // } - //} - for key, typeValue := range o.TypesValue { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] if !o.DiscriminatorInlined && hasDiscriminator { From 37ecda5448b69362a541442227a086923211827c Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 12:25:37 -0500 Subject: [PATCH 12/43] try to panic --- schema/oneof_int.go | 7 ++++++- schema/oneof_string.go | 7 ++++++- schema/oneof_string_test.go | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/schema/oneof_int.go b/schema/oneof_int.go index fa29336..e917629 100644 --- a/schema/oneof_int.go +++ b/schema/oneof_int.go @@ -16,10 +16,15 @@ func NewOneOfIntSchema[ItemsInterface any]( discriminatorInlined bool, ) *OneOfSchema[int64] { var defaultValue ItemsInterface - return &OneOfSchema[int64]{ + oos := &OneOfSchema[int64]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, discriminatorInlined, } + //err := oos.ValidateSubtypeDiscriminatorInlineFields() + //if err != nil { + // panic(err) + //} + return oos } diff --git a/schema/oneof_string.go b/schema/oneof_string.go index 52ce02e..d39ab35 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -16,10 +16,15 @@ func NewOneOfStringSchema[ItemsInterface any]( discriminatorInlined bool, ) *OneOfSchema[string] { var defaultValue ItemsInterface - return &OneOfSchema[string]{ + oos := &OneOfSchema[string]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, discriminatorInlined, } + //err := oos.ValidateSubtypeDiscriminatorInlineFields() + //if err != nil { + // panic(err) + //} + return oos } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 42cb187..91aad9c 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -816,5 +816,11 @@ func TestOneOf_Error_OneOfInt_InvalidDiscriminatorType(t *testing.T) { 2: inlinedTestObjectBMappedSchema, }, "d_type", true) assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) + //assert.Panics(t, func() { + // schema.NewOneOfIntSchema[any](map[int64]schema.Object{ + // 1: inlinedTestIntDiscriminatorASchema, + // 2: inlinedTestObjectBMappedSchema, + // }, "d_type", true) + //}) // check error message } From e5b6c98c999eb77a477c7d7aeab2c3ad87c69a95 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 15:52:01 -0500 Subject: [PATCH 13/43] move validation call and refactor tests --- schema/oneof.go | 15 ++++++--------- schema/oneof_int.go | 7 +------ schema/oneof_int_test.go | 15 +++++++++++++++ schema/oneof_string.go | 7 +------ schema/oneof_string_test.go | 33 ++++++++++++--------------------- 5 files changed, 35 insertions(+), 42 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 7beb428..cdd7ade 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -47,6 +47,11 @@ func (o OneOfSchema[KeyType]) ApplyScope(scope Scope) { for _, t := range o.TypesValue { t.ApplyScope(scope) } + // scope must be applied before we can access the subtypes' properties + err := o.ValidateSubtypeDiscriminatorInlineFields() + if err != nil { + panic(err) + } } func (o OneOfSchema[KeyType]) ReflectedType() reflect.Type { @@ -157,14 +162,6 @@ func (o OneOfSchema[KeyType]) SerializeType(data any) (any, error) { } data = cloneData } - //reflectedType := reflect.TypeOf(data) - //if reflectedType.Kind() == reflect.Map { - // cloneData := maps.Clone(data.(map[string]any)) - // if !o.DiscriminatorInlined { - // delete(cloneData, o.DiscriminatorFieldNameValue) - // } - // data = cloneData - //} serializedData, err := underlyingType.Serialize(data) if err != nil { return nil, err @@ -474,7 +471,7 @@ func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) } else if o.DiscriminatorInlined && hasDiscriminator && (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()) { return fmt.Errorf( - "object id %q discriminator %q type %v does not match OneOfSchema discriminator type %T", + "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) } } diff --git a/schema/oneof_int.go b/schema/oneof_int.go index e917629..fa29336 100644 --- a/schema/oneof_int.go +++ b/schema/oneof_int.go @@ -16,15 +16,10 @@ func NewOneOfIntSchema[ItemsInterface any]( discriminatorInlined bool, ) *OneOfSchema[int64] { var defaultValue ItemsInterface - oos := &OneOfSchema[int64]{ + return &OneOfSchema[int64]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, discriminatorInlined, } - //err := oos.ValidateSubtypeDiscriminatorInlineFields() - //if err != nil { - // panic(err) - //} - return oos } diff --git a/schema/oneof_int_test.go b/schema/oneof_int_test.go index b9e29c3..330af75 100644 --- a/schema/oneof_int_test.go +++ b/schema/oneof_int_test.go @@ -219,3 +219,18 @@ func TestOneOfIntCompatibilityMapValidation(t *testing.T) { assert.NoError(t, oneOfIntTestObjectASchema.ValidateCompatibility(combinedMapAndSchema)) assert.Error(t, oneOfIntTestObjectASchema.ValidateCompatibility(combinedMapAndInvalidSchema)) } + +func TestOneOf_Error_OneOfInt_InvalidDiscriminatorType(t *testing.T) { + assert.Panics(t, func() { + schema.NewScopeSchema(schema.NewObjectSchema("test", + map[string]*schema.PropertySchema{ + "test": schema.NewPropertySchema( + schema.NewOneOfIntSchema[any](map[int64]schema.Object{ + 1: inlinedTestIntDiscriminatorASchema, + 2: inlinedTestObjectBMappedSchema, + }, "d_type", true), + nil, true, nil, nil, + nil, nil, nil), + })) + }) +} diff --git a/schema/oneof_string.go b/schema/oneof_string.go index d39ab35..52ce02e 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -16,15 +16,10 @@ func NewOneOfStringSchema[ItemsInterface any]( discriminatorInlined bool, ) *OneOfSchema[string] { var defaultValue ItemsInterface - oos := &OneOfSchema[string]{ + return &OneOfSchema[string]{ reflect.TypeOf(&defaultValue).Elem(), types, discriminatorFieldName, discriminatorInlined, } - //err := oos.ValidateSubtypeDiscriminatorInlineFields() - //if err != nil { - // panic(err) - //} - return oos } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 91aad9c..8b44ad6 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -802,25 +802,16 @@ var inlinedTestIntDiscriminatorBSchema = schema.NewObjectSchema( ) func TestOneOf_Error_InvalidDiscriminatorTypeInSubtype(t *testing.T) { - oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ - "A": inlinedTestIntDiscriminatorAMappedSchema, - "B": inlinedTestObjectBMappedSchema, - }, "d_type", true) - assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) - // check error message -} - -func TestOneOf_Error_OneOfInt_InvalidDiscriminatorType(t *testing.T) { - oneofSchema := schema.NewOneOfIntSchema[any](map[int64]schema.Object{ - 1: inlinedTestIntDiscriminatorASchema, - 2: inlinedTestObjectBMappedSchema, - }, "d_type", true) - assert.Error(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) - //assert.Panics(t, func() { - // schema.NewOneOfIntSchema[any](map[int64]schema.Object{ - // 1: inlinedTestIntDiscriminatorASchema, - // 2: inlinedTestObjectBMappedSchema, - // }, "d_type", true) - //}) - // check error message + assert.Panics(t, func() { + schema.NewScopeSchema(schema.NewObjectSchema("test", + map[string]*schema.PropertySchema{ + "test": schema.NewPropertySchema( + schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestIntDiscriminatorAMappedSchema, + "B": inlinedTestObjectBMappedSchema, + }, "d_type", true), + nil, true, nil, nil, + nil, nil, nil), + })) + }) } From f74c4fa0a22191de079c4619e6f8724e675fc7d1 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 16:03:24 -0500 Subject: [PATCH 14/43] rename tests --- schema/oneof_int_test.go | 15 ---------- schema/oneof_string_test.go | 56 ++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/schema/oneof_int_test.go b/schema/oneof_int_test.go index 330af75..b9e29c3 100644 --- a/schema/oneof_int_test.go +++ b/schema/oneof_int_test.go @@ -219,18 +219,3 @@ func TestOneOfIntCompatibilityMapValidation(t *testing.T) { assert.NoError(t, oneOfIntTestObjectASchema.ValidateCompatibility(combinedMapAndSchema)) assert.Error(t, oneOfIntTestObjectASchema.ValidateCompatibility(combinedMapAndInvalidSchema)) } - -func TestOneOf_Error_OneOfInt_InvalidDiscriminatorType(t *testing.T) { - assert.Panics(t, func() { - schema.NewScopeSchema(schema.NewObjectSchema("test", - map[string]*schema.PropertySchema{ - "test": schema.NewPropertySchema( - schema.NewOneOfIntSchema[any](map[int64]schema.Object{ - 1: inlinedTestIntDiscriminatorASchema, - 2: inlinedTestObjectBMappedSchema, - }, "d_type", true), - nil, true, nil, nil, - nil, nil, nil), - })) - }) -} diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 8b44ad6..42acc4a 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -769,11 +769,11 @@ var inlinedTestIntDiscriminatorAProperties = map[string]*schema.PropertySchema{ } var inlinedTestIntDiscriminatorBProperties = map[string]*schema.PropertySchema{ - "d_type": schema.NewPropertySchema( - schema.NewIntSchema(nil, nil, nil), - nil, true, nil, nil, nil, - nil, nil, - ), + //"d_type": schema.NewPropertySchema( + // schema.NewIntSchema(nil, nil, nil), + // nil, true, nil, nil, nil, + // nil, nil, + //), "other_field_b": schema.NewPropertySchema( schema.NewStringSchema(nil, nil, nil), nil, true, nil, nil, nil, @@ -801,15 +801,51 @@ var inlinedTestIntDiscriminatorBSchema = schema.NewObjectSchema( inlinedTestIntDiscriminatorBProperties, ) -func TestOneOf_Error_InvalidDiscriminatorTypeInSubtype(t *testing.T) { +func TestOneOf_Error_SubtypeHasInvalidDiscriminatorType(t *testing.T) { + testSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestIntDiscriminatorAMappedSchema, + "B": inlinedTestObjectBMappedSchema, + }, "d_type", true) + + assert.Panics(t, func() { + schema.NewScopeSchema(schema.NewObjectSchema("test", + map[string]*schema.PropertySchema{ + "test": schema.NewPropertySchema( + testSchema, + nil, true, nil, nil, + nil, nil, nil), + })) + }) +} + +func TestOneOf_Error_InlineSubtypeMissingDiscriminator(t *testing.T) { + testSchema := schema.NewOneOfIntSchema[any](map[int64]schema.Object{ + 1: inlinedTestIntDiscriminatorASchema, + 2: inlinedTestIntDiscriminatorBSchema, + }, "d_type", true) + + assert.Panics(t, func() { + schema.NewScopeSchema(schema.NewObjectSchema("test", + map[string]*schema.PropertySchema{ + "test": schema.NewPropertySchema( + testSchema, + nil, true, nil, nil, + nil, nil, nil), + })) + }) +} + +func TestOneOf_Error_SubtypeHasDiscriminator(t *testing.T) { + testSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ + "A": inlinedTestIntDiscriminatorASchema, + "B": nonInlinedTestObjectBSchema, + }, "d_type", false) + assert.Panics(t, func() { schema.NewScopeSchema(schema.NewObjectSchema("test", map[string]*schema.PropertySchema{ "test": schema.NewPropertySchema( - schema.NewOneOfStringSchema[any](map[string]schema.Object{ - "A": inlinedTestIntDiscriminatorAMappedSchema, - "B": inlinedTestObjectBMappedSchema, - }, "d_type", true), + testSchema, nil, true, nil, nil, nil, nil, nil), })) From 581c1599fc2b8f475b26e5316e13414d26dc4026 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 17:56:18 -0500 Subject: [PATCH 15/43] upgrade go-assert --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 40e47ac..c4e6b68 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/fxamacker/cbor/v2 v2.5.0 - go.arcalot.io/assert v1.7.0 + go.arcalot.io/assert v1.8.0 go.arcalot.io/log/v2 v2.1.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b261836..28ca8e4 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADi github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go.arcalot.io/assert v1.7.0 h1:PTLyeisNMUKpM9wXRDxResanBhuGOYO1xFK3v5b3FSw= -go.arcalot.io/assert v1.7.0/go.mod h1:nNmWPoNUHFyrPkNrD2aASm5yPuAfiWdB/4X7Lw3ykHk= +go.arcalot.io/assert v1.8.0 h1:hGcHMPncQXwQvjj7MbyOu2gg8VIBB00crUJZpeQOjxs= +go.arcalot.io/assert v1.8.0/go.mod h1:nNmWPoNUHFyrPkNrD2aASm5yPuAfiWdB/4X7Lw3ykHk= go.arcalot.io/log/v2 v2.1.0 h1:lNO931hJ82LgS6WcCFCxpLWXQXPFhOkz6PyAJ/augq4= go.arcalot.io/log/v2 v2.1.0/go.mod h1:PNWOSkkPmgS2OMlWTIlB/WqOw0yaBvDYd8ENAP80H4k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From d2805308a498e5f3439ce4b849956cb8cba28c42 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 18:11:25 -0500 Subject: [PATCH 16/43] tidy up --- schema/oneof.go | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index cdd7ade..0a82989 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -132,6 +132,7 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) unserializedMap[o.DiscriminatorFieldNameValue] = discriminator return unserializedMap, nil } + if o.interfaceType == nil { return unserializedData, nil } @@ -374,7 +375,6 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } return myKey, mySchemaObj, nil } else if reflectedType.Kind() == reflect.Struct { - fmt.Printf("%v\n", reflectedType) for key, ref := range o.TypesValue { underlyingReflectedType := ref.ReflectedType() if underlyingReflectedType == reflectedType { @@ -382,23 +382,8 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err foundKey = &keyValue } } - //obj, ok := data.(Object) - //if !ok { - // return defaultValue, nil, fmt.Errorf("finding underlying type asserting data is an object") - //} - //var objID any = obj.ID() - //objKeyType, ok := objID.(KeyType) - //if !ok { - // return defaultValue, nil, fmt.Errorf("finding underlying type asserting key type") - //} - //selectedSchema := o.TypesValue[objKeyType] - //if selectedSchema == nil { - // return defaultValue, nil, fmt.Errorf("finding underlying type for selected struct mapped schema") - //} - //return *foundKey, selectedSchema, nil - } - - // probably don't need this + } + if foundKey == nil { dataType := reflect.TypeOf(data) values := make([]string, len(o.TypesValue)) @@ -455,6 +440,19 @@ func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, O } } + cloneData := maps.Clone(data) + if selectedSchema.Properties()[o.DiscriminatorFieldNameValue] == nil { // Check to see if the discriminator is part of the sub-object. + delete(cloneData, o.DiscriminatorFieldNameValue) // The discriminator isn't part of the object. + } + err := selectedSchema.ValidateCompatibility(cloneData) + if err != nil { + return foundKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", + selectedSchema, selectedTypeIDAsserted, err), + } + } + return foundKey, selectedSchema, nil } From fe31d4223d52580c2e0fb18e70697ef0db906357 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 19:05:25 -0500 Subject: [PATCH 17/43] debug --- schema/oneof.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/oneof.go b/schema/oneof.go index 0a82989..7bc35df 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -367,6 +367,8 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } } + mytype := reflectedType.Kind() + fmt.Printf("%v\n", mytype) var foundKey *KeyType if reflectedType.Kind() == reflect.Map { myKey, mySchemaObj, err := o.mapUnderlyingType(data.(map[string]any)) From 3b9a48c7d2e17bf45e6f5f5f7e6e54054bc407ef Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 19:26:20 -0500 Subject: [PATCH 18/43] fixed it --- schema/oneof.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 7bc35df..31ecc93 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -367,8 +367,6 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } } - mytype := reflectedType.Kind() - fmt.Printf("%v\n", mytype) var foundKey *KeyType if reflectedType.Kind() == reflect.Map { myKey, mySchemaObj, err := o.mapUnderlyingType(data.(map[string]any)) @@ -376,7 +374,7 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err return defaultValue, nil, err } return myKey, mySchemaObj, nil - } else if reflectedType.Kind() == reflect.Struct { + } else { for key, ref := range o.TypesValue { underlyingReflectedType := ref.ReflectedType() if underlyingReflectedType == reflectedType { From 40937d1e56c107e8bf241932b99a004851072459 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Thu, 15 Feb 2024 19:31:03 -0500 Subject: [PATCH 19/43] ineffectual err --- schema/oneof_string_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 42acc4a..1394957 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -358,6 +358,7 @@ func TestOneOfString_Fullname(t *testing.T) { serialized, err := oneOfNameNoRefsRootScope.Serialize(unserialized) assert.NoError(t, err) unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) + assert.NoError(t, err) assert.Equals(t, unserialized2, unserialized) } @@ -481,6 +482,7 @@ func TestOneOfString_FullnameInline(t *testing.T) { serialized, err := oneOfNameInlineRootScope.Serialize(unserialized) assert.NoError(t, err) unserialized2, err := oneOfNameInlineRootScope.Unserialize(serialized) + assert.NoError(t, err) assert.Equals(t, unserialized2, unserialized) } From 51426365c894e9c75caac5299df74fdf5798bc16 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 09:34:33 -0500 Subject: [PATCH 20/43] rm dead code --- schema/oneof.go | 68 ++++++++----------------------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 31ecc93..446d20b 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -186,7 +186,8 @@ func (o OneOfSchema[KeyType]) ValidateCompatibility(typeOrData any) error { // If not, verify it as data. inputAsMap, ok := typeOrData.(map[string]any) if ok { - return o.validateMap(inputAsMap) + _, _, err := o.validateMap(inputAsMap) + return err } value := reflect.ValueOf(typeOrData) if reflect.Indirect(value).Kind() != reflect.Struct { @@ -241,13 +242,14 @@ func (o OneOfSchema[KeyType]) validateSchema(otherSchema OneOfSchema[KeyType]) e return nil } -func (o OneOfSchema[KeyType]) validateMap(data map[string]any) error { +func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, error) { + var defaultKey KeyType // Validate that it has the discriminator field. // If it doesn't, fail // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object selectedTypeID := data[o.DiscriminatorFieldNameValue] if selectedTypeID == nil { - return &ConstraintError{ + return defaultKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), } @@ -255,7 +257,7 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) error { // Ensure it's the correct type selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) if !ok { - return &ConstraintError{ + return defaultKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), @@ -264,7 +266,7 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) error { // Find the object that's associated with the selected type selectedSchema := o.TypesValue[selectedTypeIDAsserted] if selectedSchema == nil { - return &ConstraintError{ + return defaultKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", selectedTypeIDAsserted, o.getTypeValues()), @@ -276,13 +278,13 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) error { } err := selectedSchema.ValidateCompatibility(cloneData) if err != nil { - return &ConstraintError{ + return defaultKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", selectedSchema, selectedTypeIDAsserted, err), } } - return nil + return selectedTypeIDAsserted, selectedSchema, nil } func (o OneOfSchema[KeyType]) getTypeValues() []KeyType { @@ -369,7 +371,7 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err var foundKey *KeyType if reflectedType.Kind() == reflect.Map { - myKey, mySchemaObj, err := o.mapUnderlyingType(data.(map[string]any)) + myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) if err != nil { return defaultValue, nil, err } @@ -406,56 +408,6 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err return *foundKey, o.TypesValue[*foundKey], nil } -func (o OneOfSchema[KeyType]) mapUnderlyingType(data map[string]any) (KeyType, Object, error) { - // Validate that it has the discriminator field. - // If it doesn't, fail - // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object - - var foundKey KeyType - selectedTypeID := data[o.DiscriminatorFieldNameValue] - if selectedTypeID == nil { - return foundKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), - } - } - // Ensure it's the correct type - selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) - if !ok { - return foundKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", - o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), - } - } - foundKey = selectedTypeIDAsserted - - // Find the object that's associated with the selected type - selectedSchema := o.TypesValue[selectedTypeIDAsserted] - if selectedSchema == nil { - return foundKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", - selectedTypeIDAsserted, o.getTypeValues()), - } - } - - cloneData := maps.Clone(data) - if selectedSchema.Properties()[o.DiscriminatorFieldNameValue] == nil { // Check to see if the discriminator is part of the sub-object. - delete(cloneData, o.DiscriminatorFieldNameValue) // The discriminator isn't part of the object. - } - err := selectedSchema.ValidateCompatibility(cloneData) - if err != nil { - return foundKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", - selectedSchema, selectedTypeIDAsserted, err), - } - } - - return foundKey, selectedSchema, nil -} - func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { for key, typeValue := range o.TypesValue { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] From aeb622893ecdc1d8cf83dd3b851a9b8f12efe875 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 09:53:52 -0500 Subject: [PATCH 21/43] whitespace --- schema/oneof.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema/oneof.go b/schema/oneof.go index 446d20b..d524ee5 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -85,6 +85,7 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) if err != nil { return result, err } + typedData := make(map[string]any, reflectedValue.Len()) for _, k := range reflectedValue.MapKeys() { v := reflectedValue.MapIndex(k) @@ -121,7 +122,6 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) if !o.DiscriminatorInlined { delete(cloneData, o.DiscriminatorFieldNameValue) } - unserializedData, err := selectedType.Unserialize(cloneData) if err != nil { return result, err @@ -356,6 +356,7 @@ func (o OneOfSchema[KeyType]) getTypedDiscriminator(discriminator any) (KeyType, func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, error) { var defaultValue KeyType + reflectedType := reflect.TypeOf(data) if reflectedType.Kind() != reflect.Struct && reflectedType.Kind() != reflect.Map && From 1b207dc9f49f31cb664dc9b018d9fc00ffca6336 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 11:07:20 -0500 Subject: [PATCH 22/43] rm dead code --- schema/oneof_string_test.go | 253 +----------------------------------- 1 file changed, 3 insertions(+), 250 deletions(-) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 1394957..49fb1b8 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -244,248 +244,6 @@ func TestOneOfStringCompatibilityMapValidation(t *testing.T) { assert.Error(t, oneOfStringTestObjectASchema.ValidateCompatibility(combinedMapAndInvalidSchema)) } -var oneOfNamePropertiesNoRefs = map[string]*schema.PropertySchema{ - "name": schema.NewPropertySchema( - schema.NewOneOfStringSchema[any]( - map[string]schema.Object{ - "fullname": fullnameSchema, - "nickname": nicknameSchema, - }, - "_type", - false, - ), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var fullnameProperties = map[string]*schema.PropertySchema{ - "first_name": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("first_name"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "last_name": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("last_name"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "middle": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("middle"), nil, nil), - false, - nil, - nil, - nil, - nil, - nil, - ), -} - -var nicknameProperties = map[string]*schema.PropertySchema{ - "nick": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("nick"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var fullnameSchema = schema.NewObjectSchema( - "FullName", - fullnameProperties, -) - -var nicknameSchema = schema.NewObjectSchema( - "Nickname", - nicknameProperties, -) - -var oneOfNameNoRefsRootScope = schema.NewScopeSchema( - schema.NewObjectSchema( - "RootObject", - oneOfNamePropertiesNoRefs, - ), - fullnameSchema, - nicknameSchema, -) - -func TestOneOfString_Nickname(t *testing.T) { - var input any = map[string]any{ - "name": map[string]any{ - "_type": "nickname", - "nick": "ArcaLot", - }, - } - unserialized, err := oneOfNameNoRefsRootScope.Unserialize(input) - assert.NoError(t, err) - serialized, err := oneOfNameNoRefsRootScope.Serialize(unserialized) - assert.NoError(t, err) - unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) - assert.NoError(t, err) - assert.Equals(t, unserialized2, unserialized) -} - -func TestOneOfString_Fullname(t *testing.T) { - var input_full any = map[string]any{ - "name": map[string]any{ - "_type": "fullname", - "first_name": "Arca", - "last_name": "Lot", - }, - } - unserialized, err := oneOfNameNoRefsRootScope.Unserialize(input_full) - assert.NoError(t, err) - serialized, err := oneOfNameNoRefsRootScope.Serialize(unserialized) - assert.NoError(t, err) - unserialized2, err := oneOfNameNoRefsRootScope.Unserialize(serialized) - assert.NoError(t, err) - assert.Equals(t, unserialized2, unserialized) -} - -var discriminatorInline = "_type" - -var fullnameInlineProperties = map[string]*schema.PropertySchema{ - discriminatorInline: schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo(discriminatorInline), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "first_name": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("first_name"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "last_name": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("last_name"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "middle": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("middle"), nil, nil), - false, - nil, - nil, - nil, - nil, - nil, - ), -} - -var nicknameInlineProperties = map[string]*schema.PropertySchema{ - discriminatorInline: schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo(discriminatorInline), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), - "nick": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - schema.NewDisplayValue(schema.PointerTo("nick"), nil, nil), - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var fullnameInlineSchema = schema.NewObjectSchema( - "FullName", - fullnameInlineProperties, -) - -var nicknameInlineSchema = schema.NewObjectSchema( - "Nickname", - nicknameInlineProperties, -) - -var oneOfNameInlineProperties = map[string]*schema.PropertySchema{ - "name": schema.NewPropertySchema( - schema.NewOneOfStringSchema[any]( - map[string]schema.Object{ - "fullname": fullnameInlineSchema, - "nickname": nicknameInlineSchema, - }, - "_type", - true, - ), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var oneOfNameInlineRootScope = schema.NewScopeSchema( - schema.NewObjectSchema( - "RootObject", - oneOfNameInlineProperties, - ), - fullnameInlineSchema, - nicknameInlineSchema, -) - -func TestOneOfString_FullnameInline(t *testing.T) { - var input_full any = map[string]any{ - "name": map[string]any{ - "_type": "fullname", - "first_name": "Arca", - "last_name": "Lot", - }, - } - unserialized, err := oneOfNameInlineRootScope.Unserialize(input_full) - assert.NoError(t, err) - serialized, err := oneOfNameInlineRootScope.Serialize(unserialized) - assert.NoError(t, err) - unserialized2, err := oneOfNameInlineRootScope.Unserialize(serialized) - assert.NoError(t, err) - assert.Equals(t, unserialized2, unserialized) -} - var oneOfStringTestInlineObjectAProperties = map[string]*schema.PropertySchema{ "s": schema.NewPropertySchema( schema.NewOneOfStringSchema[any]( @@ -770,12 +528,7 @@ var inlinedTestIntDiscriminatorAProperties = map[string]*schema.PropertySchema{ ), } -var inlinedTestIntDiscriminatorBProperties = map[string]*schema.PropertySchema{ - //"d_type": schema.NewPropertySchema( - // schema.NewIntSchema(nil, nil, nil), - // nil, true, nil, nil, nil, - // nil, nil, - //), +var inlinedTestNoDiscriminatorBProperties = map[string]*schema.PropertySchema{ "other_field_b": schema.NewPropertySchema( schema.NewStringSchema(nil, nil, nil), nil, true, nil, nil, nil, @@ -790,7 +543,7 @@ var inlinedTestIntDiscriminatorAMappedSchema = schema.NewStructMappedObjectSchem var inlinedTestIntDiscriminatorBMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestIntDiscriminatorB]( "inlined_int_B", - inlinedTestIntDiscriminatorBProperties, + inlinedTestNoDiscriminatorBProperties, ) var inlinedTestIntDiscriminatorASchema = schema.NewObjectSchema( @@ -800,7 +553,7 @@ var inlinedTestIntDiscriminatorASchema = schema.NewObjectSchema( var inlinedTestIntDiscriminatorBSchema = schema.NewObjectSchema( "inlined_int_B", - inlinedTestIntDiscriminatorBProperties, + inlinedTestNoDiscriminatorBProperties, ) func TestOneOf_Error_SubtypeHasInvalidDiscriminatorType(t *testing.T) { From de3831c8a765d5ccdb57b1678408bc7b66ad88e4 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 11:08:52 -0500 Subject: [PATCH 23/43] rm unused --- schema/oneof_string_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 49fb1b8..9f79a3b 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -510,11 +510,6 @@ type inlinedTestIntDiscriminatorA struct { OtherFieldA string `json:"other_field_a"` } -type inlinedTestIntDiscriminatorB struct { - DType int `json:"d_type"` - OtherFieldB string `json:"other_field_b"` -} - var inlinedTestIntDiscriminatorAProperties = map[string]*schema.PropertySchema{ "d_type": schema.NewPropertySchema( schema.NewIntSchema(nil, nil, nil), @@ -541,11 +536,6 @@ var inlinedTestIntDiscriminatorAMappedSchema = schema.NewStructMappedObjectSchem inlinedTestIntDiscriminatorAProperties, ) -var inlinedTestIntDiscriminatorBMappedSchema = schema.NewStructMappedObjectSchema[inlinedTestIntDiscriminatorB]( - "inlined_int_B", - inlinedTestNoDiscriminatorBProperties, -) - var inlinedTestIntDiscriminatorASchema = schema.NewObjectSchema( "inlined_int_A", inlinedTestIntDiscriminatorAProperties, From b8e870c55d677a6ef8c8498bc830752c3e9afef6 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 11:14:30 -0500 Subject: [PATCH 24/43] refactor to switch --- schema/oneof.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index d524ee5..7abf28f 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -412,15 +412,17 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { for key, typeValue := range o.TypesValue { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - if !o.DiscriminatorInlined && hasDiscriminator { + switch { + case !o.DiscriminatorInlined && hasDiscriminator: return fmt.Errorf( "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - } else if o.DiscriminatorInlined && !hasDiscriminator { + case o.DiscriminatorInlined && !hasDiscriminator: return fmt.Errorf( "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - } else if o.DiscriminatorInlined && hasDiscriminator && (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()) { + case o.DiscriminatorInlined && hasDiscriminator && + (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()): return fmt.Errorf( "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) From 8bc2099f1afadafe6ee85ed656ab566ee05c1df6 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 11:22:40 -0500 Subject: [PATCH 25/43] make function private --- schema/oneof.go | 7 +++++-- schema/oneof_string_test.go | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 7abf28f..947c7b7 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -48,7 +48,7 @@ func (o OneOfSchema[KeyType]) ApplyScope(scope Scope) { t.ApplyScope(scope) } // scope must be applied before we can access the subtypes' properties - err := o.ValidateSubtypeDiscriminatorInlineFields() + err := o.validateSubtypeDiscriminatorInlineFields() if err != nil { panic(err) } @@ -409,7 +409,10 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err return *foundKey, o.TypesValue[*foundKey], nil } -func (o OneOfSchema[KeyType]) ValidateSubtypeDiscriminatorInlineFields() error { +// validateSubtypeDiscriminatorInlineFields checks to see if a subtype's +// discriminator field has been written in accordance with the OneOfSchema's +// declaration. +func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error { for key, typeValue := range o.TypesValue { typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] switch { diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 9f79a3b..49f4b79 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -448,7 +448,6 @@ func TestOneOf_InlinedStructMapped(t *testing.T) { "A": inlinedTestObjectAMappedSchema, "B": inlinedTestObjectBMappedSchema, }, "d_type", true) - assert.NoError(t, oneofSchema.ValidateSubtypeDiscriminatorInlineFields()) serializedData := map[string]any{ "d_type": "A", "other_field_a": "test", From 5239cf659efa0dd54cc16963103c80c824e97729 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 12:03:11 -0500 Subject: [PATCH 26/43] use panicscontains --- schema/oneof_string_test.go | 56 +++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 49f4b79..72b36d8 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -5,12 +5,15 @@ package schema_test import ( "encoding/json" + "fmt" "go.arcalot.io/assert" "testing" "go.flow.arcalot.io/pluginsdk/schema" ) +const discriminatorFieldName = "d_type" + var oneOfStringTestObjectAProperties = map[string]*schema.PropertySchema{ "s": schema.NewPropertySchema( schema.NewOneOfStringSchema[any]( @@ -332,7 +335,7 @@ type nonInlinedTestObjectB struct { } var inlinedTestObjectAProperties = map[string]*schema.PropertySchema{ - "d_type": schema.NewPropertySchema( + discriminatorFieldName: schema.NewPropertySchema( schema.NewStringSchema(nil, nil, nil), nil, true, @@ -355,7 +358,7 @@ var inlinedTestObjectAProperties = map[string]*schema.PropertySchema{ } var inlinedTestObjectBProperties = map[string]*schema.PropertySchema{ - "d_type": schema.NewPropertySchema( + discriminatorFieldName: schema.NewPropertySchema( schema.NewStringSchema(nil, nil, nil), nil, true, @@ -447,10 +450,10 @@ func TestOneOf_InlinedStructMapped(t *testing.T) { oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": inlinedTestObjectAMappedSchema, "B": inlinedTestObjectBMappedSchema, - }, "d_type", true) + }, discriminatorFieldName, true) serializedData := map[string]any{ - "d_type": "A", - "other_field_a": "test", + discriminatorFieldName: "A", + "other_field_a": "test", } // Since this is struct-mapped, unserializedData is a struct. unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) @@ -462,10 +465,10 @@ func TestOneOf_NonInlinedStructMapped(t *testing.T) { oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": nonInlinedTestObjectAMappedSchema, "B": nonInlinedTestObjectBMappedSchema, - }, "d_type", false) + }, discriminatorFieldName, false) serializedData := map[string]any{ - "d_type": "A", - "other_field_a": "test", + discriminatorFieldName: "A", + "other_field_a": "test", } // Since this is struct-mapped, unserializedData is a struct. unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) @@ -477,10 +480,10 @@ func TestOneOf_InlinedNonStructMapped(t *testing.T) { oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": inlinedTestObjectASchema, "B": inlinedTestObjectBSchema, - }, "d_type", true) + }, discriminatorFieldName, true) serializedData := map[string]any{ - "d_type": "A", - "other_field_a": "test", + discriminatorFieldName: "A", + "other_field_a": "test", } // Since this is not struct-mapped, unserializedData is a map. unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) @@ -492,10 +495,10 @@ func TestOneOf_NonInlinedNonStructMapped(t *testing.T) { oneofSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": nonInlinedTestObjectASchema, "B": nonInlinedTestObjectBSchema, - }, "d_type", false) + }, discriminatorFieldName, false) serializedData := map[string]any{ - "d_type": "A", - "other_field_a": "test", + discriminatorFieldName: "A", + "other_field_a": "test", } // Since this is not struct-mapped, unserializedData is a map. unserializedData := assert.NoErrorR[any](t)(oneofSchema.Unserialize(serializedData)) @@ -510,7 +513,7 @@ type inlinedTestIntDiscriminatorA struct { } var inlinedTestIntDiscriminatorAProperties = map[string]*schema.PropertySchema{ - "d_type": schema.NewPropertySchema( + discriminatorFieldName: schema.NewPropertySchema( schema.NewIntSchema(nil, nil, nil), nil, true, nil, nil, nil, nil, nil, @@ -549,9 +552,12 @@ func TestOneOf_Error_SubtypeHasInvalidDiscriminatorType(t *testing.T) { testSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": inlinedTestIntDiscriminatorAMappedSchema, "B": inlinedTestObjectBMappedSchema, - }, "d_type", true) + }, discriminatorFieldName, true) + expMsg := fmt.Sprintf( + "discriminator field %q does not match OneOfSchema discriminator type", + discriminatorFieldName) - assert.Panics(t, func() { + assert.PanicsContains(t, func() { schema.NewScopeSchema(schema.NewObjectSchema("test", map[string]*schema.PropertySchema{ "test": schema.NewPropertySchema( @@ -559,16 +565,17 @@ func TestOneOf_Error_SubtypeHasInvalidDiscriminatorType(t *testing.T) { nil, true, nil, nil, nil, nil, nil), })) - }) + }, expMsg) } func TestOneOf_Error_InlineSubtypeMissingDiscriminator(t *testing.T) { testSchema := schema.NewOneOfIntSchema[any](map[int64]schema.Object{ 1: inlinedTestIntDiscriminatorASchema, 2: inlinedTestIntDiscriminatorBSchema, - }, "d_type", true) + }, discriminatorFieldName, true) + expMsg := "needs discriminator field" - assert.Panics(t, func() { + assert.PanicsContains(t, func() { schema.NewScopeSchema(schema.NewObjectSchema("test", map[string]*schema.PropertySchema{ "test": schema.NewPropertySchema( @@ -576,16 +583,17 @@ func TestOneOf_Error_InlineSubtypeMissingDiscriminator(t *testing.T) { nil, true, nil, nil, nil, nil, nil), })) - }) + }, expMsg) } func TestOneOf_Error_SubtypeHasDiscriminator(t *testing.T) { testSchema := schema.NewOneOfStringSchema[any](map[string]schema.Object{ "A": inlinedTestIntDiscriminatorASchema, "B": nonInlinedTestObjectBSchema, - }, "d_type", false) + }, discriminatorFieldName, false) + expMsg := "has conflicting field" - assert.Panics(t, func() { + assert.PanicsContains(t, func() { schema.NewScopeSchema(schema.NewObjectSchema("test", map[string]*schema.PropertySchema{ "test": schema.NewPropertySchema( @@ -593,5 +601,5 @@ func TestOneOf_Error_SubtypeHasDiscriminator(t *testing.T) { nil, true, nil, nil, nil, nil, nil), })) - }) + }, expMsg) } From e7a5f5caba3f011f931aa58dff6bbb88b02a8aed Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 15:23:08 -0500 Subject: [PATCH 27/43] small change --- schema/oneof.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 947c7b7..9b55242 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -355,14 +355,14 @@ func (o OneOfSchema[KeyType]) getTypedDiscriminator(discriminator any) (KeyType, } func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, error) { - var defaultValue KeyType + var nilKey KeyType reflectedType := reflect.TypeOf(data) if reflectedType.Kind() != reflect.Struct && reflectedType.Kind() != reflect.Map && (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { - return defaultValue, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "Invalid type for one-of type: '%s' expected struct or map.", reflect.TypeOf(data).Name(), @@ -374,19 +374,18 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err if reflectedType.Kind() == reflect.Map { myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) if err != nil { - return defaultValue, nil, err + return nilKey, nil, err } return myKey, mySchemaObj, nil - } else { - for key, ref := range o.TypesValue { - underlyingReflectedType := ref.ReflectedType() - if underlyingReflectedType == reflectedType { - keyValue := key - foundKey = &keyValue - } + } + // else + for key, ref := range o.TypesValue { + underlyingReflectedType := ref.ReflectedType() + if underlyingReflectedType == reflectedType { + keyValue := key + foundKey = &keyValue } } - if foundKey == nil { dataType := reflect.TypeOf(data) values := make([]string, len(o.TypesValue)) @@ -398,7 +397,7 @@ func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, err } i++ } - return defaultValue, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "Invalid type for one-of schema: '%s' (valid types are: %s)", dataType.String(), From 2aba5c8667c44c95d0a2e5fceed403b002291e02 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 17:37:41 -0500 Subject: [PATCH 28/43] add map change to validate type --- schema/oneof.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 9b55242..6fcd4b8 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -144,6 +144,14 @@ func (o OneOfSchema[KeyType]) ValidateType(data any) error { if err != nil { return err } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } if err := underlyingType.Validate(data); err != nil { return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue)) } @@ -243,13 +251,13 @@ func (o OneOfSchema[KeyType]) validateSchema(otherSchema OneOfSchema[KeyType]) e } func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, error) { - var defaultKey KeyType + var nilKey KeyType // Validate that it has the discriminator field. // If it doesn't, fail // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object selectedTypeID := data[o.DiscriminatorFieldNameValue] if selectedTypeID == nil { - return defaultKey, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), } @@ -257,7 +265,7 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, // Ensure it's the correct type selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) if !ok { - return defaultKey, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), @@ -266,19 +274,21 @@ func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, // Find the object that's associated with the selected type selectedSchema := o.TypesValue[selectedTypeIDAsserted] if selectedSchema == nil { - return defaultKey, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", selectedTypeIDAsserted, o.getTypeValues()), } } cloneData := maps.Clone(data) - if selectedSchema.Properties()[o.DiscriminatorFieldNameValue] == nil { // Check to see if the discriminator is part of the sub-object. + 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. } + data = cloneData + err := selectedSchema.ValidateCompatibility(cloneData) if err != nil { - return defaultKey, nil, &ConstraintError{ + return nilKey, nil, &ConstraintError{ Message: fmt.Sprintf( "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", selectedSchema, selectedTypeIDAsserted, err), From c24d1ee29c6d000da907e75cc3824008436f4a90 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 19:29:01 -0500 Subject: [PATCH 29/43] hopefully fix interface type access --- schema/oneof.go | 742 ++++++++++++++++++++--------------------- schema/oneof_string.go | 26 +- 2 files changed, 382 insertions(+), 386 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 6fcd4b8..1700710 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -1,444 +1,438 @@ package schema import ( - "fmt" - "maps" - "reflect" - "strings" + "fmt" + "maps" + "reflect" + "strings" ) // OneOf is the root interface for one-of types. It should not be used directly but is provided for convenience. type OneOf[KeyType int64 | string] interface { - Type + Type - Types() map[KeyType]Object - DiscriminatorFieldName() string + Types() map[KeyType]Object + DiscriminatorFieldName() string } type OneOfSchema[KeyType int64 | string] struct { - interfaceType reflect.Type - TypesValue map[KeyType]Object `json:"types"` - DiscriminatorFieldNameValue string `json:"discriminator_field_name"` - // whether or not the discriminator is inlined in the underlying objects' schema - DiscriminatorInlined bool `json:"discriminator_inlined"` + interfaceType reflect.Type + TypesValue map[KeyType]Object `json:"types"` + DiscriminatorFieldNameValue string `json:"discriminator_field_name"` + // whether or not the discriminator is inlined in the underlying objects' schema + DiscriminatorInlined bool `json:"discriminator_inlined"` } func (o OneOfSchema[KeyType]) TypeID() TypeID { - var defaultValue KeyType - switch any(defaultValue).(type) { - case int64: - return TypeIDOneOfInt - case string: - return TypeIDOneOfString - default: - panic(BadArgumentError{Message: fmt.Sprintf("Unexpected key type: %T", defaultValue)}) - } + var defaultValue KeyType + switch any(defaultValue).(type) { + case int64: + return TypeIDOneOfInt + case string: + return TypeIDOneOfString + default: + panic(BadArgumentError{Message: fmt.Sprintf("Unexpected key type: %T", defaultValue)}) + } } func (o OneOfSchema[KeyType]) Types() map[KeyType]Object { - return o.TypesValue + return o.TypesValue } func (o OneOfSchema[KeyType]) DiscriminatorFieldName() string { - return o.DiscriminatorFieldNameValue + return o.DiscriminatorFieldNameValue } func (o OneOfSchema[KeyType]) ApplyScope(scope Scope) { - for _, t := range o.TypesValue { - t.ApplyScope(scope) - } - // scope must be applied before we can access the subtypes' properties - err := o.validateSubtypeDiscriminatorInlineFields() - if err != nil { - panic(err) - } + for _, t := range o.TypesValue { + t.ApplyScope(scope) + } + // scope must be applied before we can access the subtypes' properties + err := o.validateSubtypeDiscriminatorInlineFields() + if err != nil { + panic(err) + } } func (o OneOfSchema[KeyType]) ReflectedType() reflect.Type { - if o.interfaceType == nil { - var defaultValue any - return reflect.TypeOf(&defaultValue).Elem() - } - return o.interfaceType + if o.interfaceType == nil { + var defaultValue any + return reflect.TypeOf(&defaultValue).Elem() + } + return o.interfaceType } //nolint:funlen func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) { - reflectedValue := reflect.ValueOf(data) - if reflectedValue.Kind() != reflect.Map { - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of type: '%s'. Expected map.", - reflect.TypeOf(data).Name(), - ), - } - } - - discriminatorValue := reflectedValue.MapIndex(reflect.ValueOf(o.DiscriminatorFieldNameValue)) - if !discriminatorValue.IsValid() { - return result, &ConstraintError{ - Message: fmt.Sprintf("Missing discriminator field '%s' in '%v'", o.DiscriminatorFieldNameValue, data), - } - } - discriminator := discriminatorValue.Interface() - typedDiscriminator, err := o.getTypedDiscriminator(discriminator) - if err != nil { - return result, err - } - - typedData := make(map[string]any, reflectedValue.Len()) - for _, k := range reflectedValue.MapKeys() { - v := reflectedValue.MapIndex(k) - keyString, ok := k.Interface().(string) - if !ok { - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid key type for one-of: '%T'", - k.Interface(), - ), - } - } - typedData[keyString] = v.Interface() - } - - selectedType, ok := o.TypesValue[typedDiscriminator] - if !ok { - validDiscriminators := make([]string, len(o.TypesValue)) - i := 0 - for k := range o.TypesValue { - validDiscriminators[i] = fmt.Sprintf("%v", k) - i++ - } - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid value for '%s', expected one of: %s", - o.DiscriminatorFieldNameValue, - strings.Join(validDiscriminators, ", "), - ), - } - } - - cloneData := maps.Clone(typedData) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - 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 - } - - if o.interfaceType == nil { - return unserializedData, nil - } - return saveConvertTo(unserializedData, o.interfaceType) + if data == nil { + return nil, fmt.Errorf("bug: data is nil in OneOfSchema UnserializeType") + } + reflectedValue := reflect.ValueOf(data) + if reflectedValue.Kind() != reflect.Map { + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of type: '%s'. Expected map.", + reflect.TypeOf(data).Name(), + ), + } + } + + discriminatorValue := reflectedValue.MapIndex(reflect.ValueOf(o.DiscriminatorFieldNameValue)) + if !discriminatorValue.IsValid() { + return result, &ConstraintError{ + Message: fmt.Sprintf("Missing discriminator field '%s' in '%v'", o.DiscriminatorFieldNameValue, data), + } + } + discriminator := discriminatorValue.Interface() + typedDiscriminator, err := o.getTypedDiscriminator(discriminator) + if err != nil { + return result, err + } + + typedData := make(map[string]any, reflectedValue.Len()) + for _, k := range reflectedValue.MapKeys() { + v := reflectedValue.MapIndex(k) + keyString, ok := k.Interface().(string) + if !ok { + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid key type for one-of: '%T'", + k.Interface(), + ), + } + } + typedData[keyString] = v.Interface() + } + + selectedType, ok := o.TypesValue[typedDiscriminator] + if !ok { + validDiscriminators := make([]string, len(o.TypesValue)) + i := 0 + for k := range o.TypesValue { + validDiscriminators[i] = fmt.Sprintf("%v", k) + i++ + } + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid value for '%s', expected one of: %s", + o.DiscriminatorFieldNameValue, + strings.Join(validDiscriminators, ", "), + ), + } + } + + cloneData := maps.Clone(typedData) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + 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()) } func (o OneOfSchema[KeyType]) ValidateType(data any) error { - discriminatorValue, underlyingType, err := o.findUnderlyingType(data) - if err != nil { - return err - } - dataMap, ok := data.(map[string]any) - if ok { - cloneData := maps.Clone(dataMap) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - data = cloneData - } - if err := underlyingType.Validate(data); err != nil { - return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue)) - } - return nil + discriminatorValue, underlyingType, err := o.findUnderlyingType(data) + if err != nil { + return err + } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } + if err := underlyingType.Validate(data); err != nil { + return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue)) + } + return nil } func (o OneOfSchema[KeyType]) SerializeType(data any) (any, error) { - discriminatorValue, underlyingType, err := o.findUnderlyingType(data) - if err != nil { - return nil, err - } - dataMap, ok := data.(map[string]any) - if ok { - cloneData := maps.Clone(dataMap) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - data = cloneData - } - serializedData, err := underlyingType.Serialize(data) - if err != nil { - return nil, err - } - mapData := serializedData.(map[string]any) - if _, ok := mapData[o.DiscriminatorFieldNameValue]; !ok { - mapData[o.DiscriminatorFieldNameValue] = discriminatorValue - } - return mapData, nil + discriminatorValue, underlyingType, err := o.findUnderlyingType(data) + if err != nil { + return nil, err + } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } + serializedData, err := underlyingType.Serialize(data) + if err != nil { + return nil, err + } + mapData := serializedData.(map[string]any) + if _, ok := mapData[o.DiscriminatorFieldNameValue]; !ok { + mapData[o.DiscriminatorFieldNameValue] = discriminatorValue + } + return mapData, nil } func (o OneOfSchema[KeyType]) Unserialize(data any) (any, error) { - return o.UnserializeType(data) + return o.UnserializeType(data) } func (o OneOfSchema[KeyType]) ValidateCompatibility(typeOrData any) error { - // If a schema is given, validate that it's a oneof schema. If it isn't, fail. - // If a schema is not given, validate as data. - - // Check if it's a map. If it is, verify it. If not, check if it's a schema, if it is, verify it. - // If not, verify it as data. - inputAsMap, ok := typeOrData.(map[string]any) - if ok { - _, _, err := o.validateMap(inputAsMap) - return err - } - value := reflect.ValueOf(typeOrData) - if reflect.Indirect(value).Kind() != reflect.Struct { - // Validate as data - return o.Validate(typeOrData) - } - - inputAsIndirectInterface := reflect.Indirect(value).Interface() - - // Validate the oneof and key types - schemaType, ok := inputAsIndirectInterface.(OneOfSchema[KeyType]) - if !ok { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Found type (%T) does not match expected type (%T)", - inputAsIndirectInterface, o), - } - } - - return o.validateSchema(schemaType) + // If a schema is given, validate that it's a oneof schema. If it isn't, fail. + // If a schema is not given, validate as data. + + // Check if it's a map. If it is, verify it. If not, check if it's a schema, if it is, verify it. + // If not, verify it as data. + inputAsMap, ok := typeOrData.(map[string]any) + if ok { + _, _, err := o.validateMap(inputAsMap) + return err + } + value := reflect.ValueOf(typeOrData) + if reflect.Indirect(value).Kind() != reflect.Struct { + // Validate as data + return o.Validate(typeOrData) + } + + inputAsIndirectInterface := reflect.Indirect(value).Interface() + + // Validate the oneof and key types + schemaType, ok := inputAsIndirectInterface.(OneOfSchema[KeyType]) + if !ok { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Found type (%T) does not match expected type (%T)", + inputAsIndirectInterface, o), + } + } + + return o.validateSchema(schemaType) } func (o OneOfSchema[KeyType]) validateSchema(otherSchema OneOfSchema[KeyType]) error { - // Validate that the discriminator fields match, and all other values match. - - // Validate the discriminator field name - if otherSchema.DiscriminatorFieldName() != o.DiscriminatorFieldName() { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field name (%s) does not match expected field name (%s)", - otherSchema.DiscriminatorFieldName(), o.DiscriminatorFieldName()), - } - } - // Validate the key values and matching types - for key, typeValue := range o.Types() { - matchingTypeValue := otherSchema.Types()[key] - if matchingTypeValue == nil { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. OneOf key '%v' is not present in given type", key), - } - } - err := typeValue.ValidateCompatibility(matchingTypeValue) - if err != nil { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. OneOf key '%v' does not have a compatible object schema (%s) ", - key, err), - } - } - } - return nil + // Validate that the discriminator fields match, and all other values match. + + // Validate the discriminator field name + if otherSchema.DiscriminatorFieldName() != o.DiscriminatorFieldName() { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field name (%s) does not match expected field name (%s)", + otherSchema.DiscriminatorFieldName(), o.DiscriminatorFieldName()), + } + } + // Validate the key values and matching types + for key, typeValue := range o.Types() { + matchingTypeValue := otherSchema.Types()[key] + if matchingTypeValue == nil { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. OneOf key '%v' is not present in given type", key), + } + } + err := typeValue.ValidateCompatibility(matchingTypeValue) + if err != nil { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. OneOf key '%v' does not have a compatible object schema (%s) ", + key, err), + } + } + } + return nil } func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, error) { - var nilKey KeyType - // Validate that it has the discriminator field. - // If it doesn't, fail - // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object - selectedTypeID := data[o.DiscriminatorFieldNameValue] - if selectedTypeID == nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), - } - } - // Ensure it's the correct type - selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) - if !ok { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", - o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), - } - } - // Find the object that's associated with the selected type - selectedSchema := o.TypesValue[selectedTypeIDAsserted] - if selectedSchema == nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", - 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. - } - data = cloneData - - err := selectedSchema.ValidateCompatibility(cloneData) - if err != nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", - selectedSchema, selectedTypeIDAsserted, err), - } - } - return selectedTypeIDAsserted, selectedSchema, nil + var nilKey KeyType + // Validate that it has the discriminator field. + // If it doesn't, fail + // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object + selectedTypeID := data[o.DiscriminatorFieldNameValue] + if selectedTypeID == nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), + } + } + // Ensure it's the correct type + selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) + if !ok { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", + o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), + } + } + // Find the object that's associated with the selected type + selectedSchema := o.TypesValue[selectedTypeIDAsserted] + if selectedSchema == nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", + 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. + } + data = cloneData + + err := selectedSchema.ValidateCompatibility(cloneData) + if err != nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", + selectedSchema, selectedTypeIDAsserted, err), + } + } + return selectedTypeIDAsserted, selectedSchema, nil } func (o OneOfSchema[KeyType]) getTypeValues() []KeyType { - output := make([]KeyType, len(o.TypesValue)) - i := 0 - for key := range o.TypesValue { - output[i] = key - i += 1 - } - return output + output := make([]KeyType, len(o.TypesValue)) + i := 0 + for key := range o.TypesValue { + output[i] = key + i += 1 + } + return output } func (o OneOfSchema[KeyType]) Validate(data any) error { - if o.interfaceType == nil { - return o.ValidateType(data) - } - d, err := saveConvertTo(data, o.interfaceType) - if err != nil { - return err - } - return o.ValidateType(d) + d, err := saveConvertTo(data, o.ReflectedType()) + if err != nil { + return err + } + return o.ValidateType(d) } func (o OneOfSchema[KeyType]) Serialize(data any) (result any, err error) { - if o.interfaceType == nil { - return nil, o.ValidateType(data) - } - d, err := saveConvertTo(data, o.interfaceType) - if err != nil { - return nil, err - } - return o.SerializeType(d) + d, err := saveConvertTo(data, o.ReflectedType()) + if err != nil { + return nil, err + } + return o.SerializeType(d) } func (o OneOfSchema[KeyType]) getTypedDiscriminator(discriminator any) (KeyType, error) { - var typedDiscriminator KeyType - switch any(typedDiscriminator).(type) { - case int64: - intDiscriminator, err := intInputMapper(discriminator, nil) - if err != nil { - return typedDiscriminator, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type %T for field %s, expected %T", - discriminator, - o.DiscriminatorFieldNameValue, - typedDiscriminator, - ), - Cause: err, - } - } - typedDiscriminator = any(intDiscriminator).(KeyType) - case string: - stringDiscriminator, err := stringInputMapper(discriminator) - if err != nil { - return typedDiscriminator, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type %T for field %s, expected %T", - discriminator, - o.DiscriminatorFieldNameValue, - typedDiscriminator, - ), - Cause: err, - } - } - typedDiscriminator = any(stringDiscriminator).(KeyType) - } - return typedDiscriminator, nil + var typedDiscriminator KeyType + switch any(typedDiscriminator).(type) { + case int64: + intDiscriminator, err := intInputMapper(discriminator, nil) + if err != nil { + return typedDiscriminator, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type %T for field %s, expected %T", + discriminator, + o.DiscriminatorFieldNameValue, + typedDiscriminator, + ), + Cause: err, + } + } + typedDiscriminator = any(intDiscriminator).(KeyType) + case string: + stringDiscriminator, err := stringInputMapper(discriminator) + if err != nil { + return typedDiscriminator, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type %T for field %s, expected %T", + discriminator, + o.DiscriminatorFieldNameValue, + typedDiscriminator, + ), + Cause: err, + } + } + typedDiscriminator = any(stringDiscriminator).(KeyType) + } + return typedDiscriminator, nil } func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, error) { - var nilKey KeyType - - reflectedType := reflect.TypeOf(data) - if reflectedType.Kind() != reflect.Struct && - reflectedType.Kind() != reflect.Map && - (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { - - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of type: '%s' expected struct or map.", - reflect.TypeOf(data).Name(), - ), - } - } - - var foundKey *KeyType - if reflectedType.Kind() == reflect.Map { - myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) - if err != nil { - return nilKey, nil, err - } - return myKey, mySchemaObj, nil - } - // else - for key, ref := range o.TypesValue { - underlyingReflectedType := ref.ReflectedType() - if underlyingReflectedType == reflectedType { - keyValue := key - foundKey = &keyValue - } - } - if foundKey == nil { - dataType := reflect.TypeOf(data) - values := make([]string, len(o.TypesValue)) - i := 0 - for _, ref := range o.TypesValue { - values[i] = ref.ReflectedType().String() - if values[i] == "" { - panic(fmt.Errorf("bug: reflected type name is empty")) - } - i++ - } - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of schema: '%s' (valid types are: %s)", - dataType.String(), - strings.Join(values, ", "), - ), - } - } - return *foundKey, o.TypesValue[*foundKey], nil + var nilKey KeyType + + reflectedType := reflect.TypeOf(data) + if reflectedType.Kind() != reflect.Struct && + reflectedType.Kind() != reflect.Map && + (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { + + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of type: '%s' expected struct or map.", + reflect.TypeOf(data).Name(), + ), + } + } + + var foundKey *KeyType + if reflectedType.Kind() == reflect.Map { + myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) + if err != nil { + return nilKey, nil, err + } + return myKey, mySchemaObj, nil + } + // else + for key, ref := range o.TypesValue { + underlyingReflectedType := ref.ReflectedType() + if underlyingReflectedType == reflectedType { + keyValue := key + foundKey = &keyValue + } + } + if foundKey == nil { + dataType := reflect.TypeOf(data) + values := make([]string, len(o.TypesValue)) + i := 0 + for _, ref := range o.TypesValue { + values[i] = ref.ReflectedType().String() + if values[i] == "" { + panic(fmt.Errorf("bug: reflected type name is empty")) + } + i++ + } + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of schema: '%s' (valid types are: %s)", + dataType.String(), + strings.Join(values, ", "), + ), + } + } + return *foundKey, o.TypesValue[*foundKey], nil } // validateSubtypeDiscriminatorInlineFields checks to see if a subtype's // discriminator field has been written in accordance with the OneOfSchema's // declaration. func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error { - for key, typeValue := range o.TypesValue { - typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - switch { - case !o.DiscriminatorInlined && hasDiscriminator: - return fmt.Errorf( - "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - case o.DiscriminatorInlined && !hasDiscriminator: - return fmt.Errorf( - "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - case o.DiscriminatorInlined && hasDiscriminator && - (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()): - return fmt.Errorf( - "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", - typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) - } - } - return nil + for key, typeValue := range o.TypesValue { + typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + switch { + case !o.DiscriminatorInlined && hasDiscriminator: + return fmt.Errorf( + "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + case o.DiscriminatorInlined && !hasDiscriminator: + return fmt.Errorf( + "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + case o.DiscriminatorInlined && hasDiscriminator && + (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()): + return fmt.Errorf( + "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) + } + } + return nil } diff --git a/schema/oneof_string.go b/schema/oneof_string.go index 52ce02e..3401adf 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -1,25 +1,27 @@ 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 // (the discriminator) which tells a parsing party which type it is. The field type in this case is a string. type OneOfString interface { - OneOf[string] + OneOf[string] } // NewOneOfStringSchema creates a new OneOf-type with integer discriminators. func NewOneOfStringSchema[ItemsInterface any]( - types map[string]Object, - discriminatorFieldName string, - discriminatorInlined bool, + types map[string]Object, + discriminatorFieldName string, + discriminatorInlined bool, ) *OneOfSchema[string] { - var defaultValue ItemsInterface - return &OneOfSchema[string]{ - reflect.TypeOf(&defaultValue).Elem(), - types, - discriminatorFieldName, - discriminatorInlined, - } + var defaultValue ItemsInterface + return &OneOfSchema[string]{ + reflect.TypeOf(&defaultValue).Elem(), + types, + discriminatorFieldName, + discriminatorInlined, + } } From 68d8078f287016f0e03b33e3767b013421c051bb Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Fri, 16 Feb 2024 19:31:01 -0500 Subject: [PATCH 30/43] also lint --- schema/oneof.go | 735 ++++++++++++++++++++--------------------- schema/oneof_string.go | 24 +- 2 files changed, 379 insertions(+), 380 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 1700710..a1d1c8a 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -1,438 +1,437 @@ package schema import ( - "fmt" - "maps" - "reflect" - "strings" + "fmt" + "maps" + "reflect" + "strings" ) // OneOf is the root interface for one-of types. It should not be used directly but is provided for convenience. type OneOf[KeyType int64 | string] interface { - Type + Type - Types() map[KeyType]Object - DiscriminatorFieldName() string + Types() map[KeyType]Object + DiscriminatorFieldName() string } type OneOfSchema[KeyType int64 | string] struct { - interfaceType reflect.Type - TypesValue map[KeyType]Object `json:"types"` - DiscriminatorFieldNameValue string `json:"discriminator_field_name"` - // whether or not the discriminator is inlined in the underlying objects' schema - DiscriminatorInlined bool `json:"discriminator_inlined"` + interfaceType reflect.Type + TypesValue map[KeyType]Object `json:"types"` + DiscriminatorFieldNameValue string `json:"discriminator_field_name"` + // whether or not the discriminator is inlined in the underlying objects' schema + DiscriminatorInlined bool `json:"discriminator_inlined"` } func (o OneOfSchema[KeyType]) TypeID() TypeID { - var defaultValue KeyType - switch any(defaultValue).(type) { - case int64: - return TypeIDOneOfInt - case string: - return TypeIDOneOfString - default: - panic(BadArgumentError{Message: fmt.Sprintf("Unexpected key type: %T", defaultValue)}) - } + var defaultValue KeyType + switch any(defaultValue).(type) { + case int64: + return TypeIDOneOfInt + case string: + return TypeIDOneOfString + default: + panic(BadArgumentError{Message: fmt.Sprintf("Unexpected key type: %T", defaultValue)}) + } } func (o OneOfSchema[KeyType]) Types() map[KeyType]Object { - return o.TypesValue + return o.TypesValue } func (o OneOfSchema[KeyType]) DiscriminatorFieldName() string { - return o.DiscriminatorFieldNameValue + return o.DiscriminatorFieldNameValue } func (o OneOfSchema[KeyType]) ApplyScope(scope Scope) { - for _, t := range o.TypesValue { - t.ApplyScope(scope) - } - // scope must be applied before we can access the subtypes' properties - err := o.validateSubtypeDiscriminatorInlineFields() - if err != nil { - panic(err) - } + for _, t := range o.TypesValue { + t.ApplyScope(scope) + } + // scope must be applied before we can access the subtypes' properties + err := o.validateSubtypeDiscriminatorInlineFields() + if err != nil { + panic(err) + } } func (o OneOfSchema[KeyType]) ReflectedType() reflect.Type { - if o.interfaceType == nil { - var defaultValue any - return reflect.TypeOf(&defaultValue).Elem() - } - return o.interfaceType + if o.interfaceType == nil { + var defaultValue any + return reflect.TypeOf(&defaultValue).Elem() + } + return o.interfaceType } //nolint:funlen func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) { - if data == nil { - return nil, fmt.Errorf("bug: data is nil in OneOfSchema UnserializeType") - } - reflectedValue := reflect.ValueOf(data) - if reflectedValue.Kind() != reflect.Map { - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of type: '%s'. Expected map.", - reflect.TypeOf(data).Name(), - ), - } - } - - discriminatorValue := reflectedValue.MapIndex(reflect.ValueOf(o.DiscriminatorFieldNameValue)) - if !discriminatorValue.IsValid() { - return result, &ConstraintError{ - Message: fmt.Sprintf("Missing discriminator field '%s' in '%v'", o.DiscriminatorFieldNameValue, data), - } - } - discriminator := discriminatorValue.Interface() - typedDiscriminator, err := o.getTypedDiscriminator(discriminator) - if err != nil { - return result, err - } - - typedData := make(map[string]any, reflectedValue.Len()) - for _, k := range reflectedValue.MapKeys() { - v := reflectedValue.MapIndex(k) - keyString, ok := k.Interface().(string) - if !ok { - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid key type for one-of: '%T'", - k.Interface(), - ), - } - } - typedData[keyString] = v.Interface() - } - - selectedType, ok := o.TypesValue[typedDiscriminator] - if !ok { - validDiscriminators := make([]string, len(o.TypesValue)) - i := 0 - for k := range o.TypesValue { - validDiscriminators[i] = fmt.Sprintf("%v", k) - i++ - } - return result, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid value for '%s', expected one of: %s", - o.DiscriminatorFieldNameValue, - strings.Join(validDiscriminators, ", "), - ), - } - } - - cloneData := maps.Clone(typedData) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - 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()) + if data == nil { + return nil, fmt.Errorf("bug: data is nil in OneOfSchema UnserializeType") + } + reflectedValue := reflect.ValueOf(data) + if reflectedValue.Kind() != reflect.Map { + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of type: '%s'. Expected map.", + reflect.TypeOf(data).Name(), + ), + } + } + + discriminatorValue := reflectedValue.MapIndex(reflect.ValueOf(o.DiscriminatorFieldNameValue)) + if !discriminatorValue.IsValid() { + return result, &ConstraintError{ + Message: fmt.Sprintf("Missing discriminator field '%s' in '%v'", o.DiscriminatorFieldNameValue, data), + } + } + discriminator := discriminatorValue.Interface() + typedDiscriminator, err := o.getTypedDiscriminator(discriminator) + if err != nil { + return result, err + } + + typedData := make(map[string]any, reflectedValue.Len()) + for _, k := range reflectedValue.MapKeys() { + v := reflectedValue.MapIndex(k) + keyString, ok := k.Interface().(string) + if !ok { + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid key type for one-of: '%T'", + k.Interface(), + ), + } + } + typedData[keyString] = v.Interface() + } + + selectedType, ok := o.TypesValue[typedDiscriminator] + if !ok { + validDiscriminators := make([]string, len(o.TypesValue)) + i := 0 + for k := range o.TypesValue { + validDiscriminators[i] = fmt.Sprintf("%v", k) + i++ + } + return result, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid value for '%s', expected one of: %s", + o.DiscriminatorFieldNameValue, + strings.Join(validDiscriminators, ", "), + ), + } + } + + cloneData := maps.Clone(typedData) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + 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()) } func (o OneOfSchema[KeyType]) ValidateType(data any) error { - discriminatorValue, underlyingType, err := o.findUnderlyingType(data) - if err != nil { - return err - } - dataMap, ok := data.(map[string]any) - if ok { - cloneData := maps.Clone(dataMap) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - data = cloneData - } - if err := underlyingType.Validate(data); err != nil { - return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue)) - } - return nil + discriminatorValue, underlyingType, err := o.findUnderlyingType(data) + if err != nil { + return err + } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } + if err := underlyingType.Validate(data); err != nil { + return ConstraintErrorAddPathSegment(err, fmt.Sprintf("{oneof[%v]}", discriminatorValue)) + } + return nil } func (o OneOfSchema[KeyType]) SerializeType(data any) (any, error) { - discriminatorValue, underlyingType, err := o.findUnderlyingType(data) - if err != nil { - return nil, err - } - dataMap, ok := data.(map[string]any) - if ok { - cloneData := maps.Clone(dataMap) - if !o.DiscriminatorInlined { - delete(cloneData, o.DiscriminatorFieldNameValue) - } - data = cloneData - } - serializedData, err := underlyingType.Serialize(data) - if err != nil { - return nil, err - } - mapData := serializedData.(map[string]any) - if _, ok := mapData[o.DiscriminatorFieldNameValue]; !ok { - mapData[o.DiscriminatorFieldNameValue] = discriminatorValue - } - return mapData, nil + discriminatorValue, underlyingType, err := o.findUnderlyingType(data) + if err != nil { + return nil, err + } + dataMap, ok := data.(map[string]any) + if ok { + cloneData := maps.Clone(dataMap) + if !o.DiscriminatorInlined { + delete(cloneData, o.DiscriminatorFieldNameValue) + } + data = cloneData + } + serializedData, err := underlyingType.Serialize(data) + if err != nil { + return nil, err + } + mapData := serializedData.(map[string]any) + if _, ok := mapData[o.DiscriminatorFieldNameValue]; !ok { + mapData[o.DiscriminatorFieldNameValue] = discriminatorValue + } + return mapData, nil } func (o OneOfSchema[KeyType]) Unserialize(data any) (any, error) { - return o.UnserializeType(data) + return o.UnserializeType(data) } func (o OneOfSchema[KeyType]) ValidateCompatibility(typeOrData any) error { - // If a schema is given, validate that it's a oneof schema. If it isn't, fail. - // If a schema is not given, validate as data. - - // Check if it's a map. If it is, verify it. If not, check if it's a schema, if it is, verify it. - // If not, verify it as data. - inputAsMap, ok := typeOrData.(map[string]any) - if ok { - _, _, err := o.validateMap(inputAsMap) - return err - } - value := reflect.ValueOf(typeOrData) - if reflect.Indirect(value).Kind() != reflect.Struct { - // Validate as data - return o.Validate(typeOrData) - } - - inputAsIndirectInterface := reflect.Indirect(value).Interface() - - // Validate the oneof and key types - schemaType, ok := inputAsIndirectInterface.(OneOfSchema[KeyType]) - if !ok { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Found type (%T) does not match expected type (%T)", - inputAsIndirectInterface, o), - } - } - - return o.validateSchema(schemaType) + // If a schema is given, validate that it's a oneof schema. If it isn't, fail. + // If a schema is not given, validate as data. + + // Check if it's a map. If it is, verify it. If not, check if it's a schema, if it is, verify it. + // If not, verify it as data. + inputAsMap, ok := typeOrData.(map[string]any) + if ok { + _, _, err := o.validateMap(inputAsMap) + return err + } + value := reflect.ValueOf(typeOrData) + if reflect.Indirect(value).Kind() != reflect.Struct { + // Validate as data + return o.Validate(typeOrData) + } + + inputAsIndirectInterface := reflect.Indirect(value).Interface() + + // Validate the oneof and key types + schemaType, ok := inputAsIndirectInterface.(OneOfSchema[KeyType]) + if !ok { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Found type (%T) does not match expected type (%T)", + inputAsIndirectInterface, o), + } + } + + return o.validateSchema(schemaType) } func (o OneOfSchema[KeyType]) validateSchema(otherSchema OneOfSchema[KeyType]) error { - // Validate that the discriminator fields match, and all other values match. - - // Validate the discriminator field name - if otherSchema.DiscriminatorFieldName() != o.DiscriminatorFieldName() { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field name (%s) does not match expected field name (%s)", - otherSchema.DiscriminatorFieldName(), o.DiscriminatorFieldName()), - } - } - // Validate the key values and matching types - for key, typeValue := range o.Types() { - matchingTypeValue := otherSchema.Types()[key] - if matchingTypeValue == nil { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. OneOf key '%v' is not present in given type", key), - } - } - err := typeValue.ValidateCompatibility(matchingTypeValue) - if err != nil { - return &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. OneOf key '%v' does not have a compatible object schema (%s) ", - key, err), - } - } - } - return nil + // Validate that the discriminator fields match, and all other values match. + + // Validate the discriminator field name + if otherSchema.DiscriminatorFieldName() != o.DiscriminatorFieldName() { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field name (%s) does not match expected field name (%s)", + otherSchema.DiscriminatorFieldName(), o.DiscriminatorFieldName()), + } + } + // Validate the key values and matching types + for key, typeValue := range o.Types() { + matchingTypeValue := otherSchema.Types()[key] + if matchingTypeValue == nil { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. OneOf key '%v' is not present in given type", key), + } + } + err := typeValue.ValidateCompatibility(matchingTypeValue) + if err != nil { + return &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. OneOf key '%v' does not have a compatible object schema (%s) ", + key, err), + } + } + } + return nil } func (o OneOfSchema[KeyType]) validateMap(data map[string]any) (KeyType, Object, error) { - var nilKey KeyType - // Validate that it has the discriminator field. - // If it doesn't, fail - // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object - selectedTypeID := data[o.DiscriminatorFieldNameValue] - if selectedTypeID == nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), - } - } - // Ensure it's the correct type - selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) - if !ok { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", - o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), - } - } - // Find the object that's associated with the selected type - selectedSchema := o.TypesValue[selectedTypeIDAsserted] - if selectedSchema == nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", - 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. - } - data = cloneData - - err := selectedSchema.ValidateCompatibility(cloneData) - if err != nil { - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", - selectedSchema, selectedTypeIDAsserted, err), - } - } - return selectedTypeIDAsserted, selectedSchema, nil + var nilKey KeyType + // Validate that it has the discriminator field. + // If it doesn't, fail + // If it does, pass the non-discriminator fields into the ValidateCompatibility method for the object + selectedTypeID := data[o.DiscriminatorFieldNameValue] + if selectedTypeID == nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%s' missing", o.DiscriminatorFieldNameValue), + } + } + // Ensure it's the correct type + selectedTypeIDAsserted, ok := selectedTypeID.(KeyType) + if !ok { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator field '%v' has invalid type '%T'. Expected %T", + o.DiscriminatorFieldNameValue, selectedTypeID, selectedTypeIDAsserted), + } + } + // Find the object that's associated with the selected type + selectedSchema := o.TypesValue[selectedTypeIDAsserted] + if selectedSchema == nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Discriminator value '%v' is invalid. Expected one of: %v", + 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. + } + + err := selectedSchema.ValidateCompatibility(cloneData) + if err != nil { + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "validation failed for OneOfSchema. Failed to validate as selected schema type '%T' from discriminator value '%v' (%s)", + selectedSchema, selectedTypeIDAsserted, err), + } + } + return selectedTypeIDAsserted, selectedSchema, nil } func (o OneOfSchema[KeyType]) getTypeValues() []KeyType { - output := make([]KeyType, len(o.TypesValue)) - i := 0 - for key := range o.TypesValue { - output[i] = key - i += 1 - } - return output + output := make([]KeyType, len(o.TypesValue)) + i := 0 + for key := range o.TypesValue { + output[i] = key + i += 1 + } + return output } func (o OneOfSchema[KeyType]) Validate(data any) error { - d, err := saveConvertTo(data, o.ReflectedType()) - if err != nil { - return err - } - return o.ValidateType(d) + d, err := saveConvertTo(data, o.ReflectedType()) + if err != nil { + return err + } + return o.ValidateType(d) } func (o OneOfSchema[KeyType]) Serialize(data any) (result any, err error) { - d, err := saveConvertTo(data, o.ReflectedType()) - if err != nil { - return nil, err - } - return o.SerializeType(d) + d, err := saveConvertTo(data, o.ReflectedType()) + if err != nil { + return nil, err + } + return o.SerializeType(d) } func (o OneOfSchema[KeyType]) getTypedDiscriminator(discriminator any) (KeyType, error) { - var typedDiscriminator KeyType - switch any(typedDiscriminator).(type) { - case int64: - intDiscriminator, err := intInputMapper(discriminator, nil) - if err != nil { - return typedDiscriminator, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type %T for field %s, expected %T", - discriminator, - o.DiscriminatorFieldNameValue, - typedDiscriminator, - ), - Cause: err, - } - } - typedDiscriminator = any(intDiscriminator).(KeyType) - case string: - stringDiscriminator, err := stringInputMapper(discriminator) - if err != nil { - return typedDiscriminator, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type %T for field %s, expected %T", - discriminator, - o.DiscriminatorFieldNameValue, - typedDiscriminator, - ), - Cause: err, - } - } - typedDiscriminator = any(stringDiscriminator).(KeyType) - } - return typedDiscriminator, nil + var typedDiscriminator KeyType + switch any(typedDiscriminator).(type) { + case int64: + intDiscriminator, err := intInputMapper(discriminator, nil) + if err != nil { + return typedDiscriminator, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type %T for field %s, expected %T", + discriminator, + o.DiscriminatorFieldNameValue, + typedDiscriminator, + ), + Cause: err, + } + } + typedDiscriminator = any(intDiscriminator).(KeyType) + case string: + stringDiscriminator, err := stringInputMapper(discriminator) + if err != nil { + return typedDiscriminator, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type %T for field %s, expected %T", + discriminator, + o.DiscriminatorFieldNameValue, + typedDiscriminator, + ), + Cause: err, + } + } + typedDiscriminator = any(stringDiscriminator).(KeyType) + } + return typedDiscriminator, nil } func (o OneOfSchema[KeyType]) findUnderlyingType(data any) (KeyType, Object, error) { - var nilKey KeyType - - reflectedType := reflect.TypeOf(data) - if reflectedType.Kind() != reflect.Struct && - reflectedType.Kind() != reflect.Map && - (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { - - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of type: '%s' expected struct or map.", - reflect.TypeOf(data).Name(), - ), - } - } - - var foundKey *KeyType - if reflectedType.Kind() == reflect.Map { - myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) - if err != nil { - return nilKey, nil, err - } - return myKey, mySchemaObj, nil - } - // else - for key, ref := range o.TypesValue { - underlyingReflectedType := ref.ReflectedType() - if underlyingReflectedType == reflectedType { - keyValue := key - foundKey = &keyValue - } - } - if foundKey == nil { - dataType := reflect.TypeOf(data) - values := make([]string, len(o.TypesValue)) - i := 0 - for _, ref := range o.TypesValue { - values[i] = ref.ReflectedType().String() - if values[i] == "" { - panic(fmt.Errorf("bug: reflected type name is empty")) - } - i++ - } - return nilKey, nil, &ConstraintError{ - Message: fmt.Sprintf( - "Invalid type for one-of schema: '%s' (valid types are: %s)", - dataType.String(), - strings.Join(values, ", "), - ), - } - } - return *foundKey, o.TypesValue[*foundKey], nil + var nilKey KeyType + + reflectedType := reflect.TypeOf(data) + if reflectedType.Kind() != reflect.Struct && + reflectedType.Kind() != reflect.Map && + (reflectedType.Kind() != reflect.Pointer || reflectedType.Elem().Kind() != reflect.Struct) { + + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of type: '%s' expected struct or map.", + reflect.TypeOf(data).Name(), + ), + } + } + + var foundKey *KeyType + if reflectedType.Kind() == reflect.Map { + myKey, mySchemaObj, err := o.validateMap(data.(map[string]any)) + if err != nil { + return nilKey, nil, err + } + return myKey, mySchemaObj, nil + } + // else + for key, ref := range o.TypesValue { + underlyingReflectedType := ref.ReflectedType() + if underlyingReflectedType == reflectedType { + keyValue := key + foundKey = &keyValue + } + } + if foundKey == nil { + dataType := reflect.TypeOf(data) + values := make([]string, len(o.TypesValue)) + i := 0 + for _, ref := range o.TypesValue { + values[i] = ref.ReflectedType().String() + if values[i] == "" { + panic(fmt.Errorf("bug: reflected type name is empty")) + } + i++ + } + return nilKey, nil, &ConstraintError{ + Message: fmt.Sprintf( + "Invalid type for one-of schema: '%s' (valid types are: %s)", + dataType.String(), + strings.Join(values, ", "), + ), + } + } + return *foundKey, o.TypesValue[*foundKey], nil } // validateSubtypeDiscriminatorInlineFields checks to see if a subtype's // discriminator field has been written in accordance with the OneOfSchema's // declaration. func (o OneOfSchema[KeyType]) validateSubtypeDiscriminatorInlineFields() error { - for key, typeValue := range o.TypesValue { - typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] - switch { - case !o.DiscriminatorInlined && hasDiscriminator: - return fmt.Errorf( - "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - case o.DiscriminatorInlined && !hasDiscriminator: - return fmt.Errorf( - "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", - typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) - case o.DiscriminatorInlined && hasDiscriminator && - (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()): - return fmt.Errorf( - "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", - typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) - } - } - return nil + for key, typeValue := range o.TypesValue { + typeValueDiscriminatorValue, hasDiscriminator := typeValue.Properties()[o.DiscriminatorFieldNameValue] + switch { + case !o.DiscriminatorInlined && hasDiscriminator: + return fmt.Errorf( + "object id %q has conflicting field %q; either remove that field or set inline to true for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + case o.DiscriminatorInlined && !hasDiscriminator: + return fmt.Errorf( + "object id %q needs discriminator field %q; either add that field or set inline to false for %T[%T]", + typeValue.ID(), o.DiscriminatorFieldNameValue, o, key) + case o.DiscriminatorInlined && hasDiscriminator && + (typeValueDiscriminatorValue.ReflectedType().Kind() != reflect.TypeOf(key).Kind()): + return fmt.Errorf( + "the type of object id %v's discriminator field %q does not match OneOfSchema discriminator type; expected %v got %T", + typeValue.ID(), o.DiscriminatorFieldNameValue, typeValueDiscriminatorValue.TypeID(), key) + } + } + return nil } diff --git a/schema/oneof_string.go b/schema/oneof_string.go index 3401adf..a4c3e9a 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -1,27 +1,27 @@ package schema import ( - "reflect" + "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 // (the discriminator) which tells a parsing party which type it is. The field type in this case is a string. type OneOfString interface { - OneOf[string] + OneOf[string] } // NewOneOfStringSchema creates a new OneOf-type with integer discriminators. func NewOneOfStringSchema[ItemsInterface any]( - types map[string]Object, - discriminatorFieldName string, - discriminatorInlined bool, + types map[string]Object, + discriminatorFieldName string, + discriminatorInlined bool, ) *OneOfSchema[string] { - var defaultValue ItemsInterface - return &OneOfSchema[string]{ - reflect.TypeOf(&defaultValue).Elem(), - types, - discriminatorFieldName, - discriminatorInlined, - } + var defaultValue ItemsInterface + return &OneOfSchema[string]{ + reflect.TypeOf(&defaultValue).Elem(), + types, + discriminatorFieldName, + discriminatorInlined, + } } From 6551c0d448fcc9a634daa7623dd328c6d29d54eb Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Mon, 19 Feb 2024 13:00:23 -0500 Subject: [PATCH 31/43] add oneof scope scope schema --- schema/oneof_test.go | 91 ++++++++++++++++++++++++++++++++++++++++- schema/schema_schema.go | 28 +++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index 63f1c54..9a27e5b 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -1,9 +1,11 @@ package schema_test import ( - "go.arcalot.io/assert" + "encoding/json" + "fmt" "testing" + "go.arcalot.io/assert" "go.flow.arcalot.io/pluginsdk/schema" ) @@ -205,3 +207,90 @@ var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestI "C", oneOfTestInlineObjectCProperties, ) + +func Test_OneOfString_ConstructorBypass(t *testing.T) { + data := `{ + "objects": { + "FullName": { + "id": "FullName", + "properties": { + "first_name": { + "required": true, + "type": { + "type_id": "string" + } + }, + "last_name": { + "required": true, + "type": { + "type_id": "string" + } + } + } + }, + "Nickname": { + "id": "Nickname", + "properties": { + "nick": { + "required": true, + "type": { + "type_id": "string" + } + } + } + }, + "InputParams": { + "id": "InputParams", + "properties": { + "name": { + "required": true, + "type": { + "discriminator_field_name": "_type", + "type_id": "one_of_string", + "types": { + "fullname": { + "id": "FullName", + "type_id": "ref" + }, + "nickname": { + "id": "Nickname", + "type_id": "ref" + } + } + } + } + } + } + }, + "root": "InputParams" +}` + var input any + assert.NoError(t, json.Unmarshal([]byte(data), &input)) + fmt.Printf("%v\n", input) + myScopeSchema := schema.DescribeScope() + scopeAny, err := myScopeSchema.Unserialize(input) + assert.NoError(t, err) + scopeSchemaTyped := scopeAny.(*schema.ScopeSchema) + scopeSchemaTyped.ApplyScope(scopeSchemaTyped) + fmt.Printf("%v\n", scopeSchemaTyped) + + //var input_nick any = map[string]any{ + // "name": map[string]any{ + // "_type": "nickname", + // "nick": "ArcaLot", + // }, + //} + + var input_full any = map[string]any{ + "name": map[string]any{ + "_type": "fullname", + "first_name": "Arca", + "last_name": "Lot", + }, + } + + unserializedData, err := scopeSchemaTyped.Unserialize(input_full) + assert.NoError(t, err) + fmt.Printf("%v\n", unserializedData) + +} diff --git a/schema/schema_schema.go b/schema/schema_schema.go index 743d5ba..67843c2 100644 --- a/schema/schema_schema.go +++ b/schema/schema_schema.go @@ -532,6 +532,20 @@ var basicObjects = []*ObjectSchema{ NewStructMappedObjectSchema[*OneOfSchema[int64]]( "OneOfIntSchema", map[string]*PropertySchema{ + "discriminator_inlined": NewPropertySchema( + NewBoolSchema(), + NewDisplayValue( + PointerTo("Discriminator Inlined"), + PointerTo("whether or not the discriminator is inlined in the underlying objects' schema."), + nil, + ), + false, + nil, + nil, + nil, + nil, + []string{"\"inlined\""}, + ), "discriminator_field_name": NewPropertySchema( NewStringSchema(nil, nil, nil), NewDisplayValue( @@ -579,6 +593,20 @@ var basicObjects = []*ObjectSchema{ NewStructMappedObjectSchema[*OneOfSchema[string]]( "OneOfStringSchema", map[string]*PropertySchema{ + "discriminator_inlined": NewPropertySchema( + NewBoolSchema(), + NewDisplayValue( + PointerTo("Discriminator Inlined"), + PointerTo("whether or not the discriminator is inlined in the underlying objects' schema."), + nil, + ), + false, + nil, + nil, + nil, + nil, + []string{"\"inlined\""}, + ), "discriminator_field_name": NewPropertySchema( NewStringSchema(nil, nil, nil), NewDisplayValue( From 2794142a3b605d2347a5800e0f7f0a9b1b30af5a Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Mon, 19 Feb 2024 14:38:00 -0500 Subject: [PATCH 32/43] dry out test --- schema/oneof_test.go | 144 ++++++++++++++++++---------------------- schema/schema_schema.go | 8 +-- 2 files changed, 68 insertions(+), 84 deletions(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index 9a27e5b..ba91c4d 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -1,8 +1,6 @@ package schema_test import ( - "encoding/json" - "fmt" "testing" "go.arcalot.io/assert" @@ -209,88 +207,74 @@ var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestI ) func Test_OneOfString_ConstructorBypass(t *testing.T) { - data := `{ - "objects": { - "FullName": { - "id": "FullName", - "properties": { - "first_name": { - "required": true, - "type": { - "type_id": "string" - } - }, - "last_name": { - "required": true, - "type": { - "type_id": "string" - } - } - } - }, - "Nickname": { - "id": "Nickname", - "properties": { - "nick": { - "required": true, - "type": { - "type_id": "string" - } - } - } - }, - "InputParams": { - "id": "InputParams", - "properties": { - "name": { - "required": true, - "type": { - "discriminator_field_name": "_type", - "type_id": "one_of_string", - "types": { - "fullname": { - "id": "FullName", - "type_id": "ref" - }, - "nickname": { - "id": "Nickname", - "type_id": "ref" - } - } - } - } - } - } - }, - "root": "InputParams" -}` - var input any - assert.NoError(t, json.Unmarshal([]byte(data), &input)) - fmt.Printf("%v\n", input) - myScopeSchema := schema.DescribeScope() - scopeAny, err := myScopeSchema.Unserialize(input) - assert.NoError(t, err) - scopeSchemaTyped := scopeAny.(*schema.ScopeSchema) - scopeSchemaTyped.ApplyScope(scopeSchemaTyped) - fmt.Printf("%v\n", scopeSchemaTyped) - - //var input_nick any = map[string]any{ - // "name": map[string]any{ - // "_type": "nickname", - // "nick": "ArcaLot", - // }, - //} - - var input_full any = map[string]any{ + input_schema := map[string]any{ + "root": "InputParams", + "objects": map[string]any{ + "InputParams": map[string]any{ + "id": "InputParams", + "properties": map[string]any{ + "name": map[string]any{ + "required": true, + "type": map[string]any{ + "discriminator_field_name": "_type", + "discriminator_inlined": false, + "type_id": "one_of_string", + "types": map[string]any{ + "fullname": map[string]any{ + "id": "FullName", + "type_id": "ref", + }, + "nick": map[string]any{ + "id": "Nickname", + "type_id": "ref", + }, + }, + }, + }, + }, + }, + "FullName": map[string]any{ + "id": "FullName", + "properties": map[string]any{ + "first_name": map[string]any{ + "required": true, + "type": map[string]any{ + "type_id": "string", + }, + }, + "last_name": map[string]any{ + "required": true, + "type": map[string]any{ + "type_id": "string", + }, + }, + }, + }, + "Nickname": map[string]any{ + "id": "Nickname", + "properties": map[string]any{ + "nick": map[string]any{ + "required": true, + "type": map[string]any{ + "type_id": "string", + }, + }, + }, + }, + }, + } + var input_data_fullname any = map[string]any{ "name": map[string]any{ "_type": "fullname", "first_name": "Arca", "last_name": "Lot", }, } - - unserializedData, err := scopeSchemaTyped.Unserialize(input_full) - assert.NoError(t, err) - fmt.Printf("%v\n", unserializedData) - + scopeAny := assert.NoErrorR[any](t)(schema.DescribeScope().Unserialize(input_schema)) + scopeSchemaTyped := scopeAny.(*schema.ScopeSchema) + scopeSchemaTyped.ApplyScope(scopeSchemaTyped) + unserialized := assert.NoErrorR[any](t)(scopeSchemaTyped.Unserialize(input_data_fullname)) + serialized := assert.NoErrorR[any](t)(scopeSchemaTyped.Serialize(unserialized)) + unserialized2 := assert.NoErrorR[any](t)(scopeSchemaTyped.Unserialize(serialized)) + assert.Equals(t, unserialized2, unserialized) } diff --git a/schema/schema_schema.go b/schema/schema_schema.go index 67843c2..756e9c6 100644 --- a/schema/schema_schema.go +++ b/schema/schema_schema.go @@ -539,7 +539,7 @@ var basicObjects = []*ObjectSchema{ PointerTo("whether or not the discriminator is inlined in the underlying objects' schema."), nil, ), - false, + true, nil, nil, nil, @@ -554,7 +554,7 @@ var basicObjects = []*ObjectSchema{ "field is present on any of the component objects it must also be an int."), nil, ), - false, + true, nil, nil, nil, @@ -600,7 +600,7 @@ var basicObjects = []*ObjectSchema{ PointerTo("whether or not the discriminator is inlined in the underlying objects' schema."), nil, ), - false, + true, nil, nil, nil, @@ -615,7 +615,7 @@ var basicObjects = []*ObjectSchema{ "field is present on any of the component objects it must also be an int."), nil, ), - false, + true, nil, nil, nil, From 0eba29fefe8d424ca97007e5b82f0b68f458b9d1 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Mon, 19 Feb 2024 14:39:20 -0500 Subject: [PATCH 33/43] nolint function length on test --- schema/oneof_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index ba91c4d..30807c2 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -206,6 +206,7 @@ var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestI oneOfTestInlineObjectCProperties, ) +//nolint:funlen func Test_OneOfString_ConstructorBypass(t *testing.T) { input_schema := map[string]any{ "root": "InputParams", From fa78446f1a9112408058e5530a0a5f639e0cb8d9 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Mon, 19 Feb 2024 15:17:08 -0500 Subject: [PATCH 34/43] add inline default --- schema/oneof_test.go | 1 - schema/schema_schema.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index 30807c2..e671d4f 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -218,7 +218,6 @@ func Test_OneOfString_ConstructorBypass(t *testing.T) { "required": true, "type": map[string]any{ "discriminator_field_name": "_type", - "discriminator_inlined": false, "type_id": "one_of_string", "types": map[string]any{ "fullname": map[string]any{ diff --git a/schema/schema_schema.go b/schema/schema_schema.go index 756e9c6..f54dd16 100644 --- a/schema/schema_schema.go +++ b/schema/schema_schema.go @@ -543,8 +543,8 @@ var basicObjects = []*ObjectSchema{ nil, nil, nil, + PointerTo("false"), nil, - []string{"\"inlined\""}, ), "discriminator_field_name": NewPropertySchema( NewStringSchema(nil, nil, nil), @@ -604,8 +604,8 @@ var basicObjects = []*ObjectSchema{ nil, nil, nil, + PointerTo("false"), nil, - []string{"\"inlined\""}, ), "discriminator_field_name": NewPropertySchema( NewStringSchema(nil, nil, nil), From 690ed396d400928f9cd24d2f5121a41c3e5f6261 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Mon, 19 Feb 2024 15:25:11 -0500 Subject: [PATCH 35/43] remove import parens --- schema/oneof_string.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/schema/oneof_string.go b/schema/oneof_string.go index a4c3e9a..52ce02e 100644 --- a/schema/oneof_string.go +++ b/schema/oneof_string.go @@ -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 From ac7fd1cdfcdd188549261929944519c0b9943c98 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 20 Feb 2024 10:21:14 -0500 Subject: [PATCH 36/43] finish function refactor --- schema/oneof.go | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index a1d1c8a..7ff3d46 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -121,21 +121,16 @@ func (o OneOfSchema[KeyType]) UnserializeType(data any) (result any, err error) } } - 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()) } @@ -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)) @@ -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 { @@ -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{ @@ -435,3 +418,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 +} From 9415a48584fe5ba9be83cb9e26c9b07454eb63ff Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 20 Feb 2024 10:44:33 -0500 Subject: [PATCH 37/43] rm unnecessary lines --- schema/oneof_string_test.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index 72b36d8..c6bc6ac 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -145,11 +145,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) @@ -163,7 +163,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!") @@ -287,11 +286,11 @@ var oneOfStringTestInlineObjectASchema = schema.NewScopeSchema( func TestOneOfStringInline_Unserialization(t *testing.T) { data := `{ - "s": { - "choice": "B", - "message": "Hello world!" - } -}` + "s": { + "choice": "B", + "message": "Hello world!" + } + }` var input any assert.NoError(t, json.Unmarshal([]byte(data), &input)) unserializedData, err := oneOfStringTestInlineObjectAType.Unserialize(input) @@ -305,7 +304,6 @@ func TestOneOfStringInline_Unserialization(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 = oneOfStringTestInlineObjectASchema.Unserialize(input) assert.NoError(t, err) assert.Equals(t, unserializedData.(map[string]any)["s"].(oneOfTestInlineObjectB).Message, "Hello world!") From 5464419aeea813c0fcf83293858464d70d132b56 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 20 Feb 2024 11:30:46 -0500 Subject: [PATCH 38/43] improve coverage --- schema/oneof.go | 6 ++-- schema/oneof_string_test.go | 69 +++++++++++-------------------------- schema/oneof_test.go | 32 +++++++++++++---- 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/schema/oneof.go b/schema/oneof.go index 7ff3d46..04e4927 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -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(), ), } @@ -114,7 +114,7 @@ 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, ", "), ), @@ -350,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(), ), } diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index c6bc6ac..d01f8dd 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "go.arcalot.io/assert" + "reflect" "testing" "go.flow.arcalot.io/pluginsdk/schema" @@ -266,54 +267,6 @@ var oneOfStringTestInlineObjectAProperties = map[string]*schema.PropertySchema{ ), } -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. - 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"` @@ -503,6 +456,26 @@ 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{}{} + error_msg := fmt.Sprintf("Invalid type for one-of schema") + err := oneofSchema.Validate(input_mismatched_type) + assert.Error(t, err) + assert.Contains(t, err.Error(), error_msg) + + 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 { diff --git a/schema/oneof_test.go b/schema/oneof_test.go index e671d4f..eaefc9c 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -1,6 +1,7 @@ package schema_test import ( + "fmt" "testing" "go.arcalot.io/assert" @@ -206,8 +207,12 @@ var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestI oneOfTestInlineObjectCProperties, ) -//nolint:funlen -func Test_OneOfString_ConstructorBypass(t *testing.T) { +// Test_OneOf_ConstructorBypass tests the behavior of a OneOf object created +// by the Scope Scope Schema through unserialization of data without using a +// New* constructor function, like NewOneOfStringSchema or NewOneOfIntSchema, +// behaves as one would expect from a OneOf object created from a constructor. +func Test_OneOf_ConstructorBypass(t *testing.T) { //nolint:funlen + discriminator_field := "_type" input_schema := map[string]any{ "root": "InputParams", "objects": map[string]any{ @@ -217,7 +222,7 @@ func Test_OneOfString_ConstructorBypass(t *testing.T) { "name": map[string]any{ "required": true, "type": map[string]any{ - "discriminator_field_name": "_type", + "discriminator_field_name": discriminator_field, "type_id": "one_of_string", "types": map[string]any{ "fullname": map[string]any{ @@ -265,16 +270,31 @@ func Test_OneOfString_ConstructorBypass(t *testing.T) { } var input_data_fullname any = map[string]any{ "name": map[string]any{ - "_type": "fullname", - "first_name": "Arca", - "last_name": "Lot", + discriminator_field: "fullname", + "first_name": "Arca", + "last_name": "Lot", }, } + scopeAny := assert.NoErrorR[any](t)(schema.DescribeScope().Unserialize(input_schema)) scopeSchemaTyped := scopeAny.(*schema.ScopeSchema) scopeSchemaTyped.ApplyScope(scopeSchemaTyped) + assert.NoError(t, scopeSchemaTyped.Validate(input_data_fullname)) unserialized := assert.NoErrorR[any](t)(scopeSchemaTyped.Unserialize(input_data_fullname)) serialized := assert.NoErrorR[any](t)(scopeSchemaTyped.Serialize(unserialized)) unserialized2 := assert.NoErrorR[any](t)(scopeSchemaTyped.Unserialize(serialized)) assert.Equals(t, unserialized2, unserialized) + + var input_invalid_discriminator_value any = map[string]any{ + "name": map[string]any{ + discriminator_field: 1, + "first_name": "Arca", + "last_name": "Lot", + }, + } + error_msg := fmt.Sprintf("Invalid value for %q", discriminator_field) + _, err := scopeSchemaTyped.Unserialize(input_invalid_discriminator_value) + assert.Error(t, err) + assert.Contains(t, err.Error(), error_msg) + } From a82b000a99fa75bce47357e83ba3e21fbb7e2182 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 20 Feb 2024 11:33:12 -0500 Subject: [PATCH 39/43] fix lint --- schema/oneof_string_test.go | 25 +----------- schema/oneof_test.go | 80 ------------------------------------- 2 files changed, 2 insertions(+), 103 deletions(-) diff --git a/schema/oneof_string_test.go b/schema/oneof_string_test.go index d01f8dd..9ecc875 100644 --- a/schema/oneof_string_test.go +++ b/schema/oneof_string_test.go @@ -247,26 +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, - ), -} - type inlinedTestObjectA struct { DType string `json:"d_type"` OtherFieldA string `json:"other_field_a"` @@ -457,13 +437,12 @@ func TestOneOf_NonInlinedNonStructMapped(t *testing.T) { assert.Equals[any](t, reserializedData, serializedData) var input_mismatched_type any = struct{}{} - error_msg := fmt.Sprintf("Invalid type for one-of schema") err := oneofSchema.Validate(input_mismatched_type) assert.Error(t, err) - assert.Contains(t, err.Error(), error_msg) + 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()) + 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) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index eaefc9c..c45f31c 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -24,20 +24,6 @@ type oneOfTestObjectA struct { S any `json:"s"` } -type oneOfTestInlineObjectB struct { - Message string `json:"message"` - Choice string `json:"choice"` -} - -func (o oneOfTestInlineObjectB) String() string { - return o.Message -} - -type oneOfTestInlineObjectC struct { - M string `json:"m"` - Choice string `json:"choice"` -} - func TestOneOfTypeID(t *testing.T) { assert.Equals( t, @@ -141,72 +127,6 @@ var oneOfTestCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestObjectC oneOfTestObjectCProperties, ) -var oneOfTestInlineObjectBProperties = map[string]*schema.PropertySchema{ - "message": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), - "choice": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var oneOfTestInlineObjectCProperties = map[string]*schema.PropertySchema{ - "m": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), - "choice": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, nil), - nil, - true, - nil, - nil, - nil, - nil, - nil, - ), -} - -var oneOfTestInlineBSchema = schema.NewObjectSchema( - "B", - oneOfTestInlineObjectBProperties, -) - -var oneOfTestInlineCSchema = schema.NewObjectSchema( - "C", - oneOfTestInlineObjectCProperties, -) - -var oneOfTestInlineBMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestInlineObjectB]( - "B", - oneOfTestInlineObjectBProperties, -) - -var oneOfTestInlineCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestInlineObjectC]( - "C", - oneOfTestInlineObjectCProperties, -) - // Test_OneOf_ConstructorBypass tests the behavior of a OneOf object created // by the Scope Scope Schema through unserialization of data without using a // New* constructor function, like NewOneOfStringSchema or NewOneOfIntSchema, From df18e50414b202fa602c878a467bf9fdea1511c9 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Tue, 20 Feb 2024 11:35:05 -0500 Subject: [PATCH 40/43] rm comment --- schema/oneof.go | 1 - 1 file changed, 1 deletion(-) diff --git a/schema/oneof.go b/schema/oneof.go index 04e4927..24c201f 100644 --- a/schema/oneof.go +++ b/schema/oneof.go @@ -364,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 { From f2fefed73c57c4469ac21011c5a5a8ccc8580288 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 21 Feb 2024 10:11:52 -0500 Subject: [PATCH 41/43] use a string discriminator for test --- schema/oneof_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index c45f31c..f8118ec 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -207,7 +207,7 @@ func Test_OneOf_ConstructorBypass(t *testing.T) { //nolint:funlen var input_invalid_discriminator_value any = map[string]any{ "name": map[string]any{ - discriminator_field: 1, + discriminator_field: "robotname", "first_name": "Arca", "last_name": "Lot", }, @@ -216,5 +216,4 @@ func Test_OneOf_ConstructorBypass(t *testing.T) { //nolint:funlen _, err := scopeSchemaTyped.Unserialize(input_invalid_discriminator_value) assert.Error(t, err) assert.Contains(t, err.Error(), error_msg) - } From 8fd95f0acc70c1e5b27f375eabb2df5ca92c5efe Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 21 Feb 2024 10:13:19 -0500 Subject: [PATCH 42/43] clarify scope scope schema --- schema/oneof_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index f8118ec..a89eb59 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -128,7 +128,8 @@ var oneOfTestCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestObjectC ) // Test_OneOf_ConstructorBypass tests the behavior of a OneOf object created -// by the Scope Scope Schema through unserialization of data without using a +// by the Scope Scope Schema, a scope that contains the schema of a scope +// and an object, through unserialization of data without using a // New* constructor function, like NewOneOfStringSchema or NewOneOfIntSchema, // behaves as one would expect from a OneOf object created from a constructor. func Test_OneOf_ConstructorBypass(t *testing.T) { //nolint:funlen From 3c27f118ba8c4468989bd842a94489b2f8414372 Mon Sep 17 00:00:00 2001 From: Matthew F Leader Date: Wed, 21 Feb 2024 11:48:39 -0500 Subject: [PATCH 43/43] rm whitespace --- schema/oneof_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/oneof_test.go b/schema/oneof_test.go index a89eb59..4ff8ab7 100644 --- a/schema/oneof_test.go +++ b/schema/oneof_test.go @@ -128,7 +128,7 @@ var oneOfTestCMappedSchema = schema.NewStructMappedObjectSchema[oneOfTestObjectC ) // Test_OneOf_ConstructorBypass tests the behavior of a OneOf object created -// by the Scope Scope Schema, a scope that contains the schema of a scope +// by the Scope ScopeSchema, a scope that contains the schema of a scope // and an object, through unserialization of data without using a // New* constructor function, like NewOneOfStringSchema or NewOneOfIntSchema, // behaves as one would expect from a OneOf object created from a constructor.