From 75f25cf3a7ae4fc2afff324a57dc4a197e22fb73 Mon Sep 17 00:00:00 2001 From: Ronald K <43806892+rkodev@users.noreply.github.com> Date: Wed, 1 Mar 2023 16:02:15 +0300 Subject: [PATCH] serialize collections (#13) * Adds support for serialization of collections * Update changelog * Adds test coverage * Adds serialization tests * Revert adding collection of object values --- CHANGELOG.md | 6 ++ form_parse_node.go | 58 +++++++++++++++++++- form_parse_node_test.go | 91 ++++++++++++++++++++++++++++++- form_serialization_writer.go | 38 ++++++++----- form_serialization_writer_test.go | 45 +++++++++++++++ 5 files changed, 221 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56ab7f..b393273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +## [0.4.0] - 2023-03-01 + +### Added + +- Adds support for serialization of collections by serialization writer and parsenode. + ## [0.3.0] - 2023-01-26 ### Changed diff --git a/form_parse_node.go b/form_parse_node.go index 3be7d8a..b17e922 100644 --- a/form_parse_node.go +++ b/form_parse_node.go @@ -149,7 +149,63 @@ func (n *FormParseNode) GetCollectionOfObjectValues(ctor absser.ParsableFactory) // GetCollectionOfPrimitiveValues returns the collection of primitive values from the node. func (n *FormParseNode) GetCollectionOfPrimitiveValues(targetType string) ([]interface{}, error) { - return nil, errors.New("collections are not supported in form serialization") + if n == nil || n.value == "" { + return nil, nil + } + if targetType == "" { + return nil, errors.New("targetType is empty") + } + valueList := strings.Split(n.value, ",") + + result := make([]interface{}, len(valueList)) + for i, element := range valueList { + parseNode, err := NewFormParseNode([]byte(element)) + if err != nil { + return nil, err + } + + val, err := parseNode.getPrimitiveValue(targetType) + if err != nil { + return nil, err + } + result[i] = val + } + return result, nil +} + +func (n *FormParseNode) getPrimitiveValue(targetType string) (interface{}, error) { + switch targetType { + case "string": + return n.GetStringValue() + case "bool": + return n.GetBoolValue() + case "uint8": + return n.GetInt8Value() + case "byte": + return n.GetByteValue() + case "float32": + return n.GetFloat32Value() + case "float64": + return n.GetFloat64Value() + case "int32": + return n.GetInt32Value() + case "int64": + return n.GetInt64Value() + case "time": + return n.GetTimeValue() + case "timeonly": + return n.GetTimeOnlyValue() + case "dateonly": + return n.GetDateOnlyValue() + case "isoduration": + return n.GetISODurationValue() + case "uuid": + return n.GetUUIDValue() + case "base64": + return n.GetByteArrayValue() + default: + return nil, errors.New("targetType is not supported") + } } // GetCollectionOfEnumValues returns the collection of Enum values from the node. diff --git a/form_parse_node_test.go b/form_parse_node_test.go index 867f86f..733f652 100644 --- a/form_parse_node_test.go +++ b/form_parse_node_test.go @@ -1,14 +1,13 @@ package formserialization import ( - testing "testing" - "github.com/stretchr/testify/require" + "testing" "github.com/microsoft/kiota-serialization-form-go/internal" absser "github.com/microsoft/kiota-abstractions-go/serialization" - assert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestGetRawValue(t *testing.T) { @@ -26,6 +25,92 @@ func TestGetRawValue(t *testing.T) { value, err := someProp.GetRawValue() assert.Equal(t, "200", *value.(*string)) } + +func TestGetCollectionOfPrimitiveValues(t *testing.T) { + source := `id=2&status=200&item=1&item=2&item=3` + sourceArray := []byte(source) + parseNode, err := NewFormParseNode(sourceArray) + require.NoError(t, err) + someProp, err := parseNode.GetChildNode("item") + require.NoError(t, err) + + value, err := someProp.GetCollectionOfPrimitiveValues("int32") + require.NoError(t, err) + + expected := []interface{}{ref(int32(1)), ref(int32(2)), ref(int32(3))} + assert.Equal(t, expected, value) +} + +func TestGetCollectionOfPrimitiveValuesTypes(t *testing.T) { + assert.Equal(t, + []interface{}{ref("milk"), ref("soda")}, + getCollectionValues("id=2&item=milk&item=soda", "item", "string"), + ) + assert.Equal(t, + []interface{}{ref(true), ref(false)}, + getCollectionValues("id=2&item=true&item=false", "item", "bool"), + ) + assert.Equal(t, + []interface{}{ref(int8(1)), ref(int8(2)), ref(int8(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "uint8"), + ) + assert.Equal(t, + []interface{}{ref(byte(1)), ref(byte(2)), ref(byte(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "byte"), + ) + assert.Equal(t, + []interface{}{ref(float32(1)), ref(float32(2)), ref(float32(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "float32"), + ) + assert.Equal(t, + []interface{}{ref(float64(1)), ref(float64(2)), ref(float64(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "float64"), + ) + assert.Equal(t, + []interface{}{ref(float64(1)), ref(float64(2)), ref(float64(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "float64"), + ) + assert.Equal(t, + []interface{}{ref(int32(1)), ref(int32(2)), ref(int32(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "int32"), + ) + assert.Equal(t, + []interface{}{ref(int64(1)), ref(int64(2)), ref(int64(3))}, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "int64"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "time"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "timeonly"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "dateonly"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "isoduration"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "uuid"), + ) + assert.Nil(t, + getCollectionValues("id=2&status=200&item=1&item=2&item=3", "item", "base64"), + ) +} + +func getCollectionValues(source string, indexName string, targetType string) []interface{} { + sourceArray := []byte(source) + parseNode, _ := NewFormParseNode(sourceArray) + someProp, _ := parseNode.GetChildNode(indexName) + + value, _ := someProp.GetCollectionOfPrimitiveValues(targetType) + return value +} + +func ref[T interface{}](t T) *T { + return &t +} + func TestFormParseNodeHonoursInterface(t *testing.T) { instance := &FormParseNode{} assert.Implements(t, (*absser.ParseNode)(nil), instance) diff --git a/form_serialization_writer.go b/form_serialization_writer.go index 029a2aa..b4bd012 100644 --- a/form_serialization_writer.go +++ b/form_serialization_writer.go @@ -274,69 +274,81 @@ func (w *FormSerializationWriter) WriteCollectionOfObjectValues(key string, coll return errors.New("collections serialization is not supported with FormSerializationWriter") } +func writeCollectionOfPrimitiveValues[T interface{}](key string, writer func(string, *T) error, collection []T) error { + if collection != nil && len(collection) > 0 { + for _, item := range collection { + err := writer(key, &item) + if err != nil { + return err + } + } + } + return nil +} + // WriteCollectionOfStringValues writes a collection of String values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfStringValues(key string, collection []string) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteStringValue, collection) } // WriteCollectionOfInt32Values writes a collection of Int32 values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfInt32Values(key string, collection []int32) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteInt32Value, collection) } // WriteCollectionOfInt64Values writes a collection of Int64 values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfInt64Values(key string, collection []int64) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteInt64Value, collection) } // WriteCollectionOfFloat32Values writes a collection of Float32 values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfFloat32Values(key string, collection []float32) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteFloat32Value, collection) } // WriteCollectionOfFloat64Values writes a collection of Float64 values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfFloat64Values(key string, collection []float64) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteFloat64Value, collection) } // WriteCollectionOfTimeValues writes a collection of Time values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfTimeValues(key string, collection []time.Time) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteTimeValue, collection) } // WriteCollectionOfISODurationValues writes a collection of ISODuration values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfISODurationValues(key string, collection []absser.ISODuration) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteISODurationValue, collection) } // WriteCollectionOfTimeOnlyValues writes a collection of TimeOnly values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfTimeOnlyValues(key string, collection []absser.TimeOnly) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteTimeOnlyValue, collection) } // WriteCollectionOfDateOnlyValues writes a collection of DateOnly values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfDateOnlyValues(key string, collection []absser.DateOnly) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteDateOnlyValue, collection) } // WriteCollectionOfUUIDValues writes a collection of UUID values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfUUIDValues(key string, collection []uuid.UUID) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteUUIDValue, collection) } // WriteCollectionOfBoolValues writes a collection of Bool values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfBoolValues(key string, collection []bool) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteBoolValue, collection) } // WriteCollectionOfByteValues writes a collection of Byte values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfByteValues(key string, collection []byte) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteByteValue, collection) } // WriteCollectionOfInt8Values writes a collection of int8 values to underlying the byte array. func (w *FormSerializationWriter) WriteCollectionOfInt8Values(key string, collection []int8) error { - return errors.New("collections serialization is not supported with FormSerializationWriter") + return writeCollectionOfPrimitiveValues(key, w.WriteInt8Value, collection) } // GetSerializedContent returns the resulting byte array from the serialization writer. diff --git a/form_serialization_writer_test.go b/form_serialization_writer_test.go index 051d810..583cc6a 100644 --- a/form_serialization_writer_test.go +++ b/form_serialization_writer_test.go @@ -2,6 +2,7 @@ package formserialization import ( "fmt" + "github.com/google/uuid" "github.com/microsoft/kiota-serialization-form-go/internal" "testing" "time" @@ -163,6 +164,50 @@ func TestWriteMultipleTypes(t *testing.T) { assert.Equal(t, len("key=value&add1=string&add2=pointer&key2=value2"), len(string(result[:]))) } +func TestWriteMultipleCollections(t *testing.T) { + serializer := NewFormSerializationWriter() + value := "value" + serializer.WriteStringValue("key", &value) + + adlData := map[string]interface{}{ + "string": []string{"str1", "str2"}, + "int32": []int32{34, 56}, + "bool": []bool{true, false}, + "uint8": []uint8{1, 2}, + "float32": []float32{32.0, 34.1}, + "float64": []float64{100.1, 400.1}, + "int64": []int64{567, 765}, + "time": []time.Time{time.Now()}, + "timeonly": []*absser.ISODuration{absser.NewDuration(0, 0, 1, 0, 0, 0, 0)}, + "dateonly": []*absser.DateOnly{absser.NewDateOnly(time.Now())}, + "isoduration": []*absser.ISODuration{absser.NewDuration(0, 0, 1, 0, 0, 0, 0)}, + "uuid": []uuid.UUID{uuid.New()}, + "base64": []int64{567, 765}, + "int8": []int8{2, 3}, + } + serializer.WriteAdditionalData(adlData) + + result, err := serializer.GetSerializedContent() + + assert.Nil(t, err) + resultVal := string(result[:]) + assert.Contains(t, resultVal, "key=value&") + assert.Contains(t, resultVal, "string=str1&") + assert.Contains(t, resultVal, "string=str2") + assert.Contains(t, resultVal, "int32=34") + assert.Contains(t, resultVal, "int32=56") + assert.Contains(t, resultVal, "bool=true") + assert.Contains(t, resultVal, "bool=false") + assert.Contains(t, resultVal, "uint8=1") + assert.Contains(t, resultVal, "uint8=2") + assert.Contains(t, resultVal, "float32=32") + assert.Contains(t, resultVal, "float32=34") + assert.Contains(t, resultVal, "float64=100.1") + assert.Contains(t, resultVal, "float64=400.1") + assert.Contains(t, resultVal, "int64=567") + assert.Contains(t, resultVal, "int64=765") +} + func TestEscapesNewLinesInStrings(t *testing.T) { serializer := NewFormSerializationWriter() value := "value\nwith\nnew\nlines"