diff --git a/pkg/stdlib/objects/keep.go b/pkg/stdlib/objects/keep.go new file mode 100644 index 00000000..f5ec181b --- /dev/null +++ b/pkg/stdlib/objects/keep.go @@ -0,0 +1,66 @@ +package objects + +import ( + "context" + + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Returns a new object with only given keys. + * @params src (Object) - source object. + * @params keys (Array Of String OR Strings) - keys that need to be keeped. + * @returns (Object) - New Object with only given keys. + */ +func Keep(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, core.MaxArgs) + + if err != nil { + return values.None, err + } + + err = core.ValidateType(args[0], core.ObjectType) + + if err != nil { + return values.None, err + } + + keys := values.NewArrayWith(args[1:]...) + + if len(args) == 2 && args[1].Type() == core.ArrayType { + keys = args[1].(*values.Array) + } + + err = validateArrayOfStrings(keys) + + if err != nil { + return values.None, err + } + + obj := args[0].(*values.Object) + resultObj := values.NewObject() + + var key values.String + var val core.Value + var exists values.Boolean + + for idx := values.NewInt(0); idx < keys.Length(); idx++ { + key = keys.Get(idx).(values.String) + if val, exists = obj.Get(key); exists { + resultObj.Set(key, val) + } + } + + return resultObj, nil +} + +func validateArrayOfStrings(arr *values.Array) (err error) { + for idx := values.NewInt(0); idx < arr.Length(); idx++ { + err = core.ValidateType(arr.Get(idx), core.StringType) + if err != nil { + break + } + } + return +} diff --git a/pkg/stdlib/objects/keep_test.go b/pkg/stdlib/objects/keep_test.go new file mode 100644 index 00000000..d3718643 --- /dev/null +++ b/pkg/stdlib/objects/keep_test.go @@ -0,0 +1,184 @@ +package objects_test + +import ( + "context" + "testing" + + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/objects" + . "github.com/smartystreets/goconvey/convey" +) + +func TestKeep(t *testing.T) { + Convey("When not enought arguments)", t, func() { + // there is no object + obj, err := objects.Keep(context.Background()) + + So(err, ShouldBeError) + So(obj, ShouldEqual, values.None) + + // there are no keys + obj, err = objects.Keep(context.Background(), values.NewObject()) + + So(err, ShouldBeError) + So(obj, ShouldEqual, values.None) + }) + + Convey("When first argument isn't object", t, func() { + obj, err := objects.Keep(context.Background(), values.NewInt(0)) + + So(err, ShouldBeError) + So(obj, ShouldEqual, values.None) + }) + + Convey("When wrong keys arguments", t, func() { + obj, err := objects.Keep(context.Background(), values.NewObject(), values.NewInt(0)) + + So(err, ShouldBeError) + So(obj, ShouldEqual, values.None) + + // looks like a valid case + // but there is another argument besides an array + obj, err = objects.Keep(context.Background(), values.NewObject(), values.NewArray(0), values.NewInt(0)) + + So(err, ShouldBeError) + So(obj, ShouldEqual, values.None) + }) +} + +func TestKeepStrings(t *testing.T) { + Convey("Keep key 'a'", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + resultObj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + ) + + afterKeep, err := objects.Keep(context.Background(), obj, values.NewString("a")) + + So(err, ShouldEqual, nil) + So(afterKeep.Compare(resultObj), ShouldEqual, 0) + }) + + Convey("Keep key doesn't exists", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + resultObj := values.NewObject() + + afterKeep, err := objects.Keep(context.Background(), obj, values.NewString("c")) + + So(err, ShouldEqual, nil) + So(isEqualObjects(afterKeep.(*values.Object), resultObj), ShouldEqual, true) + }) + + Convey("Keep when there are more keys than object properties", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + resultObj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + + afterKeep, err := objects.Keep(context.Background(), obj, + values.NewString("a"), values.NewString("b"), values.NewString("c"), + ) + + So(err, ShouldEqual, nil) + So(isEqualObjects(afterKeep.(*values.Object), resultObj), ShouldEqual, true) + }) +} + +func TestKeepArray(t *testing.T) { + Convey("Keep array", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + keys := values.NewArrayWith(values.NewString("a")) + resultObj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + ) + + afterKeep, err := objects.Keep(context.Background(), obj, keys) + + So(err, ShouldEqual, nil) + So(isEqualObjects(afterKeep.(*values.Object), resultObj), ShouldEqual, true) + }) + + Convey("Keep empty array", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + keys := values.NewArray(0) + resultObj := values.NewObject() + + afterKeep, err := objects.Keep(context.Background(), obj, keys) + + So(err, ShouldEqual, nil) + So(isEqualObjects(afterKeep.(*values.Object), resultObj), ShouldEqual, true) + }) + + Convey("Keep when there are more keys than object properties", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + keys := values.NewArrayWith( + values.NewString("a"), values.NewString("b"), values.NewString("c"), + ) + resultObj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + + afterKeep, err := objects.Keep(context.Background(), obj, keys) + + So(err, ShouldEqual, nil) + So(isEqualObjects(afterKeep.(*values.Object), resultObj), ShouldEqual, true) + }) + + Convey("When there is not string key", t, func() { + obj := values.NewObjectWith( + values.NewObjectProperty("a", values.NewInt(1)), + values.NewObjectProperty("b", values.NewString("string")), + ) + keys := values.NewArrayWith( + values.NewString("a"), + values.NewInt(0), + ) + + afterKeep, err := objects.Keep(context.Background(), obj, keys) + + So(err, ShouldBeError) + So(afterKeep, ShouldEqual, values.None) + }) +} + +func isEqualObjects(obj1 *values.Object, obj2 *values.Object) bool { + var val1 core.Value + var val2 core.Value + + for _, key := range obj1.Keys() { + val1, _ = obj1.Get(values.NewString(key)) + val2, _ = obj2.Get(values.NewString(key)) + if val1.Compare(val2) != 0 { + return false + } + } + for _, key := range obj2.Keys() { + val1, _ = obj1.Get(values.NewString(key)) + val2, _ = obj2.Get(values.NewString(key)) + if val2.Compare(val1) != 0 { + return false + } + } + return true +} diff --git a/pkg/stdlib/objects/keys.go b/pkg/stdlib/objects/keys.go index 63554363..5e1b8656 100644 --- a/pkg/stdlib/objects/keys.go +++ b/pkg/stdlib/objects/keys.go @@ -8,6 +8,12 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values" ) +/* + * Returns string array of object's keys + * @params obj (Object) - The object whose keys you want to extract + * @params sort (Boolean, optional) - If sort is true, then the returned keys will be sorted. + * @returns (Array of String) - Array that contains object keys. + */ func Keys(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 2) if err != nil { diff --git a/pkg/stdlib/objects/lib.go b/pkg/stdlib/objects/lib.go index 643310a7..b4d61690 100644 --- a/pkg/stdlib/objects/lib.go +++ b/pkg/stdlib/objects/lib.go @@ -6,5 +6,6 @@ func NewLib() map[string]core.Function { return map[string]core.Function{ "HAS": Has, "KEYS": Keys, + "KEEP": Keep, } }