From 2c7ec4de68939702938e6caea84962d834351941 Mon Sep 17 00:00:00 2001 From: janhavigupta007 <46344506+janhavigupta007@users.noreply.github.com> Date: Thu, 26 Sep 2024 04:15:01 +0000 Subject: [PATCH] Go: Check the response type sent from rust in Go (#2310) * Go:Adding a generic type Result in GO to pass nil info to the client Signed-off-by: Janhavi Gupta --- go/api/base_client.go | 104 ++++----- go/api/commands.go | 268 ++++++++++++++--------- go/api/glide_client.go | 23 +- go/api/response_handlers.go | 144 ++++++++---- go/api/response_types.go | 48 ++++ go/benchmarks/glide_benchmark_client.go | 14 +- go/examples/main.go | 16 +- go/integTest/glide_test_suite_test.go | 4 +- go/integTest/shared_commands_test.go | 253 +++++++++++---------- go/integTest/standalone_commands_test.go | 14 +- go/src/lib.rs | 97 +++++--- 11 files changed, 612 insertions(+), 373 deletions(-) create mode 100644 go/api/response_types.go diff --git a/go/api/base_client.go b/go/api/base_client.go index 0b7d9ef8e4..7118f2b85a 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -142,52 +142,52 @@ func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) { return cStrings, stringLengths } -func (client *baseClient) Set(key string, value string) (string, error) { +func (client *baseClient) Set(key string, value string) (Result[string], error) { result, err := client.executeCommand(C.Set, []string{key, value}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringResponse(result) } -func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (string, error) { +func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) { result, err := client.executeCommand(C.Set, append([]string{key, value}, options.toArgs()...)) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringOrNullResponse(result) } -func (client *baseClient) Get(key string) (string, error) { +func (client *baseClient) Get(key string) (Result[string], error) { result, err := client.executeCommand(C.Get, []string{key}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringOrNullResponse(result) } -func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { +func (client *baseClient) MSet(keyValueMap map[string]string) (Result[string], error) { result, err := client.executeCommand(C.MSet, utils.MapToString(keyValueMap)) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringResponse(result) } -func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { +func (client *baseClient) MSetNX(keyValueMap map[string]string) (Result[bool], error) { result, err := client.executeCommand(C.MSetNX, utils.MapToString(keyValueMap)) if err != nil { - return false, err + return CreateNilBoolResult(), err } return handleBooleanResponse(result) } -func (client *baseClient) MGet(keys []string) ([]string, error) { +func (client *baseClient) MGet(keys []string) ([]Result[string], error) { result, err := client.executeCommand(C.MGet, keys) if err != nil { return nil, err @@ -196,122 +196,122 @@ func (client *baseClient) MGet(keys []string) ([]string, error) { return handleStringArrayResponse(result) } -func (client *baseClient) Incr(key string) (int64, error) { +func (client *baseClient) Incr(key string) (Result[int64], error) { result, err := client.executeCommand(C.Incr, []string{key}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { +func (client *baseClient) IncrBy(key string, amount int64) (Result[int64], error) { result, err := client.executeCommand(C.IncrBy, []string{key, utils.IntToString(amount)}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) IncrByFloat(key string, amount float64) (float64, error) { +func (client *baseClient) IncrByFloat(key string, amount float64) (Result[float64], error) { result, err := client.executeCommand( C.IncrByFloat, []string{key, utils.FloatToString(amount)}, ) if err != nil { - return 0, err + return CreateNilFloat64Result(), err } return handleDoubleResponse(result) } -func (client *baseClient) Decr(key string) (int64, error) { +func (client *baseClient) Decr(key string) (Result[int64], error) { result, err := client.executeCommand(C.Decr, []string{key}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { +func (client *baseClient) DecrBy(key string, amount int64) (Result[int64], error) { result, err := client.executeCommand(C.DecrBy, []string{key, utils.IntToString(amount)}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) Strlen(key string) (int64, error) { +func (client *baseClient) Strlen(key string) (Result[int64], error) { result, err := client.executeCommand(C.Strlen, []string{key}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { +func (client *baseClient) SetRange(key string, offset int, value string) (Result[int64], error) { result, err := client.executeCommand(C.SetRange, []string{key, strconv.Itoa(offset), value}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) GetRange(key string, start int, end int) (string, error) { +func (client *baseClient) GetRange(key string, start int, end int) (Result[string], error) { result, err := client.executeCommand(C.GetRange, []string{key, strconv.Itoa(start), strconv.Itoa(end)}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringResponse(result) } -func (client *baseClient) Append(key string, value string) (int64, error) { +func (client *baseClient) Append(key string, value string) (Result[int64], error) { result, err := client.executeCommand(C.Append, []string{key, value}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) LCS(key1 string, key2 string) (string, error) { +func (client *baseClient) LCS(key1 string, key2 string) (Result[string], error) { result, err := client.executeCommand(C.LCS, []string{key1, key2}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringResponse(result) } -func (client *baseClient) GetDel(key string) (string, error) { +func (client *baseClient) GetDel(key string) (Result[string], error) { if key == "" { - return "", errors.New("key is required") + return CreateNilStringResult(), errors.New("key is required") } result, err := client.executeCommand(C.GetDel, []string{key}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringOrNullResponse(result) } -func (client *baseClient) HGet(key string, field string) (string, error) { +func (client *baseClient) HGet(key string, field string) (Result[string], error) { result, err := client.executeCommand(C.HGet, []string{key, field}) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringOrNullResponse(result) } -func (client *baseClient) HGetAll(key string) (map[string]string, error) { +func (client *baseClient) HGetAll(key string) (map[Result[string]]Result[string], error) { result, err := client.executeCommand(C.HGetAll, []string{key}) if err != nil { return nil, err @@ -320,7 +320,7 @@ func (client *baseClient) HGetAll(key string) (map[string]string, error) { return handleStringToStringMapResponse(result) } -func (client *baseClient) HMGet(key string, fields []string) ([]string, error) { +func (client *baseClient) HMGet(key string, fields []string) ([]Result[string], error) { result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) if err != nil { return nil, err @@ -329,43 +329,43 @@ func (client *baseClient) HMGet(key string, fields []string) ([]string, error) { return handleStringArrayResponse(result) } -func (client *baseClient) HSet(key string, values map[string]string) (int64, error) { +func (client *baseClient) HSet(key string, values map[string]string) (Result[int64], error) { result, err := client.executeCommand(C.HSet, utils.ConvertMapToKeyValueStringArray(key, values)) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) HSetNX(key string, field string, value string) (bool, error) { +func (client *baseClient) HSetNX(key string, field string, value string) (Result[bool], error) { result, err := client.executeCommand(C.HSetNX, []string{key, field, value}) if err != nil { - return false, err + return CreateNilBoolResult(), err } return handleBooleanResponse(result) } -func (client *baseClient) HDel(key string, fields []string) (int64, error) { +func (client *baseClient) HDel(key string, fields []string) (Result[int64], error) { result, err := client.executeCommand(C.HDel, append([]string{key}, fields...)) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) HLen(key string) (int64, error) { +func (client *baseClient) HLen(key string) (Result[int64], error) { result, err := client.executeCommand(C.HLen, []string{key}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) } -func (client *baseClient) HVals(key string) ([]string, error) { +func (client *baseClient) HVals(key string) ([]Result[string], error) { result, err := client.executeCommand(C.HVals, []string{key}) if err != nil { return nil, err @@ -374,16 +374,16 @@ func (client *baseClient) HVals(key string) ([]string, error) { return handleStringArrayResponse(result) } -func (client *baseClient) HExists(key string, field string) (bool, error) { +func (client *baseClient) HExists(key string, field string) (Result[bool], error) { result, err := client.executeCommand(C.HExists, []string{key, field}) if err != nil { - return false, err + return CreateNilBoolResult(), err } return handleBooleanResponse(result) } -func (client *baseClient) HKeys(key string) ([]string, error) { +func (client *baseClient) HKeys(key string) ([]Result[string], error) { result, err := client.executeCommand(C.HKeys, []string{key}) if err != nil { return nil, err @@ -392,10 +392,10 @@ func (client *baseClient) HKeys(key string) ([]string, error) { return handleStringArrayResponse(result) } -func (client *baseClient) HStrLen(key string, field string) (int64, error) { +func (client *baseClient) HStrLen(key string, field string) (Result[int64], error) { result, err := client.executeCommand(C.HStrlen, []string{key, field}) if err != nil { - return 0, err + return CreateNilInt64Result(), err } return handleLongResponse(result) diff --git a/go/api/commands.go b/go/api/commands.go index 56fbde418a..f1e924b676 100644 --- a/go/api/commands.go +++ b/go/api/commands.go @@ -17,18 +17,20 @@ type StringCommands interface { // value - The value to store with the given key. // // Return value: - // A simple "OK" response on success. + // A api.Result[string] containing "OK" response on success. // // For example: // result, err := client.Set("key", "value") - // result : "OK" + // result.Value() : "OK" + // result.IsNil() : false // // [valkey.io]: https://valkey.io/commands/set/ - Set(key string, value string) (string, error) + Set(key string, value string) (Result[string], error) // SetWithOptions sets the given key with the given value using the given options. The return value is dependent on the // passed options. If the value is successfully set, "OK" is returned. If value isn't set because of [OnlyIfExists] or - // [OnlyIfDoesNotExist] conditions, an empty string is returned (""). If [SetOptions#ReturnOldValue] is set, the old + // [OnlyIfDoesNotExist] conditions, api.CreateNilStringResult() is returned. If [SetOptions#ReturnOldValue] is + // set, the old // value is returned. // // See [valkey.io] for details. @@ -39,8 +41,9 @@ type StringCommands interface { // options - The Set options. // // Return value: - // If the value is successfully set, return "OK". - // If value isn't set because of ConditionalSet.OnlyIfExists or ConditionalSet.OnlyIfDoesNotExist conditions, return (""). + // If the value is successfully set, return api.Result[string] containing "OK". + // If value isn't set because of ConditionalSet.OnlyIfExists or ConditionalSet.OnlyIfDoesNotExist conditions, return + // api.CreateNilStringResult(). // If SetOptions.returnOldValue is set, return the old value as a String. // // For example: @@ -52,12 +55,14 @@ type StringCommands interface { // Count: uint64(5), // }, // }) - // result: "OK" + // result.Value(): "OK" + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/set/ - SetWithOptions(key string, value string, options *SetOptions) (string, error) + SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) - // Get string value associated with the given key, or an empty string is returned ("") if no such value exists. + // Get string value associated with the given key, or api.CreateNilStringResult() is returned if no such value + // exists. // // See [valkey.io] for details. // @@ -65,17 +70,19 @@ type StringCommands interface { // key - The key to be retrieved from the database. // // Return value: - // If key exists, returns the value of key as a String. Otherwise, return (""). + // If key exists, returns the value of key as a String. Otherwise, return [api.CreateNilStringResult()]. // // For example: // 1. key: value // result, err := client.Get("key") - // result: "value" + // result.Value(): "value" + // result.IsNil(): false // 2. result, err := client.Get("nonExistentKey") - // result: "" + // result.Value(): "" + // result.IsNil(): true // // [valkey.io]: https://valkey.io/commands/get/ - Get(key string) (string, error) + Get(key string) (Result[string], error) // Sets multiple keys to multiple values in a single operation. // @@ -88,14 +95,15 @@ type StringCommands interface { // keyValueMap - A key-value map consisting of keys and their respective values to set. // // Return value: - // "OK" on success. + // A Result[string] containing "OK" on success. // // For example: // result, err := client.MSet(map[string]string{"key1": "value1", "key2": "value2"}) - // result: "OK" + // result.Value(): "OK" + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/mset/ - MSet(keyValueMap map[string]string) (string, error) + MSet(keyValueMap map[string]string) (Result[string], error) // Retrieves the values of multiple keys. // @@ -109,15 +117,19 @@ type StringCommands interface { // // Return value: // An array of values corresponding to the provided keys. - // If a key is not found, its corresponding value in the list will be an empty string(""). + // If a key is not found, its corresponding value in the list will be a [api.CreateNilStringResult()] // // For example: // key1: value1, key2: value2 // result, err := client.MGet([]string{"key1", "key2", "key3"}) - // result : {"value1", "value2", ""} + // result : { + // api.CreateStringResult("value1), + // api.CreateStringResult("value2"), + // api.CreateNilStringResult() + // } // // [valkey.io]: https://valkey.io/commands/mget/ - MGet(keys []string) ([]string, error) + MGet(keys []string) ([]Result[string], error) // Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or more keys already exist, // the entire operation fails. @@ -131,17 +143,19 @@ type StringCommands interface { // keyValueMap - A key-value map consisting of keys and their respective values to set. // // Return value: - // true, if all keys were set. false, if no key was set. + // A Result[bool] containing true, if all keys were set. false, if no key was set. // // For example: // 1. result, err := client.MSetNX(map[string]string{"key1": "value1", "key2": "value2"}) - // result: true + // result.Value(): true + // result.IsNil(): false // 2. key3: initialValue // result, err := client.MSetNX(map[string]string{"key3": "value3", "key4": "value4"}) - // result: false + // result.Value(): false + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/msetnx/ - MSetNX(keyValueMap map[string]string) (bool, error) + MSetNX(keyValueMap map[string]string) (Result[bool], error) // Increments the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. // @@ -151,15 +165,16 @@ type StringCommands interface { // key - The key to increment its value. // // Return value: - // The value of key after the increment. + // The Result[int64] of key after the increment. // // For example: // key: 1 // result, err := client.Incr("key"); - // result: 2 + // result.Value(): 2 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/incr/ - Incr(key string) (int64, error) + Incr(key string) (Result[int64], error) // Increments the number stored at key by amount. If key does not exist, it is set to 0 before performing the operation. // @@ -170,15 +185,16 @@ type StringCommands interface { // amount - The amount to increment. // // Return value: - // The value of key after the increment. + // The Result[int64] of key after the increment. // // For example: // key: 1 - // result, err := client.IncrBy("key", 2); - // result: 3 + // result, err := client.IncrBy("key", 2) + // result.Value(): 3 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/incrby/ - IncrBy(key string, amount int64) (int64, error) + IncrBy(key string, amount int64) (Result[int64], error) // Increments the string representing a floating point number stored at key by amount. By using a negative increment value, // the result is that the value stored at key is decremented. If key does not exist, it is set to 0 before performing the @@ -191,15 +207,16 @@ type StringCommands interface { // amount - The amount to increment. // // Return value: - // The value of key after the increment. + // The Result[float64] of key after the increment. // // For example: // key: 1 - // result, err := client.IncrBy("key", 0.5); - // result: 1.5 + // result, err := client.IncrBy("key", 0.5) + // result.Value(): 1.5 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/incrbyfloat/ - IncrByFloat(key string, amount float64) (float64, error) + IncrByFloat(key string, amount float64) (Result[float64], error) // Decrements the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. // @@ -209,15 +226,16 @@ type StringCommands interface { // key - The key to decrement its value. // // Return value: - // The value of key after the decrement. + // The Result[int64] of key after the decrement. // // For example: // key: 1 - // result, err := client.Decr("key"); - // result: 0 + // result, err := client.Decr("key") + // result.Value(): 0 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/decr/ - Decr(key string) (int64, error) + Decr(key string) (Result[int64], error) // Decrements the number stored at code by amount. If key does not exist, it is set to 0 before performing the operation. // @@ -228,15 +246,16 @@ type StringCommands interface { // amount - The amount to decrement. // // Return value: - // The value of key after the decrement. + // The Result[int64] of key after the decrement. // // For example: // key: 1 - // result, err := client.DecrBy("key", 2); - // result: -1 + // result, err := client.DecrBy("key", 2) + // result.Value(): -1 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/decrby/ - DecrBy(key string, amount int64) (int64, error) + DecrBy(key string, amount int64) (Result[int64], error) // Returns the length of the string value stored at key. // @@ -246,16 +265,17 @@ type StringCommands interface { // key - The key to check its length. // // Return value: - // The length of the string value stored at key. - // If key does not exist, it is treated as an empty string, and the command returns 0. + // The length of the string value stored at key as Result[int64]. + // If key does not exist, it is treated as an empty string, and the command returns Result[int64] containing 0 . // // For example: // key: value - // result, err := client.Strlen("key"); - // result: 5 + // result, err := client.Strlen("key") + // result.Value(): 5 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/strlen/ - Strlen(key string) (int64, error) + Strlen(key string) (Result[int64], error) // Overwrites part of the string stored at key, starting at the specified byte's offset, for the entire length of value. // If the offset is larger than the current length of the string at key, the string is padded with zero bytes to make @@ -270,19 +290,20 @@ type StringCommands interface { // value - The string written with offset. // // Return value: - // The length of the string stored at key after it was modified. + // The length of the string stored at key after it was modified as Result[int64]. // // For example: // 1. result, err := client.SetRange("key", 6, "GLIDE"); - // result: 11 (New key created with length of 11 bytes) + // result.Value(): 11 (New key created with length of 11 bytes) + // result.IsNil(): false // value, err := client.Get("key") - // value: "\x00\x00\x00\x00\x00\x00GLIDE" + // value.Value(): "\x00\x00\x00\x00\x00\x00GLIDE" // 2. "key": "愛" (value char takes 3 bytes) // result, err = client.SetRange("key", 1, "a") - // "key": �a� // (becomes an invalid UTF-8 string) + // result.Value(): �a� // (becomes an invalid UTF-8 string) // // [valkey.io]: https://valkey.io/commands/setrange/ - SetRange(key string, offset int, value string) (int64, error) + SetRange(key string, offset int, value string) (Result[int64], error) // Returns the substring of the string value stored at key, determined by the byte's offsets start and end (both are // inclusive). @@ -297,21 +318,24 @@ type StringCommands interface { // end - The ending offset. // // Return value: - // A substring extracted from the value stored at key. - // An ("") empty string is returned if the offset is out of bounds. + // A Result[string] containing substring extracted from the value stored at key. + // A [api.NilResult[string]] (api.CreateNilStringResult()) is returned if the offset is out of bounds. // // For example: // 1. mykey: "This is a string" - // result, err := client.GetRange("mykey", 0, 3); - // result: "This" - // result, err := client.GetRange("mykey", -3, -1); - // result: "ing" (extracted last 3 characters of a string) + // result, err := client.GetRange("mykey", 0, 3) + // result.Value(): "This" + // result.IsNil(): false + // result, err := client.GetRange("mykey", -3, -1) + // result.Value(): "ing" (extracted last 3 characters of a string) + // result.IsNil(): false // 2. "key": "愛" (value char takes 3 bytes) // result, err = client.GetRange("key", 0, 1) - // result: � // (returns an invalid UTF-8 string) + // result.Value(): "�" (returns an invalid UTF-8 string) + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/getrange/ - GetRange(key string, start int, end int) (string, error) + GetRange(key string, start int, end int) (Result[string], error) // Appends a value to a key. If key does not exist it is created and set as an empty string, so APPEND will be similar to // SET in this special case. @@ -323,14 +347,15 @@ type StringCommands interface { // value - The value to append. // // Return value: - // The length of the string after appending the value. + // The Result[int64] containing the length of the string after appending the value. // // For example: - // result, err := client.Append("key", "value"); - // result: 5 + // result, err := client.Append("key", "value") + // result.Value(): 5 + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/append/ - Append(key string, value string) (int64, error) + Append(key string, value string) (Result[int64], error) // Returns the longest common subsequence between strings stored at key1 and key2. // @@ -347,16 +372,17 @@ type StringCommands interface { // key2 - The key that stores the second string. // // Return value: - // A String containing the longest common subsequence between the 2 strings. - // An empty String is returned if the keys do not exist or have no common subsequences. + // A Result[string] containing the longest common subsequence between the 2 strings. + // A Result[string] containing empty String is returned if the keys do not exist or have no common subsequences. // // For example: // testKey1: foo, testKey2: fao - // result, err := client.LCS("testKey1", "testKey2"); - // result: "fo" + // result, err := client.LCS("testKey1", "testKey2") + // result.Value(): "fo" + // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/lcs/ - LCS(key1 string, key2 string) (string, error) + LCS(key1 string, key2 string) (Result[string], error) // GetDel gets the value associated with the given key and deletes the key. // // Parameters: @@ -364,13 +390,13 @@ type StringCommands interface { // // Return value: // If key exists, returns the value of the key as a String and deletes the key. - // If key does not exist, returns an empty string (""). + // If key does not exist, returns a [api.NilResult[string]] (api.CreateNilStringResult()). // // For example: // result, err := client.GetDel("key") // //[valkey.io]: https://valkey.io/commands/getdel/ - GetDel(key string) (string, error) + GetDel(key string) (Result[string], error) } // HashCommands supports commands and transactions for the "Hash Commands" group for standalone and cluster @@ -389,18 +415,20 @@ type HashCommands interface { // field - The field in the hash stored at key to retrieve from the database. // // Return value: - // The value associated with field, or an empty string when field is not present in the hash or key does not exist. + // The Result[string] associated with field, or [api.NilResult[string]](api.CreateNilStringResult()) when field is not + // present in the hash or key does not exist. // // For example: // Assume we have the following hash: // my_hash := map[string]string{"field1": "value", "field2": "another_value"} // payload, err := client.HGet("my_hash", "field1") - // // payload equals "value" + // // payload.Value(): "value" + // // payload.IsNil(): false // payload, err = client.HGet("my_hash", "nonexistent_field") - // // payload equals "" + // // payload equals api.CreateNilStringResult() // // [valkey.io]: https://valkey.io/commands/hget/ - HGet(key string, field string) (string, error) + HGet(key string, field string) (Result[string], error) // HGetAll returns all fields and values of the hash stored at key. // @@ -410,14 +438,18 @@ type HashCommands interface { // key - The key of the hash. // // Return value: - // A map of all fields and their values in the hash, or an empty map when key does not exist. + // A map of all fields and their values as Result[string] in the hash, or an empty map when key does not exist. // // For example: // fieldValueMap, err := client.HGetAll("my_hash") - // // fieldValueMap equals map[string]string{"field1": "value1", "field2": "value2"} + // // field1 equals api.CreateStringResult("field1") + // // value1 equals api.CreateStringResult("value1") + // // field2 equals api.CreateStringResult("field2") + // // value2 equals api.CreateStringResult("value2") + // // fieldValueMap equals map[api.Result[string]]api.Result[string]{field1: value1, field2: value2} // // [valkey.io]: https://valkey.io/commands/hgetall/ - HGetAll(key string) (map[string]string, error) + HGetAll(key string) (map[Result[string]]Result[string], error) // HMGet returns the values associated with the specified fields in the hash stored at key. // @@ -428,16 +460,19 @@ type HashCommands interface { // fields - The fields in the hash stored at key to retrieve from the database. // // Return value: - // An array of values associated with the given fields, in the same order as they are requested. - // For every field that does not exist in the hash, a null value is returned. + // An array of Result[string]s associated with the given fields, in the same order as they are requested. + // For every field that does not exist in the hash, a [api.NilResult[string]](api.CreateNilStringResult()) is + // returned. // If key does not exist, returns an empty string array. // // For example: // values, err := client.HMGet("my_hash", []string{"field1", "field2"}) - // // values equals []string{"value1", "value2"} + // // value1 equals api.CreateStringResult("value1") + // // value2 equals api.CreateStringResult("value2") + // // values equals []api.Result[string]{value1, value2} // // [valkey.io]: https://valkey.io/commands/hmget/ - HMGet(key string, fields []string) ([]string, error) + HMGet(key string, fields []string) ([]Result[string], error) // HSet sets the specified fields to their respective values in the hash stored at key. // This command overwrites the values of specified fields that exist in the hash. @@ -450,14 +485,15 @@ type HashCommands interface { // values - A map of field-value pairs to set in the hash. // // Return value: - // The number of fields that were added or updated. + // The Result[int64] containing number of fields that were added or updated. // // For example: // num, err := client.HSet("my_hash", map[string]string{"field": "value", "field2": "value2"}) - // // num equals 2 + // // num.Value(): 2 + // // num.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hset/ - HSet(key string, values map[string]string) (int64, error) + HSet(key string, values map[string]string) (Result[int64], error) // HSetNX sets field in the hash stored at key to value, only if field does not yet exist. // If key does not exist, a new key holding a hash is created. @@ -471,17 +507,19 @@ type HashCommands interface { // value - The value to set. // // Return value: - // true if field is a new field in the hash and value was set. + // A Result[bool] containing true if field is a new field in the hash and value was set. // false if field already exists in the hash and no operation was performed. // // For example: // payload1, err := client.HSetNX("myHash", "field", "value") - // // payload1 equals true + // // payload1.Value(): true + // // payload1.IsNil(): false // payload2, err := client.HSetNX("myHash", "field", "newValue") - // // payload2 equals false + // // payload2.Value(): false + // // payload2.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hsetnx/ - HSetNX(key string, field string, value string) (bool, error) + HSetNX(key string, field string, value string) (Result[bool], error) // HDel removes the specified fields from the hash stored at key. // Specified fields that do not exist within this hash are ignored. @@ -494,14 +532,16 @@ type HashCommands interface { // fields - The fields to remove from the hash stored at key. // // Return value: - // The number of fields that were removed from the hash, not including specified but non-existing fields. + // The Result[int64] containing number of fields that were removed from the hash, not including specified but non-existing + // fields. // // For example: // num, err := client.HDel("my_hash", []string{"field_1", "field_2"}) - // // num equals 2 + // // num.Value(): 2 + // // num.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hdel/ - HDel(key string, fields []string) (int64, error) + HDel(key string, fields []string) (Result[int64], error) // HLen returns the number of fields contained in the hash stored at key. // @@ -511,17 +551,19 @@ type HashCommands interface { // key - The key of the hash. // // Return value: - // The number of fields in the hash, or 0 when key does not exist. + // The Result[int64] containing number of fields in the hash, or 0 when key does not exist. // If key holds a value that is not a hash, an error is returned. // // For example: // num1, err := client.HLen("myHash") - // // num1 equals 3 + // // num.Value(): 3 + // // num.IsNil(): false // num2, err := client.HLen("nonExistingKey") - // // num2 equals 0 + // // num.Value(): 0 + // // num.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hlen/ - HLen(key string) (int64, error) + HLen(key string) (Result[int64], error) // HVals returns all values in the hash stored at key. // @@ -531,14 +573,17 @@ type HashCommands interface { // key - The key of the hash. // // Return value: - // A slice of strings containing all the values in the hash, or an empty slice when key does not exist. + // A slice of Result[string]s containing all the values in the hash, or an empty slice when key does not exist. // // For example: // values, err := client.HVals("myHash") - // // values equals []string{"value1", "value2", "value3"} + // // value1 equals api.CreateStringResult("value1") + // // value2 equals api.CreateStringResult("value2") + // // value3 equals api.CreateStringResult("value3") + // // values equals []api.Result[string]{value1, value2, value3} // // [valkey.io]: https://valkey.io/commands/hvals/ - HVals(key string) ([]string, error) + HVals(key string) ([]Result[string], error) // HExists returns if field is an existing field in the hash stored at key. // @@ -549,17 +594,19 @@ type HashCommands interface { // field - The field to check in the hash stored at key. // // Return value: - // true if the hash contains the specified field. + // A Result[bool] containing true if the hash contains the specified field. // false if the hash does not contain the field, or if the key does not exist. // // For example: // exists, err := client.HExists("my_hash", "field1") - // // exists equals true + // // exists.Value(): true + // // exists.IsNil(): false // exists, err = client.HExists("my_hash", "non_existent_field") - // // exists equals false + // // exists.Value(): false + // // exists.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hexists/ - HExists(key string, field string) (bool, error) + HExists(key string, field string) (Result[bool], error) // HKeys returns all field names in the hash stored at key. // @@ -569,14 +616,16 @@ type HashCommands interface { // key - The key of the hash. // // Return value: - // A slice of strings containing all the field names in the hash, or an empty slice when key does not exist. + // A slice of Result[string]s containing all the field names in the hash, or an empty slice when key does not exist. // // For example: // names, err := client.HKeys("my_hash") - // // names equals []string{"field_1", "field_2"} + // // field1 equals api.CreateStringResult("field_1") + // // field2 equals api.CreateStringResult("field_2") + // // names equals []api.Result[string]{field1, field2} // // [valkey.io]: https://valkey.io/commands/hkeys/ - HKeys(key string) ([]string, error) + HKeys(key string) ([]Result[string], error) // HStrLen returns the string length of the value associated with field in the hash stored at key. // If the key or the field do not exist, 0 is returned. @@ -588,12 +637,13 @@ type HashCommands interface { // field - The field to get the string length of its value. // // Return value: - // The length of the string value associated with field, or 0 when field or key do not exist. + // The Result[int64] containing length of the string value associated with field, or 0 when field or key do not exist. // // For example: // strlen, err := client.HStrLen("my_hash", "my_field") - // // strlen equals 10 + // // strlen.Value(): 10 + // // strlen.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hstrlen/ - HStrLen(key string, field string) (int64, error) + HStrLen(key string, field string) (Result[int64], error) } diff --git a/go/api/glide_client.go b/go/api/glide_client.go index a62573284b..27f3dbac75 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -40,8 +40,11 @@ func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { if err != nil { return nil, err } - - return handleStringOrNullResponse(res) + resString, err := handleStringOrNullResponse(res) + if err != nil { + return nil, err + } + return resString.Value(), err } // Sets configuration parameters to the specified values. @@ -54,18 +57,18 @@ func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { // // Return value: // -// "OK" if all configurations have been successfully set. Otherwise, raises an error. +// A api.Result[string] containing "OK" if all configurations have been successfully set. Otherwise, raises an error. // // For example: // // result, err := client.ConfigSet(map[string]string{"timeout": "1000", "maxmemory": "1GB"}) -// result: "OK" +// result.Value(): "OK" // // [valkey.io]: https://valkey.io/commands/config-set/ -func (client *GlideClient) ConfigSet(parameters map[string]string) (string, error) { +func (client *GlideClient) ConfigSet(parameters map[string]string) (Result[string], error) { result, err := client.executeCommand(C.ConfigSet, utils.MapToString(parameters)) if err != nil { - return "", err + return CreateNilStringResult(), err } return handleStringResponse(result) } @@ -80,16 +83,16 @@ func (client *GlideClient) ConfigSet(parameters map[string]string) (string, erro // // Return value: // -// A map of values corresponding to the configuration parameters. +// A map of api.Result[string] corresponding to the configuration parameters. // // For example: // // result, err := client.ConfigGet([]string{"timeout" , "maxmemory"}) -// result["timeout"] = "1000" -// result["maxmemory"] = "1GB" +// result[api.CreateStringResult("timeout")] = api.CreateStringResult("1000") +// result[api.CreateStringResult"maxmemory")] = api.CreateStringResult("1GB") // // [valkey.io]: https://valkey.io/commands/config-get/ -func (client *GlideClient) ConfigGet(args []string) (map[string]string, error) { +func (client *GlideClient) ConfigGet(args []string) (map[Result[string]]Result[string], error) { res, err := client.executeCommand(C.ConfigGet, args) if err != nil { return nil, err diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 87e5fb5424..0959191ef5 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -7,88 +7,140 @@ package api import "C" import ( - "errors" + "fmt" "unsafe" ) -func convertCharArrayToString(arr *C.char, length C.long) string { - if arr == nil { - return "" +func checkResponseType(response *C.struct_CommandResponse, expectedType C.ResponseType, isNilable bool) error { + expectedTypeInt := uint32(expectedType) + expectedTypeStr := C.get_response_type_string(expectedTypeInt) + defer C.free_response_type_string(expectedTypeStr) + + if !isNilable && response == nil { + return &RequestError{ + fmt.Sprintf( + "Unexpected return type from Valkey: got nil, expected %s", + C.GoString(expectedTypeStr), + ), + } } - byteSlice := C.GoBytes(unsafe.Pointer(arr), C.int(int64(length))) - // Create Go string from byte slice (preserving null characters) - return string(byteSlice) -} -func handleStringResponse(response *C.struct_CommandResponse) (string, error) { - if response == nil { - return "", errors.New("handleStringArrayResponse: command response is nil") + if isNilable && (response == nil || response.response_type == uint32(C.Null)) { + return nil } - defer C.free_command_response(response) - return convertCharArrayToString(response.string_value, response.string_value_len), nil + if response.response_type == expectedTypeInt { + return nil + } + + actualTypeStr := C.get_response_type_string(response.response_type) + defer C.free_response_type_string(actualTypeStr) + return &RequestError{ + fmt.Sprintf( + "Unexpected return type from Valkey: got %s, expected %s", + C.GoString(actualTypeStr), + C.GoString(expectedTypeStr), + ), + } } -func handleStringOrNullResponse(response *C.struct_CommandResponse) (string, error) { - if response == nil { - return "", nil +func convertCharArrayToString(response *C.struct_CommandResponse, isNilable bool) (Result[string], error) { + typeErr := checkResponseType(response, C.String, isNilable) + if typeErr != nil { + return CreateNilStringResult(), typeErr + } + + if response.string_value == nil { + return CreateNilStringResult(), nil } + byteSlice := C.GoBytes(unsafe.Pointer(response.string_value), C.int(int64(response.string_value_len))) - return handleStringResponse(response) + // Create Go string from byte slice (preserving null characters) + return CreateStringResult(string(byteSlice)), nil } -// handleBooleanResponse converts a C struct_CommandResponse's bool_value to a Go bool. -func handleBooleanResponse(response *C.struct_CommandResponse) (bool, error) { - if response == nil { - return false, errors.New("handleBooleanResponse: command response is nil") - } +func handleStringResponse(response *C.struct_CommandResponse) (Result[string], error) { + defer C.free_command_response(response) + + return convertCharArrayToString(response, false) +} +func handleStringOrNullResponse(response *C.struct_CommandResponse) (Result[string], error) { defer C.free_command_response(response) - return bool(response.bool_value), nil + + return convertCharArrayToString(response, true) } -func handleStringArrayResponse(response *C.struct_CommandResponse) ([]string, error) { - if response == nil { - return nil, errors.New("handleStringArrayResponse: command response is nil") +func handleStringArrayResponse(response *C.struct_CommandResponse) ([]Result[string], error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Array, false) + if typeErr != nil { + return nil, typeErr } - defer C.free_command_response(response) - var slice []string + var slice []Result[string] for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - slice = append(slice, convertCharArrayToString(v.string_value, v.string_value_len)) + res, err := convertCharArrayToString(&v, true) + if err != nil { + return nil, err + } + slice = append(slice, res) } - return slice, nil } -func handleLongResponse(response *C.struct_CommandResponse) (int64, error) { - if response == nil { - return 0, errors.New("handleLongResponse: command response is nil") +func handleLongResponse(response *C.struct_CommandResponse) (Result[int64], error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Int, false) + if typeErr != nil { + return CreateNilInt64Result(), typeErr } - defer C.free_command_response(response) - return int64(response.int_value), nil + return CreateInt64Result(int64(response.int_value)), nil } -func handleDoubleResponse(response *C.struct_CommandResponse) (float64, error) { - if response == nil { - return 0, errors.New("handleStringArrayResponse: command response is nil") +func handleDoubleResponse(response *C.struct_CommandResponse) (Result[float64], error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Float, false) + if typeErr != nil { + return CreateNilFloat64Result(), typeErr } - defer C.free_command_response(response) - return float64(response.float_value), nil + return CreateFloat64Result(float64(response.float_value)), nil } -func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[string]string, error) { - if response == nil { - return nil, errors.New("handleStringMapResponse: command response is nil") +func handleBooleanResponse(response *C.struct_CommandResponse) (Result[bool], error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Bool, false) + if typeErr != nil { + return CreateNilBoolResult(), typeErr } + return CreateBoolResult(bool(response.bool_value)), nil +} + +func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[Result[string]]Result[string], error) { defer C.free_command_response(response) - m := make(map[string]string, response.array_value_len) + + typeErr := checkResponseType(response, C.Map, false) + if typeErr != nil { + return nil, typeErr + } + + m := make(map[Result[string]]Result[string], response.array_value_len) for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - key := convertCharArrayToString(v.map_key.string_value, v.map_key.string_value_len) - value := convertCharArrayToString(v.map_value.string_value, v.map_value.string_value_len) + key, err := convertCharArrayToString(v.map_key, true) + if err != nil { + return nil, err + } + value, err := convertCharArrayToString(v.map_value, true) + if err != nil { + return nil, err + } m[key] = value } diff --git a/go/api/response_types.go b/go/api/response_types.go new file mode 100644 index 0000000000..2a480adf57 --- /dev/null +++ b/go/api/response_types.go @@ -0,0 +1,48 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +type Result[T any] struct { + val T + isNil bool +} + +func (result Result[T]) IsNil() bool { + return result.isNil +} + +func (result Result[T]) Value() T { + return result.val +} + +func CreateStringResult(str string) Result[string] { + return Result[string]{val: str, isNil: false} +} + +func CreateNilStringResult() Result[string] { + return Result[string]{val: "", isNil: true} +} + +func CreateInt64Result(intVal int64) Result[int64] { + return Result[int64]{val: intVal, isNil: false} +} + +func CreateNilInt64Result() Result[int64] { + return Result[int64]{val: 0, isNil: true} +} + +func CreateFloat64Result(floatVal float64) Result[float64] { + return Result[float64]{val: floatVal, isNil: false} +} + +func CreateNilFloat64Result() Result[float64] { + return Result[float64]{val: 0, isNil: true} +} + +func CreateBoolResult(boolVal bool) Result[bool] { + return Result[bool]{val: boolVal, isNil: false} +} + +func CreateNilBoolResult() Result[bool] { + return Result[bool]{val: false, isNil: true} +} diff --git a/go/benchmarks/glide_benchmark_client.go b/go/benchmarks/glide_benchmark_client.go index 6ae9f4d8d4..60c8727528 100644 --- a/go/benchmarks/glide_benchmark_client.go +++ b/go/benchmarks/glide_benchmark_client.go @@ -37,11 +37,21 @@ func (glideBenchmarkClient *glideBenchmarkClient) connect(connectionSettings *co } func (glideBenchmarkClient *glideBenchmarkClient) get(key string) (string, error) { - return glideBenchmarkClient.client.Get(key) + result, err := glideBenchmarkClient.client.Get(key) + if err != nil { + return "", err + } + + return result.Value(), nil } func (glideBenchmarkClient *glideBenchmarkClient) set(key string, value string) (string, error) { - return glideBenchmarkClient.client.Set(key, value) + result, err := glideBenchmarkClient.client.Set(key, value) + if err != nil { + return "", err + } + + return result.Value(), nil } func (glideBenchmarkClient *glideBenchmarkClient) close() error { diff --git a/go/examples/main.go b/go/examples/main.go index d2b9761335..0f03e89ea0 100644 --- a/go/examples/main.go +++ b/go/examples/main.go @@ -28,29 +28,29 @@ func main() { } fmt.Println("PING:", res) - res, err = client.Set("apples", "oran\x00ges") + result, err := client.Set("apples", "oran\x00ges") if err != nil { log.Fatal("Glide example failed with an error: ", err) } - fmt.Println("SET(apples, oranges):", res) + fmt.Println("SET(apples, oranges):", result.Value()) - res, err = client.Get("invalidKey") + result, err = client.Get("invalidKey") if err != nil { log.Fatal("Glide example failed with an error: ", err) } - fmt.Println("GET(invalidKey):", res) + fmt.Println("GET(invalidKey):", result.Value()) - res, err = client.Get("apples") + result, err = client.Get("apples") if err != nil { log.Fatal("Glide example failed with an error: ", err) } - fmt.Println("GET(apples):", res) + fmt.Println("GET(apples):", result.Value()) - res, err = client.Get("app\x00les") + result, err = client.Get("app\x00les") if err != nil { log.Fatal("Glide example failed with an error: ", err) } - fmt.Println("GET(app\x00les):", res) + fmt.Println("GET(app\x00les):", result.Value()) client.Close() } diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go index dddfd3d297..e2f51e67f6 100644 --- a/go/integTest/glide_test_suite_test.go +++ b/go/integTest/glide_test_suite_test.go @@ -186,7 +186,7 @@ func (suite *GlideTestSuite) runWithClients(clients []api.BaseClient, test func( } } -func (suite *GlideTestSuite) verifyOK(result string, err error) { +func (suite *GlideTestSuite) verifyOK(result api.Result[string], err error) { assert.Nil(suite.T(), err) - assert.Equal(suite.T(), api.OK, result) + assert.Equal(suite.T(), api.OK, result.Value()) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 410e3a86d4..87d4c9c5cc 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -23,7 +23,7 @@ func (suite *GlideTestSuite) TestSetAndGet_noOptions() { result, err := client.Get(keyName) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), initialValue, result) + assert.Equal(suite.T(), initialValue, result.Value()) }) } @@ -34,7 +34,7 @@ func (suite *GlideTestSuite) TestSetAndGet_byteString() { result, err := client.Get(keyName) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), invalidUTF8Value, result) + assert.Equal(suite.T(), invalidUTF8Value, result.Value()) }) } @@ -46,7 +46,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_ReturnOldValue() { result, err := client.SetWithOptions(keyName, anotherValue, opts) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), initialValue, result) + assert.Equal(suite.T(), initialValue, result.Value()) }) } @@ -61,7 +61,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfExists_overwrite() { result, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), anotherValue, result) + assert.Equal(suite.T(), anotherValue, result.Value()) }) } @@ -72,7 +72,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfExists_missingKey() { result, err := client.SetWithOptions(key, anotherValue, opts) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -85,7 +85,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfDoesNotExist_missingKey() result, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), anotherValue, result) + assert.Equal(suite.T(), anotherValue, result.Value()) }) } @@ -98,12 +98,12 @@ func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfDoesNotExist_existingKey() result, err := client.SetWithOptions(key, anotherValue, opts) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), initialValue, result) + assert.Equal(suite.T(), initialValue, result.Value()) }) } @@ -116,7 +116,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_KeepExistingExpiry() { result, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), initialValue, result) + assert.Equal(suite.T(), initialValue, result.Value()) opts = &api.SetOptions{Expiry: &api.Expiry{Type: api.KeepExisting}} suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) @@ -124,13 +124,13 @@ func (suite *GlideTestSuite) TestSetWithOptions_KeepExistingExpiry() { result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), anotherValue, result) + assert.Equal(suite.T(), anotherValue, result.Value()) time.Sleep(2222 * time.Millisecond) result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -143,7 +143,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_UpdateExistingExpiry() { result, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), initialValue, result) + assert.Equal(suite.T(), initialValue, result.Value()) opts = &api.SetOptions{Expiry: &api.Expiry{Type: api.Milliseconds, Count: uint64(2000)}} suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) @@ -151,13 +151,13 @@ func (suite *GlideTestSuite) TestSetWithOptions_UpdateExistingExpiry() { result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), anotherValue, result) + assert.Equal(suite.T(), anotherValue, result.Value()) time.Sleep(2222 * time.Millisecond) result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -169,7 +169,7 @@ func (suite *GlideTestSuite) TestSetWithOptions_ReturnOldValue_nonExistentKey() result, err := client.SetWithOptions(key, anotherValue, opts) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -187,7 +187,9 @@ func (suite *GlideTestSuite) TestMSetAndMGet_existingAndNonExistingKeys() { } suite.verifyOK(client.MSet(keyValueMap)) keys := []string{key1, key2, key3} - values := []string{value, value, ""} + stringValue := api.CreateStringResult(value) + nullResult := api.CreateNilStringResult() + values := []api.Result[string]{stringValue, stringValue, nullResult} result, err := client.MGet(keys) assert.Nil(suite.T(), err) @@ -208,9 +210,10 @@ func (suite *GlideTestSuite) TestMSetNXAndMGet_nonExistingKey_valuesSet() { } res, err := client.MSetNX(keyValueMap) assert.Nil(suite.T(), err) - assert.True(suite.T(), res) + assert.True(suite.T(), res.Value()) keys := []string{key1, key2, key3} - values := []string{value, value, value} + stringValue := api.CreateStringResult(value) + values := []api.Result[string]{stringValue, stringValue, stringValue} result, err := client.MGet(keys) assert.Nil(suite.T(), err) @@ -233,9 +236,11 @@ func (suite *GlideTestSuite) TestMSetNXAndMGet_existingKey_valuesNotUpdated() { } res, err := client.MSetNX(keyValueMap) assert.Nil(suite.T(), err) - assert.False(suite.T(), res) + assert.False(suite.T(), res.Value()) keys := []string{key1, key2, key3} - values := []string{oldValue, "", ""} + oldResult := api.CreateStringResult(oldValue) + nullResult := api.CreateNilStringResult() + values := []api.Result[string]{oldResult, nullResult, nullResult} result, err := client.MGet(keys) assert.Nil(suite.T(), err) @@ -250,15 +255,15 @@ func (suite *GlideTestSuite) TestIncrCommands_existingKey() { res1, err := client.Incr(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(11), res1) + assert.Equal(suite.T(), int64(11), res1.Value()) res2, err := client.IncrBy(key, 10) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(21), res2) + assert.Equal(suite.T(), int64(21), res2.Value()) res3, err := client.IncrByFloat(key, float64(10.1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), float64(31.1), res3) + assert.Equal(suite.T(), float64(31.1), res3.Value()) }) } @@ -267,17 +272,17 @@ func (suite *GlideTestSuite) TestIncrCommands_nonExistingKey() { key1 := uuid.New().String() res1, err := client.Incr(key1) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(1), res1) + assert.Equal(suite.T(), int64(1), res1.Value()) key2 := uuid.New().String() res2, err := client.IncrBy(key2, 10) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(10), res2) + assert.Equal(suite.T(), int64(10), res2.Value()) key3 := uuid.New().String() res3, err := client.IncrByFloat(key3, float64(10.1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), float64(10.1), res3) + assert.Equal(suite.T(), float64(10.1), res3.Value()) }) } @@ -287,17 +292,17 @@ func (suite *GlideTestSuite) TestIncrCommands_TypeError() { suite.verifyOK(client.Set(key, "stringValue")) res1, err := client.Incr(key) - assert.Equal(suite.T(), int64(0), res1) + assert.Equal(suite.T(), int64(0), res1.Value()) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) res2, err := client.IncrBy(key, 10) - assert.Equal(suite.T(), int64(0), res2) + assert.Equal(suite.T(), int64(0), res2.Value()) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) res3, err := client.IncrByFloat(key, float64(10.1)) - assert.Equal(suite.T(), float64(0), res3) + assert.Equal(suite.T(), float64(0), res3.Value()) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -310,11 +315,11 @@ func (suite *GlideTestSuite) TestDecrCommands_existingKey() { res1, err := client.Decr(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(9), res1) + assert.Equal(suite.T(), int64(9), res1.Value()) res2, err := client.DecrBy(key, 10) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(-1), res2) + assert.Equal(suite.T(), int64(-1), res2.Value()) }) } @@ -323,12 +328,12 @@ func (suite *GlideTestSuite) TestDecrCommands_nonExistingKey() { key1 := uuid.New().String() res1, err := client.Decr(key1) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(-1), res1) + assert.Equal(suite.T(), int64(-1), res1.Value()) key2 := uuid.New().String() res2, err := client.DecrBy(key2, 10) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(-10), res2) + assert.Equal(suite.T(), int64(-10), res2.Value()) }) } @@ -340,7 +345,7 @@ func (suite *GlideTestSuite) TestStrlen_existingKey() { res, err := client.Strlen(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(len(value)), res) + assert.Equal(suite.T(), int64(len(value)), res.Value()) }) } @@ -349,7 +354,7 @@ func (suite *GlideTestSuite) TestStrlen_nonExistingKey() { key := uuid.New().String() res, err := client.Strlen(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res) + assert.Equal(suite.T(), int64(0), res.Value()) }) } @@ -358,24 +363,24 @@ func (suite *GlideTestSuite) TestSetRange_existingAndNonExistingKeys() { key := uuid.New().String() res, err := client.SetRange(key, 0, "Dummy string") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(12), res) + assert.Equal(suite.T(), int64(12), res.Value()) res, err = client.SetRange(key, 6, "values") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(12), res) + assert.Equal(suite.T(), int64(12), res.Value()) res1, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy values", res1) + assert.Equal(suite.T(), "Dummy values", res1.Value()) res, err = client.SetRange(key, 15, "test") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(19), res) + assert.Equal(suite.T(), int64(19), res.Value()) res1, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy values\x00\x00\x00test", res1) + assert.Equal(suite.T(), "Dummy values\x00\x00\x00test", res1.Value()) res, err = client.SetRange(key, math.MaxInt32, "test") - assert.Equal(suite.T(), int64(0), res) + assert.Equal(suite.T(), int64(0), res.Value()) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -387,21 +392,21 @@ func (suite *GlideTestSuite) TestSetRange_existingAndNonExistingKeys_binaryStrin key := uuid.New().String() res, err := client.SetRange(key, 0, nonUtf8String) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(14), res) + assert.Equal(suite.T(), int64(14), res.Value()) res, err = client.SetRange(key, 6, "values ") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(14), res) + assert.Equal(suite.T(), int64(14), res.Value()) res1, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy values g", res1) + assert.Equal(suite.T(), "Dummy values g", res1.Value()) res, err = client.SetRange(key, 15, "test") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(19), res) + assert.Equal(suite.T(), int64(19), res.Value()) res1, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy values g\x00test", res1) + assert.Equal(suite.T(), "Dummy values g\x00test", res1.Value()) }) } @@ -412,24 +417,24 @@ func (suite *GlideTestSuite) TestGetRange_existingAndNonExistingKeys() { res, err := client.GetRange(key, 0, 4) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy", res) + assert.Equal(suite.T(), "Dummy", res.Value()) res, err = client.GetRange(key, -6, -1) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "string", res) + assert.Equal(suite.T(), "string", res.Value()) res, err = client.GetRange(key, -1, -6) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res) + assert.Equal(suite.T(), "", res.Value()) res, err = client.GetRange(key, 15, 16) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res) + assert.Equal(suite.T(), "", res.Value()) nonExistingKey := uuid.New().String() res, err = client.GetRange(nonExistingKey, 0, 5) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res) + assert.Equal(suite.T(), "", res.Value()) }) } @@ -441,7 +446,7 @@ func (suite *GlideTestSuite) TestGetRange_binaryString() { res, err := client.GetRange(key, 4, 6) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "y \xFF", res) + assert.Equal(suite.T(), "y \xFF", res.Value()) }) } @@ -453,17 +458,17 @@ func (suite *GlideTestSuite) TestAppend_existingAndNonExistingKeys() { res, err := client.Append(key, value1) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(len(value1)), res) + assert.Equal(suite.T(), int64(len(value1)), res.Value()) res1, err := client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), value1, res1) + assert.Equal(suite.T(), value1, res1.Value()) res, err = client.Append(key, value2) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(len(value1)+len(value2)), res) + assert.Equal(suite.T(), int64(len(value1)+len(value2)), res.Value()) res1, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), value1+value2, res1) + assert.Equal(suite.T(), value1+value2, res1.Value()) }) } @@ -474,14 +479,14 @@ func (suite *GlideTestSuite) TestLCS_existingAndNonExistingKeys() { res, err := client.LCS(key1, key2) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res) + assert.Equal(suite.T(), "", res.Value()) suite.verifyOK(client.Set(key1, "Dummy string")) suite.verifyOK(client.Set(key2, "Dummy value")) res, err = client.LCS(key1, key2) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "Dummy ", res) + assert.Equal(suite.T(), "Dummy ", res.Value()) }) } @@ -493,11 +498,11 @@ func (suite *GlideTestSuite) TestGetDel_ExistingKey() { suite.verifyOK(client.Set(key, value)) result, err := client.GetDel(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), value, result) + assert.Equal(suite.T(), value, result.Value()) result, err = client.Get(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -508,7 +513,7 @@ func (suite *GlideTestSuite) TestGetDel_NonExistingKey() { result, err := client.GetDel(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) }) } @@ -517,7 +522,7 @@ func (suite *GlideTestSuite) TestGetDel_EmptyKey() { result, err := client.GetDel("") assert.NotNil(suite.T(), err) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), "", result.Value()) assert.Equal(suite.T(), "key is required", err.Error()) }) } @@ -529,11 +534,11 @@ func (suite *GlideTestSuite) TestHSet_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res2) + assert.Equal(suite.T(), int64(0), res2.Value()) }) } @@ -547,11 +552,19 @@ func (suite *GlideTestSuite) TestHSet_byteString() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HGetAll(key) + key1 := api.CreateStringResult(string([]byte{0xFF, 0x00, 0xAA})) + value1 := api.CreateStringResult(string([]byte{0xDE, 0xAD, 0xBE, 0xEF})) + key2 := api.CreateStringResult(string([]byte{0x01, 0x02, 0x03, 0xFE})) + value2 := api.CreateStringResult(string([]byte{0xCA, 0xFE, 0xBA, 0xBE})) + fieldsResult := map[api.Result[string]]api.Result[string]{ + key1: value1, + key2: value2, + } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), fields, res2) + assert.Equal(suite.T(), fieldsResult, res2) }) } @@ -562,16 +575,16 @@ func (suite *GlideTestSuite) TestHSet_WithAddNewField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res2) + assert.Equal(suite.T(), int64(0), res2.Value()) fields["field3"] = "value3" res3, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(1), res3) + assert.Equal(suite.T(), int64(1), res3.Value()) }) } @@ -582,11 +595,11 @@ func (suite *GlideTestSuite) TestHGet_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HGet(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "value1", res2) + assert.Equal(suite.T(), "value1", res2.Value()) }) } @@ -596,7 +609,7 @@ func (suite *GlideTestSuite) TestHGet_WithNotExistingKey() { res1, err := client.HGet(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res1) + assert.Equal(suite.T(), api.CreateNilStringResult(), res1) }) } @@ -607,11 +620,11 @@ func (suite *GlideTestSuite) TestHGet_WithNotExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HGet(key, "foo") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "", res2) + assert.Equal(suite.T(), api.CreateNilStringResult(), res2) }) } @@ -622,11 +635,16 @@ func (suite *GlideTestSuite) TestHGetAll_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) + field1 := api.CreateStringResult("field1") + value1 := api.CreateStringResult("value1") + field2 := api.CreateStringResult("field2") + value2 := api.CreateStringResult("value2") + fieldsResult := map[api.Result[string]]api.Result[string]{field1: value1, field2: value2} res2, err := client.HGetAll(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), fields, res2) + assert.Equal(suite.T(), fieldsResult, res2) }) } @@ -647,11 +665,14 @@ func (suite *GlideTestSuite) TestHMGet() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HMGet(key, []string{"field1", "field2", "field3"}) + value1 := api.CreateStringResult("value1") + value2 := api.CreateStringResult("value2") + nullValue := api.CreateNilStringResult() assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []string{"value1", "value2", ""}, res2) + assert.Equal(suite.T(), []api.Result[string]{value1, value2, nullValue}, res2) }) } @@ -660,8 +681,9 @@ func (suite *GlideTestSuite) TestHMGet_WithNotExistingKey() { key := uuid.NewString() res, err := client.HMGet(key, []string{"field1", "field2", "field3"}) + nullValue := api.CreateNilStringResult() assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []string{"", "", ""}, res) + assert.Equal(suite.T(), []api.Result[string]{nullValue, nullValue, nullValue}, res) }) } @@ -672,11 +694,12 @@ func (suite *GlideTestSuite) TestHMGet_WithNotExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HMGet(key, []string{"field3", "field4"}) + nullValue := api.CreateNilStringResult() assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []string{"", ""}, res2) + assert.Equal(suite.T(), []api.Result[string]{nullValue, nullValue}, res2) }) } @@ -687,11 +710,11 @@ func (suite *GlideTestSuite) TestHSetNX_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HSetNX(key, "field1", "value1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), false, res2) + assert.Equal(suite.T(), false, res2.Value()) }) } @@ -701,11 +724,13 @@ func (suite *GlideTestSuite) TestHSetNX_WithNotExistingKey() { res1, err := client.HSetNX(key, "field1", "value1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), true, res1) + assert.Equal(suite.T(), true, res1.Value()) res2, err := client.HGetAll(key) + field1 := api.CreateStringResult("field1") + value1 := api.CreateStringResult("value1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), map[string]string{"field1": "value1"}, res2) + assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{field1: value1}, res2) }) } @@ -716,11 +741,11 @@ func (suite *GlideTestSuite) TestHSetNX_WithExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HSetNX(key, "field1", "value1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), false, res2) + assert.Equal(suite.T(), false, res2.Value()) }) } @@ -731,11 +756,11 @@ func (suite *GlideTestSuite) TestHDel() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HDel(key, []string{"field1", "field2"}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res2) + assert.Equal(suite.T(), int64(2), res2.Value()) res3, err := client.HGetAll(key) assert.Nil(suite.T(), err) @@ -743,7 +768,7 @@ func (suite *GlideTestSuite) TestHDel() { res4, err := client.HDel(key, []string{"field1", "field2"}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res4) + assert.Equal(suite.T(), int64(0), res4.Value()) }) } @@ -752,7 +777,7 @@ func (suite *GlideTestSuite) TestHDel_WithNotExistingKey() { key := uuid.NewString() res, err := client.HDel(key, []string{"field1", "field2"}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res) + assert.Equal(suite.T(), int64(0), res.Value()) }) } @@ -763,11 +788,11 @@ func (suite *GlideTestSuite) TestHDel_WithNotExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HDel(key, []string{"field3", "field4"}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res2) + assert.Equal(suite.T(), int64(0), res2.Value()) }) } @@ -778,11 +803,11 @@ func (suite *GlideTestSuite) TestHLen() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HLen(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res2) + assert.Equal(suite.T(), int64(2), res2.Value()) }) } @@ -792,7 +817,7 @@ func (suite *GlideTestSuite) TestHLen_WithNotExistingKey() { res, err := client.HLen(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res) + assert.Equal(suite.T(), int64(0), res.Value()) }) } @@ -803,12 +828,14 @@ func (suite *GlideTestSuite) TestHVals_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HVals(key) + value1 := api.CreateStringResult("value1") + value2 := api.CreateStringResult("value2") assert.Nil(suite.T(), err) - assert.Contains(suite.T(), res2, "value1") - assert.Contains(suite.T(), res2, "value2") + assert.Contains(suite.T(), res2, value1) + assert.Contains(suite.T(), res2, value2) }) } @@ -829,11 +856,11 @@ func (suite *GlideTestSuite) TestHExists_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HExists(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), true, res2) + assert.Equal(suite.T(), true, res2.Value()) }) } @@ -843,7 +870,7 @@ func (suite *GlideTestSuite) TestHExists_WithNotExistingKey() { res, err := client.HExists(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), false, res) + assert.Equal(suite.T(), false, res.Value()) }) } @@ -854,11 +881,11 @@ func (suite *GlideTestSuite) TestHExists_WithNotExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HExists(key, "field3") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), false, res2) + assert.Equal(suite.T(), false, res2.Value()) }) } @@ -869,12 +896,14 @@ func (suite *GlideTestSuite) TestHKeys_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HKeys(key) + field1 := api.CreateStringResult("field1") + field2 := api.CreateStringResult("field2") assert.Nil(suite.T(), err) - assert.Contains(suite.T(), res2, "field1") - assert.Contains(suite.T(), res2, "field2") + assert.Contains(suite.T(), res2, field1) + assert.Contains(suite.T(), res2, field2) }) } @@ -895,11 +924,11 @@ func (suite *GlideTestSuite) TestHStrLen_WithExistingKey() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HStrLen(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(6), res2) + assert.Equal(suite.T(), int64(6), res2.Value()) }) } @@ -909,7 +938,7 @@ func (suite *GlideTestSuite) TestHStrLen_WithNotExistingKey() { res, err := client.HStrLen(key, "field1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res) + assert.Equal(suite.T(), int64(0), res.Value()) }) } @@ -920,10 +949,10 @@ func (suite *GlideTestSuite) TestHStrLen_WithNotExistingField() { res1, err := client.HSet(key, fields) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(2), res1) + assert.Equal(suite.T(), int64(2), res1.Value()) res2, err := client.HStrLen(key, "field3") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), res2) + assert.Equal(suite.T(), int64(0), res2.Value()) }) } diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index f48cd5dd97..4186c6b184 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -80,10 +80,14 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_multipleArgs() { suite.T().Skip("This feature is added in version 7") } configMap := map[string]string{"timeout": "1000", "maxmemory": "1GB"} - resultConfigMap := map[string]string{"timeout": "1000", "maxmemory": "1073741824"} + key1 := api.CreateStringResult("timeout") + value1 := api.CreateStringResult("1000") + key2 := api.CreateStringResult("maxmemory") + value2 := api.CreateStringResult("1073741824") + resultConfigMap := map[api.Result[string]]api.Result[string]{key1: value1, key2: value2} result, err := client.ConfigSet(configMap) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), result, "OK") + assert.Equal(suite.T(), "OK", result.Value()) result2, err := client.ConfigGet([]string{"timeout", "maxmemory"}) assert.Nil(suite.T(), err) @@ -96,7 +100,7 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_noArgs() { configMap := map[string]string{} result, err := client.ConfigSet(configMap) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), api.CreateNilStringResult(), result) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) @@ -112,11 +116,11 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_invalidArgs() { configMap := map[string]string{"time": "1000"} result, err := client.ConfigSet(configMap) - assert.Equal(suite.T(), "", result) + assert.Equal(suite.T(), api.CreateNilStringResult(), result) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) result2, err := client.ConfigGet([]string{"time"}) - assert.Equal(suite.T(), map[string]string{}, result2) + assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{}, result2) assert.Nil(suite.T(), err) } diff --git a/go/src/lib.rs b/go/src/lib.rs index 7c4ae6322b..57e6e3a44a 100644 --- a/go/src/lib.rs +++ b/go/src/lib.rs @@ -31,6 +31,7 @@ use tokio::runtime::Runtime; #[derive(Derivative)] #[derivative(Debug, Default)] pub struct CommandResponse { + response_type: ResponseType, int_value: c_long, float_value: c_double, bool_value: bool, @@ -58,6 +59,19 @@ pub struct CommandResponse { map_value: *mut CommandResponse, } +#[repr(C)] +#[derive(Debug, Default)] +pub enum ResponseType { + #[default] + Null = 0, + Int = 1, + Float = 2, + Bool = 3, + String = 4, + Array = 5, + Map = 6, +} + /// Success callback that is called when a command succeeds. /// /// The success callback needs to copy the given string synchronously, since it will be dropped by Rust once the callback returns. The callback should be offloaded to a separate thread in order not to exhaust the client's thread pool. @@ -219,14 +233,37 @@ pub unsafe extern "C" fn free_connection_response( } } +/// Provides the string mapping for the ResponseType enum. +#[no_mangle] +pub extern "C" fn get_response_type_string(response_type: ResponseType) -> *mut c_char { + let s = match response_type { + ResponseType::Null => "Null", + ResponseType::Int => "Int", + ResponseType::Float => "Float", + ResponseType::Bool => "Bool", + ResponseType::String => "String", + ResponseType::Array => "Array", + ResponseType::Map => "Map", + }; + let c_str = CString::new(s).unwrap_or_default(); + c_str.into_raw() +} + +/// Deallocates a string generated via get_response_type_string. +/// +/// # Safety +/// free_response_type_string can be called only once per response_string. +#[no_mangle] +pub extern "C" fn free_response_type_string(response_string: *mut c_char) { + if !response_string.is_null() { + drop(unsafe { CString::from_raw(response_string as *mut c_char) }); + } +} + /// Deallocates a `CommandResponse`. /// /// This function also frees the contained string_value and array_value. If the string_value and array_value are null pointers, the function returns and only the `CommandResponse` is freed. /// -/// # Panics -/// -/// This function panics when called with a null `CommandResponse` pointer. -/// /// # Safety /// /// * `free_command_response` can only be called once per `CommandResponse`. Calling it twice is undefined behavior, since the address will be freed twice. @@ -234,9 +271,10 @@ pub unsafe extern "C" fn free_connection_response( /// * `command_response_ptr` must be valid until `free_command_response` is called. #[no_mangle] pub unsafe extern "C" fn free_command_response(command_response_ptr: *mut CommandResponse) { - assert!(!command_response_ptr.is_null()); - let command_response = unsafe { Box::from_raw(command_response_ptr) }; - free_command_response_elements(*command_response); + if !command_response_ptr.is_null() { + let command_response = unsafe { Box::from_raw(command_response_ptr) }; + free_command_response_elements(*command_response); + } } /// Frees the nested elements of `CommandResponse`. @@ -327,48 +365,55 @@ fn convert_vec_to_pointer(mut vec: Vec) -> (*mut T, c_long) { } /// TODO: Avoid the use of expect and unwrap in the code and add a common error handling mechanism. -fn valkey_value_to_command_response(value: Value) -> RedisResult> { +fn valkey_value_to_command_response(value: Value) -> RedisResult { let mut command_response = CommandResponse::default(); - let result: RedisResult> = match value { - Value::Nil => Ok(None), + let result: RedisResult = match value { + Value::Nil => Ok(command_response), Value::SimpleString(text) => { let vec: Vec = text.into_bytes(); let (vec_ptr, len) = convert_vec_to_pointer(vec); command_response.string_value = vec_ptr as *mut c_char; command_response.string_value_len = len; - Ok(Some(command_response)) + command_response.response_type = ResponseType::String; + Ok(command_response) } Value::BulkString(text) => { let (vec_ptr, len) = convert_vec_to_pointer(text); command_response.string_value = vec_ptr as *mut c_char; command_response.string_value_len = len; - Ok(Some(command_response)) + command_response.response_type = ResponseType::String; + Ok(command_response) } Value::VerbatimString { format: _, text } => { let vec: Vec = text.into_bytes(); let (vec_ptr, len) = convert_vec_to_pointer(vec); command_response.string_value = vec_ptr as *mut c_char; command_response.string_value_len = len; - Ok(Some(command_response)) + command_response.response_type = ResponseType::String; + Ok(command_response) } Value::Okay => { let vec: Vec = String::from("OK").into_bytes(); let (vec_ptr, len) = convert_vec_to_pointer(vec); command_response.string_value = vec_ptr as *mut c_char; command_response.string_value_len = len; - Ok(Some(command_response)) + command_response.response_type = ResponseType::String; + Ok(command_response) } Value::Int(num) => { command_response.int_value = num; - Ok(Some(command_response)) + command_response.response_type = ResponseType::Int; + Ok(command_response) } Value::Double(num) => { command_response.float_value = num; - Ok(Some(command_response)) + command_response.response_type = ResponseType::Float; + Ok(command_response) } Value::Boolean(boolean) => { command_response.bool_value = boolean; - Ok(Some(command_response)) + command_response.response_type = ResponseType::Bool; + Ok(command_response) } Value::Array(array) => { let vec: Vec = array @@ -376,13 +421,13 @@ fn valkey_value_to_command_response(value: Value) -> RedisResult { let result: Vec = map @@ -391,13 +436,11 @@ fn valkey_value_to_command_response(value: Value) -> RedisResult RedisResult todo!(), @@ -467,12 +511,11 @@ pub unsafe extern "C" fn command( } }; - let result: RedisResult> = valkey_value_to_command_response(value); + let result: RedisResult = valkey_value_to_command_response(value); unsafe { match result { - Ok(None) => (client_adapter.success_callback)(channel, std::ptr::null()), - Ok(Some(message)) => { + Ok(message) => { (client_adapter.success_callback)(channel, Box::into_raw(Box::new(message))) } Err(err) => {