diff --git a/src/module.c b/src/module.c index cd58ecd42..51b4d8dd6 100644 --- a/src/module.c +++ b/src/module.c @@ -660,8 +660,8 @@ void moduleFreeContext(RedisModuleCtx *ctx) { ctx->postponed_arrays_count = 0; serverLog(LL_WARNING, "API misuse detected in module %s: " - "RedisModule_ReplyWithArray(REDISMODULE_POSTPONED_ARRAY_LEN) " - "not matched by the same number of RedisModule_SetReplyArrayLen() " + "RedisModule_ReplyWith*(REDISMODULE_POSTPONED_LEN) " + "not matched by the same number of RedisModule_SetReply*Len() " "calls.", ctx->module->name); } @@ -1448,6 +1448,18 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha * * if (... some condition ...) * return RedisModule_ReplyWithLongLong(ctx,mycount); + * + * ### Reply with collection functions + * + * After starting a collection reply, the module must make calls to other + * `ReplyWith*` style functions in order to emit the elements of the collection. + * Collection types include: Array, Map, Set and Attribute. + * + * When producing collections with a number of elements that is not known + * beforehand, the function can be called with a special flag + * REDISMODULE_POSTPONED_LEN (REDISMODULE_POSTPONED_ARRAY_LEN in the past), + * and the actual number of elements can be later set with RM_ReplySet*Length() + * call (which will set the latest "open" count if there are multiple ones). * -------------------------------------------------------------------------- */ /* Send an error about the number of arguments given to the command, @@ -1537,35 +1549,129 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) { return REDISMODULE_OK; } -/* Reply with an array type of 'len' elements. However 'len' other calls - * to `ReplyWith*` style functions must follow in order to emit the elements - * of the array. - * - * When producing arrays with a number of element that is not known beforehand - * the function can be called with the special count - * REDISMODULE_POSTPONED_ARRAY_LEN, and the actual number of elements can be - * later set with RedisModule_ReplySetArrayLength() (which will set the - * latest "open" count if there are multiple ones). - * - * The function always returns REDISMODULE_OK. */ -int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) { +#define COLLECTION_REPLY_ARRAY 1 +#define COLLECTION_REPLY_MAP 2 +#define COLLECTION_REPLY_SET 3 +#define COLLECTION_REPLY_ATTRIBUTE 4 + +int moduleReplyWithCollection(RedisModuleCtx *ctx, long len, int type) { client *c = moduleGetReplyClient(ctx); if (c == NULL) return REDISMODULE_OK; - if (len == REDISMODULE_POSTPONED_ARRAY_LEN) { + if (len == REDISMODULE_POSTPONED_LEN) { ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)* (ctx->postponed_arrays_count+1)); ctx->postponed_arrays[ctx->postponed_arrays_count] = addReplyDeferredLen(c); ctx->postponed_arrays_count++; + } else if (len == 0) { + switch (type) { + case COLLECTION_REPLY_ARRAY: + addReply(c, shared.emptyarray); + break; + case COLLECTION_REPLY_MAP: + addReply(c, shared.emptymap[c->resp]); + break; + case COLLECTION_REPLY_SET: + addReply(c, shared.emptyset[c->resp]); + break; + case COLLECTION_REPLY_ATTRIBUTE: + addReplyAttributeLen(c,len); + break; + default: + serverPanic("Invalid module empty reply type %d", type); } } else { - addReplyArrayLen(c,len); + switch (type) { + case COLLECTION_REPLY_ARRAY: + addReplyArrayLen(c,len); + break; + case COLLECTION_REPLY_MAP: + addReplyMapLen(c,len); + break; + case COLLECTION_REPLY_SET: + addReplySetLen(c,len); + break; + case COLLECTION_REPLY_ATTRIBUTE: + addReplyAttributeLen(c,len); + break; + default: + serverPanic("Invalid module reply type %d", type); + } } return REDISMODULE_OK; } -/* Reply to the client with a null array, simply null in RESP3 +/* Reply with an array type of 'len' elements. + * + * After starting an array reply, the module must make `len` calls to other + * `ReplyWith*` style functions in order to emit the elements of the array. + * See Reply APIs section for more details. + * + * Use RM_ReplySetArrayLength() to set deferred length. + * + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) { + return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ARRAY); +} + +/* Reply with a RESP3 Map type of 'len' pairs. + * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. + * + * After starting a map reply, the module must make `len*2` calls to other + * `ReplyWith*` style functions in order to emit the elements of the map. + * See Reply APIs section for more details. + * + * If the connected client is using RESP2, the reply will be converted to a flat + * array. + * + * Use RM_ReplySetMapLength() to set deferred length. + * + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithMap(RedisModuleCtx *ctx, long len) { + return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_MAP); +} + +/* Reply with a RESP3 Set type of 'len' elements. + * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. + * + * After starting a set reply, the module must make `len` calls to other + * `ReplyWith*` style functions in order to emit the elements of the set. + * See Reply APIs section for more details. + * + * If the connected client is using RESP2, the reply will be converted to an + * array type. + * + * Use RM_ReplySetSetLength() to set deferred length. + * + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithSet(RedisModuleCtx *ctx, long len) { + return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_SET); +} + + +/* Add attributes (metadata) to the reply. Should be done before adding the + * actual reply. see https://github.com/antirez/RESP3/blob/master/spec.md#attribute-type + * + * After starting an attributes reply, the module must make `len*2` calls to other + * `ReplyWith*` style functions in order to emit the elements of the attribtute map. + * See Reply APIs section for more details. + * + * Use RM_ReplySetAttributeLength() to set deferred length. + * + * Not supported by RESP2 and will return REDISMODULE_ERR, otherwise + * the function always returns REDISMODULE_OK. */ +int RM_ReplyWithAttribute(RedisModuleCtx *ctx, long len) { + if (ctx->client->resp == 2) return REDISMODULE_ERR; + + return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ATTRIBUTE); +} + +/* Reply to the client with a null array, simply null in RESP3, * null array in RESP2. * + * Note: In RESP3 there's no difference between Null reply and + * NullArray reply, so to prevent ambiguity it's better to avoid + * using this API and use RedisModule_ReplyWithNull instead. + * * The function always returns REDISMODULE_OK. */ int RM_ReplyWithNullArray(RedisModuleCtx *ctx) { client *c = moduleGetReplyClient(ctx); @@ -1584,8 +1690,42 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { return REDISMODULE_OK; } +void moduleReplySetCollectionLength(RedisModuleCtx *ctx, long len, int type) { + client *c = moduleGetReplyClient(ctx); + if (c == NULL) return; + if (ctx->postponed_arrays_count == 0) { + serverLog(LL_WARNING, + "API misuse detected in module %s: " + "RedisModule_ReplySet*Length() called without previous " + "RedisModule_ReplyWith*(ctx,REDISMODULE_POSTPONED_LEN) " + "call.", ctx->module->name); + return; + } + ctx->postponed_arrays_count--; + switch(type) { + case COLLECTION_REPLY_ARRAY: + setDeferredArrayLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len); + break; + case COLLECTION_REPLY_MAP: + setDeferredMapLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len); + break; + case COLLECTION_REPLY_SET: + setDeferredSetLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len); + break; + case COLLECTION_REPLY_ATTRIBUTE: + setDeferredAttributeLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len); + break; + default: + serverPanic("Invalid module reply type %d", type); + } + if (ctx->postponed_arrays_count == 0) { + zfree(ctx->postponed_arrays); + ctx->postponed_arrays = NULL; + } +} + /* When RedisModule_ReplyWithArray() is used with the argument - * REDISMODULE_POSTPONED_ARRAY_LEN, because we don't know beforehand the number + * REDISMODULE_POSTPONED_LEN, because we don't know beforehand the number * of items we are going to output as elements of the array, this function * will take care to set the array length. * @@ -1596,9 +1736,9 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { * For example in order to output an array like [1,[10,20,30]] we * could write: * - * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN); + * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); * RedisModule_ReplyWithLongLong(ctx,1); - * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN); + * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); * RedisModule_ReplyWithLongLong(ctx,10); * RedisModule_ReplyWithLongLong(ctx,20); * RedisModule_ReplyWithLongLong(ctx,30); @@ -1611,24 +1751,27 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { * that is not easy to calculate in advance the number of elements. */ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) { - client *c = moduleGetReplyClient(ctx); - if (c == NULL) return; - if (ctx->postponed_arrays_count == 0) { - serverLog(LL_WARNING, - "API misuse detected in module %s: " - "RedisModule_ReplySetArrayLength() called without previous " - "RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN) " - "call.", ctx->module->name); - return; - } - ctx->postponed_arrays_count--; - setDeferredArrayLen(c, - ctx->postponed_arrays[ctx->postponed_arrays_count], - len); - if (ctx->postponed_arrays_count == 0) { - zfree(ctx->postponed_arrays); - ctx->postponed_arrays = NULL; - } + moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ARRAY); +} + +/* Very similar to RedisModule_ReplySetArrayLength except `len` should + * exactly half of the number of `ReplyWith*` functions called in the + * context of the map. + * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */ +void RM_ReplySetMapLength(RedisModuleCtx *ctx, long len) { + moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_MAP); +} + +/* Very similar to RedisModule_ReplySetArrayLength + * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */ +void RM_ReplySetSetLength(RedisModuleCtx *ctx, long len) { + moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_SET); +} + +/* Very similar to RedisModule_ReplySetMapLength + * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */ +void RM_ReplySetAttributeLength(RedisModuleCtx *ctx, long len) { + moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ATTRIBUTE); } /* Reply with a bulk string, taking in input a C buffer pointer and length. @@ -1693,6 +1836,16 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) { return REDISMODULE_OK; } +/* Reply to the client with a boolean value. + * + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithBool(RedisModuleCtx *ctx, int b) { + client *c = moduleGetReplyClient(ctx); + if (c == NULL) return REDISMODULE_OK; + addReplyBool(c,b); + return REDISMODULE_OK; +} + /* Reply exactly what a Redis command returned us with RedisModule_Call(). * This function is useful when we use RedisModule_Call() in order to * execute some command, as we want to reply to the client exactly the @@ -9404,15 +9557,22 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(ReplyWithError); REGISTER_API(ReplyWithSimpleString); REGISTER_API(ReplyWithArray); + REGISTER_API(ReplyWithMap); + REGISTER_API(ReplyWithSet); + REGISTER_API(ReplyWithAttribute); REGISTER_API(ReplyWithNullArray); REGISTER_API(ReplyWithEmptyArray); REGISTER_API(ReplySetArrayLength); + REGISTER_API(ReplySetMapLength); + REGISTER_API(ReplySetSetLength); + REGISTER_API(ReplySetAttributeLength); REGISTER_API(ReplyWithString); REGISTER_API(ReplyWithEmptyString); REGISTER_API(ReplyWithVerbatimString); REGISTER_API(ReplyWithStringBuffer); REGISTER_API(ReplyWithCString); REGISTER_API(ReplyWithNull); + REGISTER_API(ReplyWithBool); REGISTER_API(ReplyWithCallReply); REGISTER_API(ReplyWithDouble); REGISTER_API(ReplyWithLongDouble); diff --git a/src/redismodule.h b/src/redismodule.h index 01d3cdfe5..df2e854fe 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -48,7 +48,8 @@ #define REDISMODULE_REPLY_NULL 4 /* Postponed array length. */ -#define REDISMODULE_POSTPONED_ARRAY_LEN -1 +#define REDISMODULE_POSTPONED_ARRAY_LEN -1 /* Deprecated, please use REDISMODULE_POSTPONED_LEN */ +#define REDISMODULE_POSTPONED_LEN -1 /* Expire */ #define REDISMODULE_NO_EXPIRE -1 @@ -623,15 +624,24 @@ REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithMap)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithSet)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithAttribute)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithPush)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetMapLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetSetLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetAttributeLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetPushLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithBool)(RedisModuleCtx *ctx, int b) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR; @@ -884,15 +894,24 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ReplyWithError); REDISMODULE_GET_API(ReplyWithSimpleString); REDISMODULE_GET_API(ReplyWithArray); + REDISMODULE_GET_API(ReplyWithMap); + REDISMODULE_GET_API(ReplyWithSet); + REDISMODULE_GET_API(ReplyWithAttribute); + REDISMODULE_GET_API(ReplyWithPush); REDISMODULE_GET_API(ReplyWithNullArray); REDISMODULE_GET_API(ReplyWithEmptyArray); REDISMODULE_GET_API(ReplySetArrayLength); + REDISMODULE_GET_API(ReplySetMapLength); + REDISMODULE_GET_API(ReplySetSetLength); + REDISMODULE_GET_API(ReplySetAttributeLength); + REDISMODULE_GET_API(ReplySetPushLength); REDISMODULE_GET_API(ReplyWithStringBuffer); REDISMODULE_GET_API(ReplyWithCString); REDISMODULE_GET_API(ReplyWithString); REDISMODULE_GET_API(ReplyWithEmptyString); REDISMODULE_GET_API(ReplyWithVerbatimString); REDISMODULE_GET_API(ReplyWithNull); + REDISMODULE_GET_API(ReplyWithBool); REDISMODULE_GET_API(ReplyWithCallReply); REDISMODULE_GET_API(ReplyWithDouble); REDISMODULE_GET_API(ReplyWithLongDouble); diff --git a/tests/modules/reply.c b/tests/modules/reply.c new file mode 100644 index 000000000..913fc72a8 --- /dev/null +++ b/tests/modules/reply.c @@ -0,0 +1,178 @@ +/* + * A module the tests RM_ReplyWith family of commands + */ + +#include "redismodule.h" + +int rw_string(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + return RedisModule_ReplyWithString(ctx, argv[1]); +} + +int rw_cstring(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + if (argc != 1) return RedisModule_WrongArity(ctx); + + return RedisModule_ReplyWithSimpleString(ctx, "A simple string"); +} + +int rw_int(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long long integer; + if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as an integer"); + + return RedisModule_ReplyWithLongLong(ctx, integer); +} + +int rw_double(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + double dbl; + if (RedisModule_StringToDouble(argv[1], &dbl) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double"); + + return RedisModule_ReplyWithDouble(ctx, dbl); +} + +int rw_longdouble(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long double longdbl; + if (RedisModule_StringToLongDouble(argv[1], &longdbl) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double"); + + return RedisModule_ReplyWithLongDouble(ctx, longdbl); +} + +int rw_array(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long long integer; + if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer"); + + RedisModule_ReplyWithArray(ctx, integer); + for (int i = 0; i < integer; ++i) { + RedisModule_ReplyWithLongLong(ctx, i); + } + + return REDISMODULE_OK; +} + +int rw_map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long long integer; + if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer"); + + RedisModule_ReplyWithMap(ctx, integer); + for (int i = 0; i < integer; ++i) { + RedisModule_ReplyWithLongLong(ctx, i); + RedisModule_ReplyWithDouble(ctx, i * 1.5); + } + + return REDISMODULE_OK; +} + +int rw_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long long integer; + if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer"); + + RedisModule_ReplyWithSet(ctx, integer); + for (int i = 0; i < integer; ++i) { + RedisModule_ReplyWithLongLong(ctx, i); + } + + return REDISMODULE_OK; +} + +int rw_attribute(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + long long integer; + if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer"); + + if (RedisModule_ReplyWithAttribute(ctx, integer) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx, "Attributes aren't supported by RESP 2"); + } + + for (int i = 0; i < integer; ++i) { + RedisModule_ReplyWithLongLong(ctx, i); + } + + RedisModule_ReplyWithSimpleString(ctx, "OK"); + return REDISMODULE_OK; +} + +int rw_bool(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + if (argc != 1) return RedisModule_WrongArity(ctx); + + RedisModule_ReplyWithArray(ctx, 2); + RedisModule_ReplyWithBool(ctx, 0); + return RedisModule_ReplyWithBool(ctx, 1); +} + +int rw_null(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + if (argc != 1) return RedisModule_WrongArity(ctx); + + return RedisModule_ReplyWithNull(ctx); +} + +int rw_error(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + if (argc != 1) return RedisModule_WrongArity(ctx); + + return RedisModule_ReplyWithError(ctx, "An error"); +} + +int rw_verbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 1) return RedisModule_WrongArity(ctx); + + return RedisModule_ReplyWithVerbatimString(ctx, argv[1]); +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + if (RedisModule_Init(ctx, "replywith", 1, REDISMODULE_APIVER_1) != REDISMODULE_OK) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"rw.string",rw_string,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.cstring",rw_cstring,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.int",rw_int,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.double",rw_double,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.longdouble",rw_longdouble,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.array",rw_array,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.map",rw_map,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.attribute",rw_attribute,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.set",rw_set,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.bool",rw_bool,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.null",rw_null,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.error",rw_error,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.verbatim",rw_verbatim,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index 978163e98..285cd364c 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -254,6 +254,12 @@ proc ::redis::redis_read_bool fd { return -code error "Bad protocol, '$v' as bool type" } +proc ::redis::redis_read_verbatim_str fd { + set v [redis_bulk_read $fd] + # strip the first 4 chars ("txt:") + return [string range $v 4 end] +} + proc ::redis::redis_read_reply {id fd} { if {$::redis::readraw($id)} { return [redis_read_line $fd] @@ -268,6 +274,7 @@ proc ::redis::redis_read_reply {id fd} { + {return [redis_read_line $fd]} , {return [expr {double([redis_read_line $fd])}]} # {return [redis_read_bool $fd]} + = {return [redis_read_verbatim_str $fd]} - {return -code error [redis_read_line $fd]} $ {return [redis_bulk_read $fd]} > - diff --git a/tests/unit/moduleapi/reply.tcl b/tests/unit/moduleapi/reply.tcl new file mode 100644 index 000000000..0c074b57f --- /dev/null +++ b/tests/unit/moduleapi/reply.tcl @@ -0,0 +1,77 @@ +set testmodule [file normalize tests/modules/reply.so] + +# test all with hello 2/3 + +start_server {tags {"modules"}} { + r module load $testmodule + + for {proto=2; proto<=3; incr proto} { + r hello $proto + + test {RM_ReplyWithString: an string reply} { + # RedisString + set string [r rw.string "Redis"] + assert_equal "Redis" $string + # C string + set string [r rw.cstring] + assert_equal "A simple string" $string + } + + test {RM_ReplyWithInt: an integer reply} { + assert_equal 42 [r rw.int 42] + } + + test {RM_ReplyWithDouble: a float reply} { + assert_equal 3.141 [r rw.double 3.141] + } + + test {RM_ReplyWithLongDouble: a float reply} { + assert_equal 3.141 [r rw.longdouble 3.141] + } + + test {RM_ReplyWithVerbatimString: a string reply} { + assert_equal "bla\nbla\nbla" [r rw.verbatim "bla\nbla\nbla"] + } + + test {RM_ReplyWithArray: an array reply} { + assert_equal {0 1 2 3 4} [r rw.array 5] + } + + test {RM_ReplyWithMap: an map reply} { + set res [r rw.map 3] + if {$proto == 2} { + assert_equal {0 0.0 1 1.5 2 3.0} $res + } else { + assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res + } + } + + test {RM_ReplyWithSet: an set reply} { + assert_equal {0 1 2} [r rw.set 3] + } + + test {RM_ReplyWithAttribute: an set reply} { + set res [r rw.attribute 3] + if {$proto == 2} { + catch {r rw.error} e + assert_match "Attributes aren't supported by RESP 2" $e + } else { + assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res + } + assert_equal "OK" $res + } + + test {RM_ReplyWithBool: a boolean reply} { + assert_equal {0 1} [r rw.bool] + } + + test {RM_ReplyWithNull: a NULL reply} { + assert_equal {} [r rw.null] + } + + test {RM_ReplyWithError: an error reply} { + catch {r rw.error} e + assert_match "An error" $e + } + } +} diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl index ed0fe6482..39c4c9953 100644 --- a/tests/unit/protocol.tcl +++ b/tests/unit/protocol.tcl @@ -190,6 +190,12 @@ start_server {tags {"protocol network"}} { assert_equal [r debug protocol false] 0 set _ {} } {} {needs:debug resp3} + + test "test verbatim str parsing" { + r hello 3 + r debug protocol verbatim + } "This is a verbatim\nstring" {needs:debug resp3} + } start_server {tags {"regression"}} {