Finished the mapping between dynamo attribute values and tamarin values
This commit is contained in:
		
							parent
							
								
									3bf5b6ec93
								
							
						
					
					
						commit
						348251c1cf
					
				
							
								
								
									
										23
									
								
								internal/common/maputils/map.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								internal/common/maputils/map.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| package maputils | ||||
| 
 | ||||
| func Values[K comparable, T any](ts map[K]T) []T { | ||||
| 	values := make([]T, 0, len(ts)) | ||||
| 	for _, v := range ts { | ||||
| 		values = append(values, v) | ||||
| 	} | ||||
| 	return values | ||||
| } | ||||
| 
 | ||||
| func MapValuesWithError[K comparable, T, U any](ts map[K]T, fn func(t T) (U, error)) (map[K]U, error) { | ||||
| 	us := make(map[K]U) | ||||
| 
 | ||||
| 	for k, t := range ts { | ||||
| 		var err error | ||||
| 		us[k], err = fn(t) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return us, nil | ||||
| } | ||||
|  | @ -1,5 +1,14 @@ | |||
| package sliceutils | ||||
| 
 | ||||
| func All[T any](ts []T, predicate func(t T) bool) bool { | ||||
| 	for _, t := range ts { | ||||
| 		if !predicate(t) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func Map[T, U any](ts []T, fn func(t T) U) []U { | ||||
| 	us := make([]U, len(ts)) | ||||
| 	for i, t := range ts { | ||||
|  |  | |||
|  | @ -2,13 +2,11 @@ package scriptmanager | |||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | ||||
| 	"github.com/cloudcmds/tamarin/arg" | ||||
| 	"github.com/cloudcmds/tamarin/object" | ||||
| 	"github.com/lmika/audax/internal/dynamo-browse/models" | ||||
| 	"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| type resultSetProxy struct { | ||||
|  | @ -172,19 +170,32 @@ func (i *itemProxy) value(ctx context.Context, args ...object.Object) object.Obj | |||
| 		return object.NewError(errors.Errorf("arg error: path expression evaluate error: %v", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO
 | ||||
| 	switch v := av.(type) { | ||||
| 	case *types.AttributeValueMemberS: | ||||
| 		return object.NewString(v.Value) | ||||
| 	case *types.AttributeValueMemberN: | ||||
| 		// TODO: better
 | ||||
| 		f, err := strconv.ParseFloat(v.Value, 64) | ||||
| 		if err != nil { | ||||
| 			return object.NewError(errors.Errorf("value error: invalid N value: %v", v.Value)) | ||||
| 		} | ||||
| 		return object.NewFloat(f) | ||||
| 	tVal, err := attributeValueToTamarin(av) | ||||
| 	if err != nil { | ||||
| 		return object.NewError(err) | ||||
| 	} | ||||
| 	return object.NewError(errors.New("TODO")) | ||||
| 	return tVal | ||||
| 
 | ||||
| 	// TODO
 | ||||
| 	//switch v := av.(type) {
 | ||||
| 	//case *types.AttributeValueMemberS:
 | ||||
| 	//	return object.NewString(v.Value)
 | ||||
| 	//case *types.AttributeValueMemberN:
 | ||||
| 	//	// TODO: better
 | ||||
| 	//	f, err := strconv.ParseFloat(v.Value, 64)
 | ||||
| 	//	if err != nil {
 | ||||
| 	//		return object.NewError(errors.Errorf("value error: invalid N value: %v", v.Value))
 | ||||
| 	//	}
 | ||||
| 	//	return object.NewFloat(f)
 | ||||
| 	//case *types.AttributeValueMemberBOOL:
 | ||||
| 	//	if v.Value {
 | ||||
| 	//		return object.True
 | ||||
| 	//	}
 | ||||
| 	//	return object.False
 | ||||
| 	//case *types.AttributeValueMemberNULL:
 | ||||
| 	//	return object.Nil
 | ||||
| 	//}
 | ||||
| 	//return object.NewError(errors.New("TODO"))
 | ||||
| } | ||||
| 
 | ||||
| func (i *itemProxy) setValue(ctx context.Context, args ...object.Object) object.Object { | ||||
|  | @ -202,15 +213,12 @@ func (i *itemProxy) setValue(ctx context.Context, args ...object.Object) object. | |||
| 		return object.Errorf("arg error: invalid path expression: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO
 | ||||
| 	newValue := args[1] | ||||
| 	switch v := newValue.(type) { | ||||
| 	case *object.String: | ||||
| 		if err := path.SetEvalItem(i.item, &types.AttributeValueMemberS{Value: v.Value()}); err != nil { | ||||
| 			return object.NewError(err) | ||||
| 		} | ||||
| 	default: | ||||
| 		return object.Errorf("type error: unsupported value type (got %v)", newValue.Type()) | ||||
| 	newValue, err := tamarinValueToAttributeValue(args[1]) | ||||
| 	if err != nil { | ||||
| 		return object.NewError(err) | ||||
| 	} | ||||
| 	if err := path.SetEvalItem(i.item, newValue); err != nil { | ||||
| 		return object.NewError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	i.resultSetProxy.resultSet.SetDirty(i.itemIndex, true) | ||||
|  |  | |||
|  | @ -54,6 +54,58 @@ func TestResultSetProxy(t *testing.T) { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestResultSetProxy_GetAttr(t *testing.T) { | ||||
| 	t.Run("should return the value of items within a result set", func(t *testing.T) { | ||||
| 		rs := &models.ResultSet{} | ||||
| 		rs.SetItems([]models.Item{ | ||||
| 			{ | ||||
| 				"pk":   &types.AttributeValueMemberS{Value: "abc"}, | ||||
| 				"sk":   &types.AttributeValueMemberN{Value: "123"}, | ||||
| 				"bool": &types.AttributeValueMemberBOOL{Value: true}, | ||||
| 				"null": &types.AttributeValueMemberNULL{Value: true}, | ||||
| 				"list": &types.AttributeValueMemberL{Value: []types.AttributeValue{ | ||||
| 					&types.AttributeValueMemberS{Value: "apple"}, | ||||
| 					&types.AttributeValueMemberS{Value: "banana"}, | ||||
| 					&types.AttributeValueMemberS{Value: "cherry"}, | ||||
| 				}}, | ||||
| 				"map": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ | ||||
| 					"this":    &types.AttributeValueMemberS{Value: "that"}, | ||||
| 					"another": &types.AttributeValueMemberS{Value: "thing"}, | ||||
| 				}}, | ||||
| 				"strSet": &types.AttributeValueMemberSS{Value: []string{"apple", "banana", "cherry"}}, | ||||
| 				"numSet": &types.AttributeValueMemberNS{Value: []string{"123", "45.67", "8.911", "-321"}}, | ||||
| 			}, | ||||
| 		}) | ||||
| 
 | ||||
| 		mockedSessionService := mocks.NewSessionService(t) | ||||
| 		mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil) | ||||
| 
 | ||||
| 		testFS := testScriptFile(t, "test.tm", ` | ||||
| 			res := session.query("some expr").unwrap() | ||||
| 
 | ||||
| 			assert(res[0].attr("pk") == "abc", "str attr") | ||||
| 			assert(res[0].attr("sk") == 123, "num attr") | ||||
| 			assert(res[0].attr("bool") == true, "bool attr") | ||||
| 			assert(res[0].attr("null") == nil, "null attr") | ||||
| 			assert(res[0].attr("list") == ["apple","banana","cherry"], "list attr") | ||||
| 			assert(res[0].attr("map") == {"this":"that", "another":"thing"}, "map attr") | ||||
| 			assert(res[0].attr("strSet") == {"apple","banana","cherry"}, "string set") | ||||
| 			assert(res[0].attr("numSet") == {123, 45.67, 8.911, -321}, "number set") | ||||
| 		`) | ||||
| 
 | ||||
| 		srv := scriptmanager.New(scriptmanager.WithFS(testFS)) | ||||
| 		srv.SetIFaces(scriptmanager.Ifaces{ | ||||
| 			Session: mockedSessionService, | ||||
| 		}) | ||||
| 
 | ||||
| 		ctx := context.Background() | ||||
| 		err := <-srv.RunAdHocScript(ctx, "test.tm") | ||||
| 		assert.NoError(t, err) | ||||
| 
 | ||||
| 		mockedSessionService.AssertExpectations(t) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestResultSetProxy_SetAttr(t *testing.T) { | ||||
| 	t.Run("should set the value of the item within a result set", func(t *testing.T) { | ||||
| 		rs := &models.ResultSet{} | ||||
|  | @ -66,6 +118,39 @@ func TestResultSetProxy_SetAttr(t *testing.T) { | |||
| 		mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil) | ||||
| 		mockedSessionService.EXPECT().SetResultSet(mock.Anything, mock.MatchedBy(func(rs *models.ResultSet) bool { | ||||
| 			assert.Equal(t, "bla-di-bla", rs.Items()[0]["pk"].(*types.AttributeValueMemberS).Value) | ||||
| 			assert.Equal(t, "123", rs.Items()[0]["num"].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, "123.45", rs.Items()[0]["numFloat"].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, true, rs.Items()[0]["bool"].(*types.AttributeValueMemberBOOL).Value) | ||||
| 			assert.Equal(t, true, rs.Items()[0]["nil"].(*types.AttributeValueMemberNULL).Value) | ||||
| 
 | ||||
| 			list := rs.Items()[0]["lists"].(*types.AttributeValueMemberL).Value | ||||
| 			assert.Equal(t, "abc", list[0].(*types.AttributeValueMemberS).Value) | ||||
| 			assert.Equal(t, "123", list[1].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, true, list[2].(*types.AttributeValueMemberBOOL).Value) | ||||
| 
 | ||||
| 			nestedLists := rs.Items()[0]["nestedLists"].(*types.AttributeValueMemberL).Value | ||||
| 			assert.Equal(t, "1", nestedLists[0].(*types.AttributeValueMemberL).Value[0].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, "2", nestedLists[0].(*types.AttributeValueMemberL).Value[1].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, "3", nestedLists[1].(*types.AttributeValueMemberL).Value[0].(*types.AttributeValueMemberN).Value) | ||||
| 			assert.Equal(t, "4", nestedLists[1].(*types.AttributeValueMemberL).Value[1].(*types.AttributeValueMemberN).Value) | ||||
| 
 | ||||
| 			mapValue := rs.Items()[0]["map"].(*types.AttributeValueMemberM).Value | ||||
| 			assert.Equal(t, "world", mapValue["hello"].(*types.AttributeValueMemberS).Value) | ||||
| 			assert.Equal(t, "213", mapValue["nums"].(*types.AttributeValueMemberN).Value) | ||||
| 
 | ||||
| 			numSet := rs.Items()[0]["numSet"].(*types.AttributeValueMemberNS).Value | ||||
| 			assert.Len(t, numSet, 4) | ||||
| 			assert.Contains(t, numSet, "1") | ||||
| 			assert.Contains(t, numSet, "2") | ||||
| 			assert.Contains(t, numSet, "3") | ||||
| 			assert.Contains(t, numSet, "4.5") | ||||
| 
 | ||||
| 			strSet := rs.Items()[0]["strSet"].(*types.AttributeValueMemberSS).Value | ||||
| 			assert.Len(t, strSet, 3) | ||||
| 			assert.Contains(t, strSet, "a") | ||||
| 			assert.Contains(t, strSet, "b") | ||||
| 			assert.Contains(t, strSet, "c") | ||||
| 
 | ||||
| 			assert.True(t, rs.IsDirty(0)) | ||||
| 			return true | ||||
| 		})) | ||||
|  | @ -74,7 +159,18 @@ func TestResultSetProxy_SetAttr(t *testing.T) { | |||
| 
 | ||||
| 		testFS := testScriptFile(t, "test.tm", ` | ||||
| 			res := session.query("some expr").unwrap() | ||||
| 
 | ||||
| 			res[0].set_attr("pk", "bla-di-bla") | ||||
| 			res[0].set_attr("num", 123) | ||||
| 			res[0].set_attr("numFloat", 123.45) | ||||
| 			res[0].set_attr("bool", true) | ||||
| 			res[0].set_attr("nil", nil) | ||||
| 			res[0].set_attr("lists", ['abc', 123, true]) | ||||
| 			res[0].set_attr("nestedLists", [[1,2], [3,4]]) | ||||
| 			res[0].set_attr("map", {"hello": "world", "nums": 213}) | ||||
| 			res[0].set_attr("numSet", {1,2,3,4.5}) | ||||
| 			res[0].set_attr("strSet", {"a","b","c"}) | ||||
| 
 | ||||
| 			session.set_result_set(res) | ||||
| 		`) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										135
									
								
								internal/dynamo-browse/services/scriptmanager/typemapping.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								internal/dynamo-browse/services/scriptmanager/typemapping.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| package scriptmanager | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | ||||
| 	"github.com/cloudcmds/tamarin/object" | ||||
| 	"github.com/lmika/audax/internal/common/maputils" | ||||
| 	"github.com/lmika/audax/internal/common/sliceutils" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| func tamarinValueToAttributeValue(val object.Object) (types.AttributeValue, error) { | ||||
| 	switch v := val.(type) { | ||||
| 	case *object.String: | ||||
| 		return &types.AttributeValueMemberS{Value: v.Value()}, nil | ||||
| 	case *object.Int: | ||||
| 		return &types.AttributeValueMemberN{Value: strconv.FormatInt(v.Value(), 10)}, nil | ||||
| 	case *object.Float: | ||||
| 		return &types.AttributeValueMemberN{Value: strconv.FormatFloat(v.Value(), 'f', -1, 64)}, nil | ||||
| 	case *object.Bool: | ||||
| 		return &types.AttributeValueMemberBOOL{Value: v.Value()}, nil | ||||
| 	case *object.NilType: | ||||
| 		return &types.AttributeValueMemberNULL{Value: true}, nil | ||||
| 	case *object.List: | ||||
| 		attrValue, err := sliceutils.MapWithError(v.Value(), tamarinValueToAttributeValue) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return &types.AttributeValueMemberL{Value: attrValue}, nil | ||||
| 	case *object.Map: | ||||
| 		attrValue, err := maputils.MapValuesWithError(v.Value(), tamarinValueToAttributeValue) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return &types.AttributeValueMemberM{Value: attrValue}, nil | ||||
| 	case *object.Set: | ||||
| 		values := maputils.Values(v.Value()) | ||||
| 		canBeNumSet := sliceutils.All(values, func(t object.Object) bool { | ||||
| 			_, isInt := t.(*object.Int) | ||||
| 			_, isFloat := t.(*object.Float) | ||||
| 			return isInt || isFloat | ||||
| 		}) | ||||
| 
 | ||||
| 		if canBeNumSet { | ||||
| 			return &types.AttributeValueMemberNS{ | ||||
| 				Value: sliceutils.Map(values, func(t object.Object) string { | ||||
| 					switch v := t.(type) { | ||||
| 					case *object.Int: | ||||
| 						return strconv.FormatInt(v.Value(), 10) | ||||
| 					case *object.Float: | ||||
| 						return strconv.FormatFloat(v.Value(), 'f', -1, 64) | ||||
| 					} | ||||
| 					panic(fmt.Sprintf("unhandled object type: %v", t.Type())) | ||||
| 				}), | ||||
| 			}, nil | ||||
| 		} | ||||
| 		return &types.AttributeValueMemberSS{ | ||||
| 			Value: sliceutils.Map(values, func(t object.Object) string { | ||||
| 				v, _ := object.AsString(t) | ||||
| 				return v | ||||
| 			}), | ||||
| 		}, nil | ||||
| 	} | ||||
| 	return nil, errors.Errorf("type error: unsupported value type (got %v)", val.Type()) | ||||
| } | ||||
| 
 | ||||
| func attributeValueToTamarin(val types.AttributeValue) (object.Object, error) { | ||||
| 	if val == nil { | ||||
| 		return object.Nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	switch v := val.(type) { | ||||
| 	case *types.AttributeValueMemberS: | ||||
| 		return object.NewString(v.Value), nil | ||||
| 	case *types.AttributeValueMemberN: | ||||
| 		f, err := convertNumAttributeToTamarinValue(v.Value) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.Errorf("value error: invalid N value: %v", v.Value) | ||||
| 		} | ||||
| 		return f, nil | ||||
| 	case *types.AttributeValueMemberBOOL: | ||||
| 		if v.Value { | ||||
| 			return object.True, nil | ||||
| 		} | ||||
| 		return object.False, nil | ||||
| 	case *types.AttributeValueMemberNULL: | ||||
| 		return object.Nil, nil | ||||
| 	case *types.AttributeValueMemberL: | ||||
| 		list, err := sliceutils.MapWithError(v.Value, attributeValueToTamarin) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return object.NewList(list), nil | ||||
| 	case *types.AttributeValueMemberM: | ||||
| 		objMap, err := maputils.MapValuesWithError(v.Value, attributeValueToTamarin) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return object.NewMap(objMap), nil | ||||
| 	case *types.AttributeValueMemberSS: | ||||
| 		return object.NewSet(sliceutils.Map(v.Value, func(s string) object.Object { | ||||
| 			return object.NewString(s) | ||||
| 		})), nil | ||||
| 	case *types.AttributeValueMemberNS: | ||||
| 		nums, err := sliceutils.MapWithError(v.Value, func(s string) (object.Object, error) { | ||||
| 			return convertNumAttributeToTamarinValue(s) | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return object.NewSet(nums), nil | ||||
| 	} | ||||
| 	return nil, errors.Errorf("value error: cannot convert type %T to tamarin object", val) | ||||
| } | ||||
| 
 | ||||
| var intNumberPattern = regexp.MustCompile(`^[-]?[0-9]+$`) | ||||
| 
 | ||||
| // XXX - this is pretty crappy in that it does not support large values
 | ||||
| func convertNumAttributeToTamarinValue(n string) (object.Object, error) { | ||||
| 	if intNumberPattern.MatchString(n) { | ||||
| 		parsedInt, err := strconv.ParseInt(n, 10, 64) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return object.NewInt(parsedInt), nil | ||||
| 	} | ||||
| 
 | ||||
| 	f, err := strconv.ParseFloat(n, 64) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return object.NewFloat(f), nil | ||||
| } | ||||
		Loading…
	
		Reference in a new issue