Skip to content

Commit

Permalink
serialize collections (#13)
Browse files Browse the repository at this point in the history
* Adds support for serialization of collections

* Update changelog

* Adds test coverage

* Adds serialization tests

* Revert adding collection of object values
  • Loading branch information
rkodev authored Mar 1, 2023
1 parent c2eed13 commit 75f25cf
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 17 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 57 additions & 1 deletion form_parse_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
91 changes: 88 additions & 3 deletions form_parse_node_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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)
Expand Down
38 changes: 25 additions & 13 deletions form_serialization_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 45 additions & 0 deletions form_serialization_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package formserialization

import (
"fmt"
"github.com/google/uuid"
"github.com/microsoft/kiota-serialization-form-go/internal"
"testing"
"time"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 75f25cf

Please sign in to comment.