diff --git a/pkg/schema/field/number.go b/pkg/schema/field/number.go index 502a2fef7c406b4b4dc96aad34efce2e518a1e28..40d9c8167f54d23faaa40666ec1934b451d7a516 100644 --- a/pkg/schema/field/number.go +++ b/pkg/schema/field/number.go @@ -10,6 +10,10 @@ import ( const ( NumberFormatInt = "int" NumberFormatFloat = "float" + + // The largest integer that can be represented in IEEE 754 double (64-bit) is a value from -2^53 to 2^53. + maxInt = 1<<53 - 1 + minInt = -1<<53 + 1 ) var numberType = &NumberType{} @@ -84,10 +88,19 @@ func (NumberType) decode(_ context.Context, field *Field, v interface{}) (interf case NumberFormatInt: switch i := n.(type) { case int64: + if i > maxInt || i < minInt { + return nil, errors.New("integer out of range") + } return i, nil case uint64: + if i > maxInt { + return nil, errors.New("integer out of range") + } return i, nil case float64: + if i > maxInt || i < minInt { + return nil, errors.New("integer out of range") + } return int64(math.Round(i)), nil } case NumberFormatFloat: diff --git a/pkg/schema/field/number_test.go b/pkg/schema/field/number_test.go index e731793d12135127dbfa67d949572f52708075a6..367ffe4249f36f2dff17aa1e61ef6a753ccabee8 100644 --- a/pkg/schema/field/number_test.go +++ b/pkg/schema/field/number_test.go @@ -1,6 +1,8 @@ package field import ( + "context" + "math" "reflect" "testing" ) @@ -13,18 +15,25 @@ func TestNumberField_Decode(t *testing.T) { want interface{} wantErr bool }{ - {"Correct", Number("int"), int64(2), int64(2), false}, // #0 - {"Correct", Number("int"), 2.2, int64(2), false}, // #1 - {"Correct", Number("int"), 2, int64(2), false}, // #2 - {"Correct", Number("int"), float32(2.2), int64(2), false}, // #3 - {"Correct", Number("int"), float64(2.6), int64(3), false}, // #4 - {"Correct", Number("int"), 2.6, int64(3), false}, // #5 + {"Correct", Number("int"), int64(2), int64(2), false}, // #0 + {"Correct", Number("int"), 2.2, int64(2), false}, // #1 + {"Correct", Number("int"), 2, int64(2), false}, // #2 + {"Correct", Number("int"), float32(2.2), int64(2), false}, // #3 + {"Correct", Number("int"), float64(2.6), int64(3), false}, // #4 + {"Correct", Number("int"), 2.6, int64(3), false}, // #5 + {"MaxInt64", Number(NumberFormatInt), int64(math.MaxInt64), nil, true}, // #6 + {"MinInt64", Number(NumberFormatInt), int64(math.MinInt64), nil, true}, // #7 + {"maxInt in float", Number(NumberFormatInt), float64(maxInt), int64(maxInt), false}, // #8 + {"minInt in float", Number(NumberFormatInt), float64(minInt), int64(minInt), false}, // #9 + {"Convert error", Number(NumberFormatInt), math.MaxFloat64, nil, true}, // #10 + {"Convert error", Number(NumberFormatInt), -math.MaxFloat64, nil, true}, // #11 + {"Convert error", Number(NumberFormatInt), float64(math.MaxInt64), nil, true}, // #13 + {"Convert error", Number(NumberFormatInt), float64(math.MinInt64), nil, true}, // #14 - {"Correct", Number("float"), int8(2), 2.0, false}, // #6 - {"Correct", Number("float"), 2.2, 2.2, false}, // #7 - {"Correct", Number("float"), 2, 2.0, false}, // #8 - {"Correct", Number("float"), float32(2.2), 2.200000047683716, false}, // #9 - {"Correct", Number("float"), int64(2), 2.0, false}, // #10 + {"Correct", Number("float"), int8(2), 2.0, false}, // #15 + {"Correct", Number("float"), 2.2, 2.2, false}, // #16 + {"Correct", Number("float"), 2, 2.0, false}, // #17 + {"Correct", Number("float"), int64(2), 2.0, false}, // #18 {"Wrong data", Number("int"), "", nil, true}, // #0 {"Wrong data", Number("int"), []byte(""), nil, true}, // #1 @@ -34,7 +43,7 @@ func TestNumberField_Decode(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Decode(nil, tt.field, tt.data) + got, err := Decode(context.Background(), tt.field, tt.data) if (err != nil) != tt.wantErr { t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr) return @@ -61,11 +70,10 @@ func TestNumberField_Encode(t *testing.T) { {"Correct", Number("int"), float32(2.6), int64(3), false}, // #4 {"Correct", Number("int"), 2.6, int64(3), false}, // #5 - {"Correct", Number("float"), int8(2), 2.0, false}, // #6 - {"Correct", Number("float"), 2.2, 2.2, false}, // #7 - {"Correct", Number("float"), 2, 2.0, false}, // #8 - {"Correct", Number("float"), float32(2.2), 2.200000047683716, false}, // #9 - {"Correct", Number("float"), int64(2), 2.0, false}, // #10 + {"Correct", Number("float"), int8(2), 2.0, false}, // #6 + {"Correct", Number("float"), 2.2, 2.2, false}, // #7 + {"Correct", Number("float"), 2, 2.0, false}, // #8 + {"Correct", Number("float"), int64(2), 2.0, false}, // #9 {"Wrong data", Number("int"), "", nil, true}, // #0 {"Wrong data", Number("int"), []byte(""), nil, true}, // #1 @@ -75,7 +83,7 @@ func TestNumberField_Encode(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Encode(nil, tt.field, tt.data) + got, err := Encode(context.Background(), tt.field, tt.data) if (err != nil) != tt.wantErr { t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/schema/test/object_test.go b/pkg/schema/test/object_test.go index e5af975cdb9ed8e86044e5d357530dd2420afee9..38868f66989108c142e50eaad62539be381edb4f 100644 --- a/pkg/schema/test/object_test.go +++ b/pkg/schema/test/object_test.go @@ -74,6 +74,80 @@ func TestNumberField_JSON(t *testing.T) { assert.Equal(t, fld, res) } +func TestNumberField_JSON_With_MaxInt(t *testing.T) { + t.Run("Without overflow from int64", func(t *testing.T) { + fld := field.Object("num", field.Number(field.NumberFormatInt)) + + itemData := map[string]interface{}{"num": 1<<53 - 1} + + b, err := json.Marshal(itemData) + require.NoError(t, err) + + itemDataToDecode := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(b, &itemDataToDecode)) + + _, err = field.Decode(context.Background(), fld, itemDataToDecode) + require.NoError(t, err) + }) + t.Run("Without overflow from float64", func(t *testing.T) { + fld := field.Object("num", field.Number(field.NumberFormatInt)) + + itemData := map[string]interface{}{"num": float64(1<<53 - 1)} + + b, err := json.Marshal(itemData) + require.NoError(t, err) + + itemDataToDecode := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(b, &itemDataToDecode)) + + _, err = field.Decode(context.Background(), fld, itemDataToDecode) + require.NoError(t, err) + }) + t.Run("With overflow from int64", func(t *testing.T) { + fld := field.Object("num", field.Number(field.NumberFormatInt)) + + itemData := map[string]interface{}{"num": 1 << 53} + + b, err := json.Marshal(itemData) + require.NoError(t, err) + + itemDataToDecode := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(b, &itemDataToDecode)) + + _, err = field.Decode(context.Background(), fld, itemDataToDecode) + require.Error(t, err, "integer out of range") + }) + t.Run("With overflow from uint64", func(t *testing.T) { + fld := field.Object("num", field.Number(field.NumberFormatInt)) + + itemData := map[string]interface{}{"num": uint64(1 << 53)} + + b, err := json.Marshal(itemData) + require.NoError(t, err) + + itemDataToDecode := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(b, &itemDataToDecode)) + + _, err = field.Decode(context.Background(), fld, itemDataToDecode) + require.Error(t, err, "integer out of range") + }) + t.Run("With overflow from float64", func(t *testing.T) { + fld := field.Object("num", field.Number(field.NumberFormatInt)) + + itemData := map[string]interface{}{"num": float64(1 << 53)} + + b, err := json.Marshal(itemData) + require.NoError(t, err) + + itemDataToDecode := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(b, &itemDataToDecode)) + + _, err = field.Decode(context.Background(), fld, itemDataToDecode) + require.Error(t, err, "integer out of range") + }) + +} + func TestSchema_JSON(t *testing.T) { enumStr := field.String().AddOptions( validate.Enum( diff --git a/pkg/setup/collection.go b/pkg/setup/collection.go index fcce47d62d6c5a7797e37d19a5b14d440cc0170e..494003d2f7cad1f8977a45913fe1b2ce8bb6c404 100644 --- a/pkg/setup/collection.go +++ b/pkg/setup/collection.go @@ -45,7 +45,10 @@ func NewCollectionConfig(collection *collections.Collection, opt ...CollectionsO func OverwriteCollection() CollectionsOption { return func(c *CollectionConfig) { c.UpdateFn = func(s *Setup, old, new *collections.Collection) (*collections.Collection, bool, bool, error) { - return new, true, true, nil + update := new.Name != old.Name || new.IsSingle() != old.IsSingle() || new.IsSystem() != old.IsSystem() || + new.IsNoData() != old.IsNoData() || new.Hidden != old.Hidden || new.IsView() != old.IsView() || !data.ElementsMatch(old.Tags, new.Tags) + + return new, update, !reflect.DeepEqual(old.Schema, new.Schema), nil } } }