Module api support for RESP3 (#8521)
Add new Module APS for RESP3 responses: - RM_ReplyWithMap - RM_ReplyWithSet - RM_ReplyWithAttribute - RM_ReplySetMapLength - RM_ReplySetSetLength - RM_ReplySetAttributeLength - RM_ReplyWithBool Deprecate REDISMODULE_POSTPONED_ARRAY_LEN in favor of a generic REDISMODULE_POSTPONED_LEN Improve documentation Add tests Co-authored-by: Guy Benoish <guy.benoish@redislabs.com> Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
parent
4bd7748362
commit
bdbf5eedae
236
src/module.c
236
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);
|
||||
|
@ -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);
|
||||
|
178
tests/modules/reply.c
Normal file
178
tests/modules/reply.c
Normal file
@ -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;
|
||||
}
|
@ -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]}
|
||||
> -
|
||||
|
77
tests/unit/moduleapi/reply.tcl
Normal file
77
tests/unit/moduleapi/reply.tcl
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}} {
|
||||
|
Loading…
x
Reference in New Issue
Block a user