From 94a006bb2f1dea3e485c69256b86e278112d7c5e Mon Sep 17 00:00:00 2001 From: Jared O'Connell Date: Thu, 24 Oct 2024 17:31:32 -0400 Subject: [PATCH 1/5] Allow objects with single field to inline the field --- schema/object.go | 28 +++++++++++++++++++++++++--- schema/object_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/schema/object.go b/schema/object.go index fc677e7..1a3f138 100644 --- a/schema/object.go +++ b/schema/object.go @@ -107,12 +107,18 @@ func (o *ObjectSchema) Properties() map[string]*PropertySchema { func (o *ObjectSchema) Unserialize(data any) (result any, err error) { v := reflect.ValueOf(data) + var rawData map[string]any if v.Kind() != reflect.Map { - return nil, &ConstraintError{ - Message: fmt.Sprintf("Must be a map, %T given", data), + if len(o.Properties()) == 1 { + rawData, err = o.unserializeInlinedDataToMap(data) + } else { + return nil, &ConstraintError{ + Message: fmt.Sprintf("Must be a map to convert to object, %T given", data), + } } + } else { + rawData, err = o.convertData(v) } - rawData, err := o.convertData(v) if err != nil { return nil, err } @@ -126,6 +132,22 @@ func (o *ObjectSchema) Unserialize(data any) (result any, err error) { return rawData, nil } +func (o *ObjectSchema) unserializeInlinedDataToMap(data any) (map[string]any, error) { + for fieldName, property := range o.Properties() { + unserializedProperty, err := property.Unserialize(data) + if err != nil { + return nil, + fmt.Errorf("error while unserializing single inlined property %s for object %s (%q);"+ + "fix the property or specify the object as a map", + fieldName, o.ID(), err) + } + return map[string]any{ + fieldName: unserializedProperty, + }, nil + } + panic("convertInlinedData called on object with zero properties") +} + func (o *ObjectSchema) unserializeToStruct(rawData map[string]any) (any, error) { reflectType := reflect.TypeOf(o.defaultValue) var reflectedValue reflect.Value diff --git a/schema/object_test.go b/schema/object_test.go index c988930..5fc014f 100644 --- a/schema/object_test.go +++ b/schema/object_test.go @@ -607,3 +607,46 @@ func TestObjectSchema_ValidateCompatibility(t *testing.T) { assert.Error(t, s1.ValidateCompatibility(schema.NewStringEnumSchema(map[string]*schema.DisplayValue{}))) assert.Error(t, s1.ValidateCompatibility(schema.NewIntEnumSchema(map[int64]*schema.DisplayValue{}, nil))) } + +type testStructWithSingleField struct { + Field1 string `json:"field1"` +} + +var testStructWithSingleFieldSchema = schema.NewStructMappedObjectSchema[testStructWithSingleField]("testStructWithSingleField", map[string]*schema.PropertySchema{ + "field1": schema.NewPropertySchema(schema.NewStringSchema(nil, nil, nil), + schema.NewDisplayValue(schema.PointerTo("field1"), nil, nil), + true, + nil, + nil, + nil, + nil, + nil, + ), +}) + +func TestUnserializeSingleFieldObject(t *testing.T) { + withoutInlineSerialized := map[string]any{ + "field1": "hello", + } + expectedOutput := testStructWithSingleField{ + "hello", + } + + unserializedData, err := testStructWithSingleFieldSchema.Unserialize(withoutInlineSerialized) + assert.NoError(t, err) + assert.InstanceOf[testStructWithSingleField](t, unserializedData) + assert.Equals(t, unserializedData.(testStructWithSingleField), expectedOutput) +} + +func TestUnserializeSingleFieldObjectInlined(t *testing.T) { + withoutInlineSerialized := "hello" + + expectedOutput := testStructWithSingleField{ + "hello", + } + + unserializedData, err := testStructWithSingleFieldSchema.Unserialize(withoutInlineSerialized) + assert.NoError(t, err) + assert.InstanceOf[testStructWithSingleField](t, unserializedData) + assert.Equals(t, unserializedData.(testStructWithSingleField), expectedOutput) +} From ce8bac23071edcd4a7686b18972c139a94f60aa1 Mon Sep 17 00:00:00 2001 From: Jared O'Connell Date: Thu, 14 Nov 2024 13:35:14 -0500 Subject: [PATCH 2/5] Test structs with private fields --- schema/object_test.go | 38 ++++++++++++++++++++++++++++++++++++ schema/testdata/test_data.go | 13 ++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 schema/testdata/test_data.go diff --git a/schema/object_test.go b/schema/object_test.go index 5fc014f..64abaeb 100644 --- a/schema/object_test.go +++ b/schema/object_test.go @@ -2,6 +2,7 @@ package schema_test import ( "go.arcalot.io/assert" + "go.flow.arcalot.io/pluginsdk/schema/testdata" "strconv" "testing" @@ -650,3 +651,40 @@ func TestUnserializeSingleFieldObjectInlined(t *testing.T) { assert.InstanceOf[testStructWithSingleField](t, unserializedData) assert.Equals(t, unserializedData.(testStructWithSingleField), expectedOutput) } + +func TestStructWithPrivateFields(t *testing.T) { + schemaForPrivateFieldStruct := schema.NewStructMappedObjectSchema[testdata.TestStructWithPrivateField]( + "structWithPrivateField", + map[string]*schema.PropertySchema{ + "field1": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, nil), + nil, + false, + nil, + nil, + nil, + schema.PointerTo("\"Hello world!\""), + nil, + ), + }, + ) + + inputWithOnlyPublicField := testdata.TestStructWithPrivateField{ + Field1: "test", + } + serializedData, err := schemaForPrivateFieldStruct.Serialize(inputWithOnlyPublicField) + assert.NoError(t, err) + unserializedData, err := schemaForPrivateFieldStruct.Unserialize(serializedData) + assert.NoError(t, err) + assert.InstanceOf[testdata.TestStructWithPrivateField](t, unserializedData) + assert.Equals(t, inputWithOnlyPublicField, unserializedData.(testdata.TestStructWithPrivateField)) + + inputWithPrivateField := testdata.GetTestStructWithPrivateFieldPresent() + serializedData, err = schemaForPrivateFieldStruct.Serialize(inputWithPrivateField) + assert.NoError(t, err) + unserializedData, err = schemaForPrivateFieldStruct.Unserialize(serializedData) + assert.NoError(t, err) + assert.InstanceOf[testdata.TestStructWithPrivateField](t, unserializedData) + // The unserialization will only be able to fill in the public fields. + assert.Equals(t, inputWithOnlyPublicField, unserializedData.(testdata.TestStructWithPrivateField)) +} diff --git a/schema/testdata/test_data.go b/schema/testdata/test_data.go new file mode 100644 index 0000000..c9481e0 --- /dev/null +++ b/schema/testdata/test_data.go @@ -0,0 +1,13 @@ +package testdata + +func GetTestStructWithPrivateFieldPresent() TestStructWithPrivateField { + return TestStructWithPrivateField{ + Field1: "test", + field2: "private", + } +} + +type TestStructWithPrivateField struct { + Field1 string `json:"field1"` + field2 string +} From 62587e3eee0bdc7fb1d6fedb872c192226284d30 Mon Sep 17 00:00:00 2001 From: Jared O'Connell Date: Fri, 15 Nov 2024 15:07:40 -0500 Subject: [PATCH 3/5] Added internal check --- schema/object.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema/object.go b/schema/object.go index 1a3f138..5614f4c 100644 --- a/schema/object.go +++ b/schema/object.go @@ -133,6 +133,10 @@ func (o *ObjectSchema) Unserialize(data any) (result any, err error) { } func (o *ObjectSchema) unserializeInlinedDataToMap(data any) (map[string]any, error) { + if len(o.Properties()) != 1 { + panic(fmt.Errorf("unserializeInlinedDataToMap called on ObjectSchema with %d"+ + " properties; only 1 allowed", len(o.Properties()))) + } for fieldName, property := range o.Properties() { unserializedProperty, err := property.Unserialize(data) if err != nil { From bc804e40700123b3eca8fe8958cc6e1d8c3160cb Mon Sep 17 00:00:00 2001 From: Jared O'Connell Date: Fri, 15 Nov 2024 15:29:30 -0500 Subject: [PATCH 4/5] Remove stale check --- schema/object.go | 1 - 1 file changed, 1 deletion(-) diff --git a/schema/object.go b/schema/object.go index 5614f4c..ca69552 100644 --- a/schema/object.go +++ b/schema/object.go @@ -149,7 +149,6 @@ func (o *ObjectSchema) unserializeInlinedDataToMap(data any) (map[string]any, er fieldName: unserializedProperty, }, nil } - panic("convertInlinedData called on object with zero properties") } func (o *ObjectSchema) unserializeToStruct(rawData map[string]any) (any, error) { From e412b64af60c9bbf27aa27bc5c19b670f90ca744 Mon Sep 17 00:00:00 2001 From: Jared O'Connell Date: Fri, 15 Nov 2024 15:31:59 -0500 Subject: [PATCH 5/5] Address compile error --- schema/object.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema/object.go b/schema/object.go index ca69552..1e3c13d 100644 --- a/schema/object.go +++ b/schema/object.go @@ -133,7 +133,7 @@ func (o *ObjectSchema) Unserialize(data any) (result any, err error) { } func (o *ObjectSchema) unserializeInlinedDataToMap(data any) (map[string]any, error) { - if len(o.Properties()) != 1 { + if len(o.Properties()) > 1 { panic(fmt.Errorf("unserializeInlinedDataToMap called on ObjectSchema with %d"+ " properties; only 1 allowed", len(o.Properties()))) } @@ -149,6 +149,7 @@ func (o *ObjectSchema) unserializeInlinedDataToMap(data any) (map[string]any, er fieldName: unserializedProperty, }, nil } + panic("convertInlinedData called on object with zero properties") } func (o *ObjectSchema) unserializeToStruct(rawData map[string]any) (any, error) {