Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/neuron/json/neu_json_rw.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ typedef struct {
char *node;
char *name;
char *desc;
char *sort_by;
bool sync;
int current_page;
int page_size;
Expand Down
2 changes: 2 additions & 0 deletions include/neuron/msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ typedef struct neu_req_read_group_paginate {
char *group;
char *name;
char *desc;
char *sort_by;
bool sync;
int current_page;
int page_size;
Expand All @@ -1088,6 +1089,7 @@ neu_req_read_group_paginate_fini(neu_req_read_group_paginate_t *req)
free(req->group);
free(req->name);
free(req->desc);
free(req->sort_by);
}

typedef struct neu_req_write_tag {
Expand Down
3 changes: 3 additions & 0 deletions include/neuron/tag.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ typedef struct neu_tag_meta {

UT_icd *neu_tag_get_icd();

typedef int (*neu_tag_cmp_fn)(const void *, const void *);
neu_tag_cmp_fn neu_tag_get_sort_cmp(const char *sort_by);

void neu_tag_format_str(const neu_datatag_t *tag, char *buf, int len);
int neu_format_from_str(const char *format_str, uint8_t *formats);

Expand Down
22 changes: 20 additions & 2 deletions plugins/restful/datatag_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,26 @@ void handle_get_tags(nng_aio *aio)

void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags)
{
neu_json_get_tags_resp_t tags_res = { 0 };
char * result = NULL;
neu_json_get_tags_resp_t tags_res = { 0 };
char * result = NULL;
char sort_by[32] = { 0 };

ssize_t sort_len =
neu_http_get_param_str(aio, "sort_by", sort_by, sizeof(sort_by));
if (sort_len != -2) {
neu_tag_cmp_fn cmp =
sort_len >= 0 && (size_t) sort_len < sizeof(sort_by)
? neu_tag_get_sort_cmp(sort_by)
Comment on lines +553 to +557

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the ascending sign when parsing GET sorts

When a caller uses the accepted ascending spelling directly in the GET URL, for example ?sort_by=+name, neu_http_get_param_str passes it through neu_url_decode, which rewrites the literal + to a space (src/utils/http.c:75-76); neu_tag_get_sort_cmp then receives " name" and this path returns NEU_ERR_PARAM_IS_WRONG. Thus ascending GET sorting only works if clients happen to percent-encode the sign as %2B, while the equivalent JSON API accepts +name as-is; preserve or normalize the leading sign before validating this parameter.

Useful? React with 👍 / 👎.

: NULL;
if (!cmp) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
});
utarray_free(tags->tags);
return;
}
utarray_sort(tags->tags, cmp);
}

tags_res.n_tag = utarray_len(tags->tags);
tags_res.tags = calloc(tags_res.n_tag, sizeof(neu_json_tag_t));
Expand Down
7 changes: 7 additions & 0 deletions plugins/restful/rw_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ void handle_read_paginate(nng_aio *aio)
goto error;
}

if (req->sort_by && !neu_tag_get_sort_cmp(req->sort_by)) {
err_type = NEU_ERR_PARAM_IS_WRONG;
goto error;
}

cmd.sort_by = req->sort_by;
req->sort_by = NULL;
cmd.driver = req->node;
cmd.group = req->group;
cmd.name = req->name;
Expand Down
6 changes: 5 additions & 1 deletion src/adapter/driver/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -963,12 +963,16 @@ void neu_adapter_driver_read_group_paginate(neu_adapter_driver_t *driver,
neu_group_t * group = g->group;
UT_array * tags;

neu_tag_cmp_fn cmp = neu_tag_get_sort_cmp(cmd->sort_by);
if (cmd->is_error != true && cmd->current_page > 0 && cmd->page_size > 0) {
tags = neu_group_query_read_tag_paginate(
group, cmd->name, cmd->desc, cmd->current_page, cmd->page_size,
&resp.total_count);
&resp.total_count, cmp);
} else {
tags = neu_group_query_read_tag(group, cmd->name, cmd->desc, 0, NULL);
if (cmp) {
utarray_sort(tags, cmp);
}
resp.total_count = utarray_len(tags);
}

Expand Down
27 changes: 24 additions & 3 deletions src/base/group.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,8 @@ UT_array *neu_group_query_read_tag(neu_group_t *group, const char *name,
UT_array *neu_group_query_read_tag_paginate(neu_group_t *group,
const char *name, const char *desc,
int current_page, int page_size,
int *total_count)
int * total_count,
neu_tag_cmp_fn cmp)
{
UT_array * array = NULL;
struct query q = {
Expand All @@ -420,10 +421,30 @@ UT_array *neu_group_query_read_tag_paginate(neu_group_t *group,
};

pthread_mutex_lock(&group->mtx);
array = filter_tags_by_page(group->tags, match_query, &q, current_page,
page_size, total_count);
if (cmp) {
array = filter_tags(group->tags, match_query, &q);
} else {
array = filter_tags_by_page(group->tags, match_query, &q, current_page,
page_size, total_count);
}
pthread_mutex_unlock(&group->mtx);

if (cmp) {
utarray_sort(array, cmp);
if (total_count) {
*total_count = utarray_len(array);
}
size_t start = (size_t)(current_page - 1) * page_size;
if (start >= utarray_len(array)) {
utarray_clear(array);
} else {
utarray_erase(array, 0, start);
if (utarray_len(array) > (unsigned int) page_size) {
utarray_resize(array, page_size);
}
}
}

return array;
}

Expand Down
3 changes: 2 additions & 1 deletion src/base/group.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ UT_array * neu_group_query_read_tag(neu_group_t *group, const char *name,
UT_array * neu_group_query_read_tag_paginate(neu_group_t *group,
const char *name, const char *desc,
int current_page, int page_size,
int *total_count);
int * total_count,
neu_tag_cmp_fn cmp);
uint16_t neu_group_tag_size(const neu_group_t *group);
neu_datatag_t *neu_group_find_tag(neu_group_t *group, const char *tag);

Expand Down
56 changes: 56 additions & 0 deletions src/base/tag.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,62 @@ config_ **/
#include "errcodes.h"
#include "tag.h"

#define TAG_CMP(field, expression) \
static int tag_cmp_##field(const void *left, const void *right) \
{ \
const neu_datatag_t *a = left, *b = right; \
int result = (expression); \
return result ? result : strcmp(a->name, b->name); \
} \
static int tag_cmp_##field##_desc(const void *left, const void *right) \
{ \
return tag_cmp_##field(right, left); \
}

#define TAG_STR_CMP(field) \
TAG_CMP(field, strcmp(a->field ? a->field : "", b->field ? b->field : ""))
#define TAG_NUM_CMP(field) \
TAG_CMP(field, (a->field > b->field) - (a->field < b->field))

TAG_STR_CMP(name)
TAG_STR_CMP(address)
TAG_STR_CMP(description)
TAG_STR_CMP(unit)
TAG_NUM_CMP(type)
TAG_NUM_CMP(attribute)
TAG_NUM_CMP(precision)
TAG_NUM_CMP(decimal)
TAG_NUM_CMP(bias)

#undef TAG_NUM_CMP
#undef TAG_STR_CMP
#undef TAG_CMP

neu_tag_cmp_fn neu_tag_get_sort_cmp(const char *sort_by)
{
if (!sort_by || (sort_by[0] != '+' && sort_by[0] != '-')) {
return NULL;
}

#define TAG_SORT_FIELD(field) \
if (strcmp(sort_by + 1, #field) == 0) { \
return sort_by[0] == '+' ? tag_cmp_##field : tag_cmp_##field##_desc; \
}

TAG_SORT_FIELD(name)
TAG_SORT_FIELD(address)
TAG_SORT_FIELD(description)
TAG_SORT_FIELD(unit)
TAG_SORT_FIELD(type)
TAG_SORT_FIELD(attribute)
TAG_SORT_FIELD(precision)
TAG_SORT_FIELD(decimal)
TAG_SORT_FIELD(bias)

#undef TAG_SORT_FIELD
return NULL;
}

static void tag_array_copy(void *_dst, const void *_src)
{
neu_datatag_t *dst = (neu_datatag_t *) _dst;
Expand Down
8 changes: 8 additions & 0 deletions src/parser/neu_json_rw.c
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,11 @@ int neu_json_decode_read_paginate_req(char * buf,
.t = NEU_JSON_BOOL,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
},
{
.name = "sort_by",
.t = NEU_JSON_STR,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
},
};

ret = neu_json_decode_by_json(json_obj, NEU_JSON_ELEM_SIZE(req_elems),
Expand All @@ -979,13 +984,15 @@ int neu_json_decode_read_paginate_req(char * buf,
req->current_page = query_elems[2].v.val_int;
req->page_size = query_elems[3].v.val_int;
req->is_error = query_elems[4].v.val_bool;
req->sort_by = query_elems[5].v.val_str;
}

*result = req;
neu_json_decode_free(json_obj);
return ret;

error:
free(query_elems[5].v.val_str);
free(query_elems[0].v.val_str);
free(query_elems[1].v.val_str);
free(req_elems[0].v.val_str);
Expand All @@ -1001,6 +1008,7 @@ void neu_json_decode_read_paginate_req_free(neu_json_read_paginate_req_t *req)
{
free(req->group);
free(req->node);
free(req->sort_by);

free(req);
}
Expand Down
104 changes: 104 additions & 0 deletions tests/ut/tag_sort_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

#include "tag_sort.h"

extern "C" {
#include "base/group.h"
#include "json/neu_json_rw.h"
}

#include "utils/log.h"

zlog_category_t *neuron = NULL;
Expand Down Expand Up @@ -220,6 +225,105 @@ TEST(TagSortTest, SortDiff)
free(tag4);
}

TEST(TagSortTest, SortByFields)
{
neu_datatag_t a = {}, b = {};
a.name = a.address = a.description = a.unit = (char *) "a";
b.name = b.address = b.description = b.unit = (char *) "b";
a.type = NEU_TYPE_INT16;
b.type = NEU_TYPE_INT32;
a.attribute = NEU_ATTRIBUTE_READ;
b.attribute = NEU_ATTRIBUTE_WRITE;
a.precision = 1;
b.precision = 2;
a.decimal = a.bias = -1.5;
b.decimal = b.bias = 2.5;

for (const char *field : { "name", "address", "description", "unit", "type",
"attribute", "precision", "decimal", "bias" }) {
SCOPED_TRACE(field);
auto asc = neu_tag_get_sort_cmp((std::string("+") + field).c_str());
auto desc = neu_tag_get_sort_cmp((std::string("-") + field).c_str());
ASSERT_NE(nullptr, asc);
ASSERT_NE(nullptr, desc);
EXPECT_LT(asc(&a, &b), 0);
EXPECT_GT(desc(&a, &b), 0);
EXPECT_EQ(asc(&a, &a), 0);
}

a.description = a.unit = nullptr;
EXPECT_LT(neu_tag_get_sort_cmp("+description")(&a, &b), 0);
EXPECT_LT(neu_tag_get_sort_cmp("+unit")(&a, &b), 0);
EXPECT_EQ(nullptr, neu_tag_get_sort_cmp(nullptr));
for (const char *invalid : { "", "+", "-", "name", " name", "+unknown" }) {
EXPECT_EQ(nullptr, neu_tag_get_sort_cmp(invalid));
}
}

TEST(TagSortTest, SortBeforePagination)
{
neu_group_t *group = neu_group_new("group", 1000);
ASSERT_NE(nullptr, group);
for (const char *name : { "tag-c", "tag-a", "tag-b" }) {
neu_datatag_t tag = {};
tag.name = (char *) name;
tag.attribute = NEU_ATTRIBUTE_READ;
ASSERT_EQ(0, neu_group_add_tag(group, &tag));
}

for (const char *sort_by : { "+name", "-name" }) {
auto cmp = neu_tag_get_sort_cmp(sort_by);
const char *first = sort_by[0] == '+' ? "tag-a" : "tag-c";
const char *last = sort_by[0] == '+' ? "tag-c" : "tag-a";
int total = 0;
UT_array * tags = neu_group_query_read_tag_paginate(
group, "tag", nullptr, 1, 2, &total, cmp);
EXPECT_EQ(3, total);
ASSERT_EQ(2, utarray_len(tags));
EXPECT_STREQ(first, ((neu_datatag_t *) utarray_front(tags))->name);
EXPECT_STREQ("tag-b", ((neu_datatag_t *) utarray_back(tags))->name);
utarray_free(tags);

tags = neu_group_query_read_tag_paginate(group, "tag", nullptr, 2, 2,
&total, cmp);
ASSERT_EQ(1, utarray_len(tags));
EXPECT_STREQ(last, ((neu_datatag_t *) utarray_front(tags))->name);
utarray_free(tags);

tags = neu_group_query_read_tag_paginate(group, "tag", nullptr, 3, 2,
&total, cmp);
EXPECT_EQ(0, utarray_len(tags));
EXPECT_EQ(3, total);
utarray_free(tags);
}

int total = 0;
UT_array *tags = neu_group_query_read_tag_paginate(group, nullptr, nullptr,
1, 2, &total, nullptr);
EXPECT_STREQ("tag-c", ((neu_datatag_t *) utarray_front(tags))->name);
utarray_free(tags);
neu_group_destroy(group);
}

TEST(TagSortTest, DecodePaginateSortBy)
{
for (const char *sort_by : { "+name", "-address" }) {
std::string body = std::string("{\"node\":\"node\",\"group\":\"group\","
"\"query\":{\"sort_by\":\"") +
sort_by + "\"}}";
neu_json_read_paginate_req_t *req = nullptr;
ASSERT_EQ(0, neu_json_decode_read_paginate_req(&body[0], &req));
ASSERT_NE(nullptr, req);
EXPECT_STREQ(sort_by, req->sort_by);
neu_json_decode_read_paginate_req_free(req);
}
char body[] = "{\"node\":\"node\",\"group\":\"group\"}";
neu_json_read_paginate_req_t *req = nullptr;
ASSERT_EQ(0, neu_json_decode_read_paginate_req(body, &req));
EXPECT_EQ(nullptr, req->sort_by);
neu_json_decode_read_paginate_req_free(req);
}

int main(int argc, char **argv)
{
zlog_init("./config/dev.conf");
Expand Down
Loading