
I have validated that these settings closely match the existing coding style with one major exception on `BreakBeforeBraces`, which will be `Attach` going forward. The mixed `BreakBeforeBraces` styles in the current codebase are hard to imitate and also very odd IMHO - see below ``` if (a == 1) { /*Attach */ } ``` ``` if (a == 1 || b == 2) { /* Why? */ } ``` Please do NOT merge just yet. Will add the github action next once the style is reviewed/approved. --------- Signed-off-by: Ping Xie <pingxie@google.com>
566 lines
21 KiB
C
566 lines
21 KiB
C
/*
|
|
* Copyright (c) 2009-2021, Redis Labs Ltd.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
* to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "server.h"
|
|
#include "call_reply.h"
|
|
|
|
#define REPLY_FLAG_ROOT (1 << 0)
|
|
#define REPLY_FLAG_PARSED (1 << 1)
|
|
#define REPLY_FLAG_RESP3 (1 << 2)
|
|
|
|
/* --------------------------------------------------------
|
|
* An opaque struct used to parse a RESP protocol reply and
|
|
* represent it. Used when parsing replies such as in RM_Call
|
|
* or Lua scripts.
|
|
* -------------------------------------------------------- */
|
|
struct CallReply {
|
|
void *private_data;
|
|
sds original_proto; /* Available only for root reply. */
|
|
const char *proto;
|
|
size_t proto_len;
|
|
int type; /* REPLY_... */
|
|
int flags; /* REPLY_FLAG... */
|
|
size_t len; /* Length of a string, or the number elements in an array. */
|
|
union {
|
|
const char *str; /* String pointer for string and error replies. This
|
|
* does not need to be freed, always points inside
|
|
* a reply->proto buffer of the reply object or, in
|
|
* case of array elements, of parent reply objects. */
|
|
struct {
|
|
const char *str;
|
|
const char *format;
|
|
} verbatim_str; /* Reply value for verbatim string */
|
|
long long ll; /* Reply value for integer reply. */
|
|
double d; /* Reply value for double reply. */
|
|
struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */
|
|
} val;
|
|
list *deferred_error_list; /* list of errors in sds form or NULL */
|
|
struct CallReply *attribute; /* attribute reply, NULL if not exists */
|
|
};
|
|
|
|
static void callReplySetSharedData(CallReply *rep, int type, const char *proto, size_t proto_len, int extra_flags) {
|
|
rep->type = type;
|
|
rep->proto = proto;
|
|
rep->proto_len = proto_len;
|
|
rep->flags |= extra_flags;
|
|
}
|
|
|
|
static void callReplyNull(void *ctx, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_NULL, proto, proto_len, REPLY_FLAG_RESP3);
|
|
}
|
|
|
|
static void callReplyNullBulkString(void *ctx, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_NULL, proto, proto_len, 0);
|
|
}
|
|
|
|
static void callReplyNullArray(void *ctx, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_NULL, proto, proto_len, 0);
|
|
}
|
|
|
|
static void callReplyBulkString(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_STRING, proto, proto_len, 0);
|
|
rep->len = len;
|
|
rep->val.str = str;
|
|
}
|
|
|
|
static void callReplyError(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_ERROR, proto, proto_len, 0);
|
|
rep->len = len;
|
|
rep->val.str = str;
|
|
}
|
|
|
|
static void callReplySimpleStr(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_STRING, proto, proto_len, 0);
|
|
rep->len = len;
|
|
rep->val.str = str;
|
|
}
|
|
|
|
static void callReplyLong(void *ctx, long long val, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_INTEGER, proto, proto_len, 0);
|
|
rep->val.ll = val;
|
|
}
|
|
|
|
static void callReplyDouble(void *ctx, double val, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_DOUBLE, proto, proto_len, REPLY_FLAG_RESP3);
|
|
rep->val.d = val;
|
|
}
|
|
|
|
static void callReplyVerbatimString(void *ctx,
|
|
const char *format,
|
|
const char *str,
|
|
size_t len,
|
|
const char *proto,
|
|
size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_VERBATIM_STRING, proto, proto_len, REPLY_FLAG_RESP3);
|
|
rep->len = len;
|
|
rep->val.verbatim_str.str = str;
|
|
rep->val.verbatim_str.format = format;
|
|
}
|
|
|
|
static void callReplyBigNumber(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_BIG_NUMBER, proto, proto_len, REPLY_FLAG_RESP3);
|
|
rep->len = len;
|
|
rep->val.str = str;
|
|
}
|
|
|
|
static void callReplyBool(void *ctx, int val, const char *proto, size_t proto_len) {
|
|
CallReply *rep = ctx;
|
|
callReplySetSharedData(rep, VALKEYMODULE_REPLY_BOOL, proto, proto_len, REPLY_FLAG_RESP3);
|
|
rep->val.ll = val;
|
|
}
|
|
|
|
static void callReplyParseCollection(ReplyParser *parser,
|
|
CallReply *rep,
|
|
size_t len,
|
|
const char *proto,
|
|
size_t elements_per_entry) {
|
|
rep->len = len;
|
|
rep->val.array = zcalloc(elements_per_entry * len * sizeof(CallReply));
|
|
for (size_t i = 0; i < len * elements_per_entry; i += elements_per_entry) {
|
|
for (size_t j = 0; j < elements_per_entry; ++j) {
|
|
rep->val.array[i + j].private_data = rep->private_data;
|
|
parseReply(parser, rep->val.array + i + j);
|
|
rep->val.array[i + j].flags |= REPLY_FLAG_PARSED;
|
|
if (rep->val.array[i + j].flags & REPLY_FLAG_RESP3) {
|
|
/* If one of the sub-replies is RESP3, then the current reply is also RESP3. */
|
|
rep->flags |= REPLY_FLAG_RESP3;
|
|
}
|
|
}
|
|
}
|
|
rep->proto = proto;
|
|
rep->proto_len = parser->curr_location - proto;
|
|
}
|
|
|
|
static void callReplyAttribute(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
|
CallReply *rep = ctx;
|
|
rep->attribute = zcalloc(sizeof(CallReply));
|
|
|
|
/* Continue parsing the attribute reply */
|
|
rep->attribute->len = len;
|
|
rep->attribute->type = VALKEYMODULE_REPLY_ATTRIBUTE;
|
|
callReplyParseCollection(parser, rep->attribute, len, proto, 2);
|
|
rep->attribute->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_RESP3;
|
|
rep->attribute->private_data = rep->private_data;
|
|
|
|
/* Continue parsing the reply */
|
|
parseReply(parser, rep);
|
|
|
|
/* In this case we need to fix the proto address and len, it should start from the attribute */
|
|
rep->proto = proto;
|
|
rep->proto_len = parser->curr_location - proto;
|
|
rep->flags |= REPLY_FLAG_RESP3;
|
|
}
|
|
|
|
static void callReplyArray(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
|
CallReply *rep = ctx;
|
|
rep->type = VALKEYMODULE_REPLY_ARRAY;
|
|
callReplyParseCollection(parser, rep, len, proto, 1);
|
|
}
|
|
|
|
static void callReplySet(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
|
CallReply *rep = ctx;
|
|
rep->type = VALKEYMODULE_REPLY_SET;
|
|
callReplyParseCollection(parser, rep, len, proto, 1);
|
|
rep->flags |= REPLY_FLAG_RESP3;
|
|
}
|
|
|
|
static void callReplyMap(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
|
CallReply *rep = ctx;
|
|
rep->type = VALKEYMODULE_REPLY_MAP;
|
|
callReplyParseCollection(parser, rep, len, proto, 2);
|
|
rep->flags |= REPLY_FLAG_RESP3;
|
|
}
|
|
|
|
static void callReplyParseError(void *ctx) {
|
|
CallReply *rep = ctx;
|
|
rep->type = VALKEYMODULE_REPLY_UNKNOWN;
|
|
}
|
|
|
|
/* Recursively free the current call reply and its sub-replies. */
|
|
static void freeCallReplyInternal(CallReply *rep) {
|
|
if (rep->type == VALKEYMODULE_REPLY_ARRAY || rep->type == VALKEYMODULE_REPLY_SET) {
|
|
for (size_t i = 0; i < rep->len; ++i) {
|
|
freeCallReplyInternal(rep->val.array + i);
|
|
}
|
|
zfree(rep->val.array);
|
|
}
|
|
|
|
if (rep->type == VALKEYMODULE_REPLY_MAP || rep->type == VALKEYMODULE_REPLY_ATTRIBUTE) {
|
|
for (size_t i = 0; i < rep->len; ++i) {
|
|
freeCallReplyInternal(rep->val.array + i * 2);
|
|
freeCallReplyInternal(rep->val.array + i * 2 + 1);
|
|
}
|
|
zfree(rep->val.array);
|
|
}
|
|
|
|
if (rep->attribute) {
|
|
freeCallReplyInternal(rep->attribute);
|
|
zfree(rep->attribute);
|
|
}
|
|
}
|
|
|
|
/* Free the given call reply and its children (in case of nested reply) recursively.
|
|
* If private data was set when the CallReply was created it will not be freed, as it's
|
|
* the caller's responsibility to free it before calling freeCallReply(). */
|
|
void freeCallReply(CallReply *rep) {
|
|
if (!(rep->flags & REPLY_FLAG_ROOT)) {
|
|
return;
|
|
}
|
|
if (rep->flags & REPLY_FLAG_PARSED) {
|
|
if (rep->type == VALKEYMODULE_REPLY_PROMISE) {
|
|
zfree(rep);
|
|
return;
|
|
}
|
|
freeCallReplyInternal(rep);
|
|
}
|
|
sdsfree(rep->original_proto);
|
|
if (rep->deferred_error_list) listRelease(rep->deferred_error_list);
|
|
zfree(rep);
|
|
}
|
|
|
|
CallReply *callReplyCreatePromise(void *private_data) {
|
|
CallReply *res = zmalloc(sizeof(*res));
|
|
res->type = VALKEYMODULE_REPLY_PROMISE;
|
|
/* Mark the reply as parsed so there will be not attempt to parse
|
|
* it when calling reply API such as freeCallReply.
|
|
* Also mark the reply as root so freeCallReply will not ignore it. */
|
|
res->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_ROOT;
|
|
res->private_data = private_data;
|
|
return res;
|
|
}
|
|
|
|
static const ReplyParserCallbacks DefaultParserCallbacks = {
|
|
.null_callback = callReplyNull,
|
|
.bulk_string_callback = callReplyBulkString,
|
|
.null_bulk_string_callback = callReplyNullBulkString,
|
|
.null_array_callback = callReplyNullArray,
|
|
.error_callback = callReplyError,
|
|
.simple_str_callback = callReplySimpleStr,
|
|
.long_callback = callReplyLong,
|
|
.array_callback = callReplyArray,
|
|
.set_callback = callReplySet,
|
|
.map_callback = callReplyMap,
|
|
.double_callback = callReplyDouble,
|
|
.bool_callback = callReplyBool,
|
|
.big_number_callback = callReplyBigNumber,
|
|
.verbatim_string_callback = callReplyVerbatimString,
|
|
.attribute_callback = callReplyAttribute,
|
|
.error = callReplyParseError,
|
|
};
|
|
|
|
/* Parse the buffer located in rep->original_proto and update the CallReply
|
|
* structure to represent its contents. */
|
|
static void callReplyParse(CallReply *rep) {
|
|
if (rep->flags & REPLY_FLAG_PARSED) {
|
|
return;
|
|
}
|
|
|
|
ReplyParser parser = {.curr_location = rep->proto, .callbacks = DefaultParserCallbacks};
|
|
|
|
parseReply(&parser, rep);
|
|
rep->flags |= REPLY_FLAG_PARSED;
|
|
}
|
|
|
|
/* Return the call reply type (VALKEYMODULE_REPLY_...). */
|
|
int callReplyType(CallReply *rep) {
|
|
if (!rep) return VALKEYMODULE_REPLY_UNKNOWN;
|
|
callReplyParse(rep);
|
|
return rep->type;
|
|
}
|
|
|
|
/* Return reply string as buffer and len. Applicable to:
|
|
* - VALKEYMODULE_REPLY_STRING
|
|
* - VALKEYMODULE_REPLY_ERROR
|
|
*
|
|
* The return value is borrowed from CallReply, so it must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*
|
|
* The returned value is not NULL terminated and its length is returned by
|
|
* reference through len, which must not be NULL.
|
|
*/
|
|
const char *callReplyGetString(CallReply *rep, size_t *len) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_STRING && rep->type != VALKEYMODULE_REPLY_ERROR) return NULL;
|
|
if (len) *len = rep->len;
|
|
return rep->val.str;
|
|
}
|
|
|
|
/* Return a long long reply value. Applicable to:
|
|
* - VALKEYMODULE_REPLY_INTEGER
|
|
*/
|
|
long long callReplyGetLongLong(CallReply *rep) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_INTEGER) return LLONG_MIN;
|
|
return rep->val.ll;
|
|
}
|
|
|
|
/* Return a double reply value. Applicable to:
|
|
* - VALKEYMODULE_REPLY_DOUBLE
|
|
*/
|
|
double callReplyGetDouble(CallReply *rep) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_DOUBLE) return LLONG_MIN;
|
|
return rep->val.d;
|
|
}
|
|
|
|
/* Return a reply Boolean value. Applicable to:
|
|
* - VALKEYMODULE_REPLY_BOOL
|
|
*/
|
|
int callReplyGetBool(CallReply *rep) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_BOOL) return INT_MIN;
|
|
return rep->val.ll;
|
|
}
|
|
|
|
/* Return reply length. Applicable to:
|
|
* - VALKEYMODULE_REPLY_STRING
|
|
* - VALKEYMODULE_REPLY_ERROR
|
|
* - VALKEYMODULE_REPLY_ARRAY
|
|
* - VALKEYMODULE_REPLY_SET
|
|
* - VALKEYMODULE_REPLY_MAP
|
|
* - VALKEYMODULE_REPLY_ATTRIBUTE
|
|
*/
|
|
size_t callReplyGetLen(CallReply *rep) {
|
|
callReplyParse(rep);
|
|
switch (rep->type) {
|
|
case VALKEYMODULE_REPLY_STRING:
|
|
case VALKEYMODULE_REPLY_ERROR:
|
|
case VALKEYMODULE_REPLY_ARRAY:
|
|
case VALKEYMODULE_REPLY_SET:
|
|
case VALKEYMODULE_REPLY_MAP:
|
|
case VALKEYMODULE_REPLY_ATTRIBUTE: return rep->len;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static CallReply *callReplyGetCollectionElement(CallReply *rep, size_t idx, int elements_per_entry) {
|
|
if (idx >= rep->len * elements_per_entry) return NULL; // real len is rep->len * elements_per_entry
|
|
return rep->val.array + idx;
|
|
}
|
|
|
|
/* Return a reply array element at a given index. Applicable to:
|
|
* - VALKEYMODULE_REPLY_ARRAY
|
|
*
|
|
* The return value is borrowed from CallReply, so it must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
CallReply *callReplyGetArrayElement(CallReply *rep, size_t idx) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_ARRAY) return NULL;
|
|
return callReplyGetCollectionElement(rep, idx, 1);
|
|
}
|
|
|
|
/* Return a reply set element at a given index. Applicable to:
|
|
* - VALKEYMODULE_REPLY_SET
|
|
*
|
|
* The return value is borrowed from CallReply, so it must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
CallReply *callReplyGetSetElement(CallReply *rep, size_t idx) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_SET) return NULL;
|
|
return callReplyGetCollectionElement(rep, idx, 1);
|
|
}
|
|
|
|
static int callReplyGetMapElementInternal(CallReply *rep, size_t idx, CallReply **key, CallReply **val, int type) {
|
|
callReplyParse(rep);
|
|
if (rep->type != type) return C_ERR;
|
|
if (idx >= rep->len) return C_ERR;
|
|
if (key) *key = callReplyGetCollectionElement(rep, idx * 2, 2);
|
|
if (val) *val = callReplyGetCollectionElement(rep, idx * 2 + 1, 2);
|
|
return C_OK;
|
|
}
|
|
|
|
/* Retrieve a map reply key and value at a given index. Applicable to:
|
|
* - VALKEYMODULE_REPLY_MAP
|
|
*
|
|
* The key and value are returned by reference through key and val,
|
|
* which may also be NULL if not needed.
|
|
*
|
|
* Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out
|
|
* of range.
|
|
*
|
|
* The returned values are borrowed from CallReply, so they must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
int callReplyGetMapElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) {
|
|
return callReplyGetMapElementInternal(rep, idx, key, val, VALKEYMODULE_REPLY_MAP);
|
|
}
|
|
|
|
/* Return reply attribute, or NULL if it does not exist. Applicable to all replies.
|
|
*
|
|
* The returned values are borrowed from CallReply, so they must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
CallReply *callReplyGetAttribute(CallReply *rep) {
|
|
return rep->attribute;
|
|
}
|
|
|
|
/* Retrieve attribute reply key and value at a given index. Applicable to:
|
|
* - VALKEYMODULE_REPLY_ATTRIBUTE
|
|
*
|
|
* The key and value are returned by reference through key and val,
|
|
* which may also be NULL if not needed.
|
|
*
|
|
* Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out
|
|
* of range.
|
|
*
|
|
* The returned values are borrowed from CallReply, so they must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
int callReplyGetAttributeElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) {
|
|
return callReplyGetMapElementInternal(rep, idx, key, val, VALKEYMODULE_REPLY_MAP);
|
|
}
|
|
|
|
/* Return a big number reply value. Applicable to:
|
|
* - VALKEYMODULE_REPLY_BIG_NUMBER
|
|
*
|
|
* The returned values are borrowed from CallReply, so they must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*
|
|
* The return value is guaranteed to be a big number, as described in the RESP3
|
|
* protocol specifications.
|
|
*
|
|
* The returned value is not NULL terminated and its length is returned by
|
|
* reference through len, which must not be NULL.
|
|
*/
|
|
const char *callReplyGetBigNumber(CallReply *rep, size_t *len) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_BIG_NUMBER) return NULL;
|
|
*len = rep->len;
|
|
return rep->val.str;
|
|
}
|
|
|
|
/* Return a verbatim string reply value. Applicable to:
|
|
* - VALKEYMODULE_REPLY_VERBATIM_STRING
|
|
*
|
|
* If format is non-NULL, the verbatim reply format is also returned by value.
|
|
*
|
|
* The optional output argument can be given to get a verbatim reply
|
|
* format, or can be set NULL if not needed.
|
|
*
|
|
* The return value is borrowed from CallReply, so it must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*
|
|
* The returned value is not NULL terminated and its length is returned by
|
|
* reference through len, which must not be NULL.
|
|
*/
|
|
const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **format) {
|
|
callReplyParse(rep);
|
|
if (rep->type != VALKEYMODULE_REPLY_VERBATIM_STRING) return NULL;
|
|
*len = rep->len;
|
|
if (format) *format = rep->val.verbatim_str.format;
|
|
return rep->val.verbatim_str.str;
|
|
}
|
|
|
|
/* Return the current reply blob.
|
|
*
|
|
* The return value is borrowed from CallReply, so it must not be freed
|
|
* explicitly or used after CallReply itself is freed.
|
|
*/
|
|
const char *callReplyGetProto(CallReply *rep, size_t *proto_len) {
|
|
*proto_len = rep->proto_len;
|
|
return rep->proto;
|
|
}
|
|
|
|
/* Return CallReply private data, as set by the caller on callReplyCreate().
|
|
*/
|
|
void *callReplyGetPrivateData(CallReply *rep) {
|
|
return rep->private_data;
|
|
}
|
|
|
|
/* Return true if the reply or one of it sub-replies is RESP3 formatted. */
|
|
int callReplyIsResp3(CallReply *rep) {
|
|
return rep->flags & REPLY_FLAG_RESP3;
|
|
}
|
|
|
|
/* Returns a list of errors in sds form, or NULL. */
|
|
list *callReplyDeferredErrorList(CallReply *rep) {
|
|
return rep->deferred_error_list;
|
|
}
|
|
|
|
/* Create a new CallReply struct from the reply blob.
|
|
*
|
|
* The function will own the reply blob, so it must not be used or freed by
|
|
* the caller after passing it to this function.
|
|
*
|
|
* The reply blob will be freed when the returned CallReply struct is later
|
|
* freed using freeCallReply().
|
|
*
|
|
* The deferred_error_list is an optional list of errors that are present
|
|
* in the reply blob, if given, this function will take ownership on it.
|
|
*
|
|
* The private_data is optional and can later be accessed using
|
|
* callReplyGetPrivateData().
|
|
*
|
|
* NOTE: The parser used for parsing the reply and producing CallReply is
|
|
* designed to handle valid replies created by the server itself. IT IS NOT
|
|
* DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is
|
|
* unsafe.
|
|
*/
|
|
CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data) {
|
|
CallReply *res = zmalloc(sizeof(*res));
|
|
res->flags = REPLY_FLAG_ROOT;
|
|
res->original_proto = reply;
|
|
res->proto = reply;
|
|
res->proto_len = sdslen(reply);
|
|
res->private_data = private_data;
|
|
res->attribute = NULL;
|
|
res->deferred_error_list = deferred_error_list;
|
|
return res;
|
|
}
|
|
|
|
/* Create a new CallReply struct from the reply blob representing an error message.
|
|
* Automatically creating deferred_error_list and set a copy of the reply in it.
|
|
* Refer to callReplyCreate for detailed explanation.
|
|
* Reply string can come in one of two forms:
|
|
* 1. A protocol reply starting with "-CODE" and ending with "\r\n"
|
|
* 2. A plain string, in which case this function adds the protocol header and footer. */
|
|
CallReply *callReplyCreateError(sds reply, void *private_data) {
|
|
sds err_buff = reply;
|
|
if (err_buff[0] != '-') {
|
|
err_buff = sdscatfmt(sdsempty(), "-ERR %S\r\n", reply);
|
|
sdsfree(reply);
|
|
}
|
|
list *deferred_error_list = listCreate();
|
|
listSetFreeMethod(deferred_error_list, (void (*)(void *))sdsfree);
|
|
listAddNodeTail(deferred_error_list, sdsnew(err_buff));
|
|
return callReplyCreate(err_buff, deferred_error_list, private_data);
|
|
}
|