From 3c75770586ec9123889d126f6bc05d3563d55ee0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 2 Nov 2022 10:02:22 +1030 Subject: [PATCH] common/json_filter: routines for json filtering. Signed-off-by: Rusty Russell --- common/Makefile | 1 + common/json_filter.c | 192 ++++++++++++++++++++++++++++++++++ common/json_filter.h | 34 ++++++ common/test/run-json.c | 13 +++ lightningd/test/run-jsonrpc.c | 1 + plugins/Makefile | 1 + 6 files changed, 242 insertions(+) create mode 100644 common/json_filter.c create mode 100644 common/json_filter.h diff --git a/common/Makefile b/common/Makefile index 27126d339..eba0fa1df 100644 --- a/common/Makefile +++ b/common/Makefile @@ -48,6 +48,7 @@ COMMON_SRC_NOGEN := \ common/initial_channel.c \ common/initial_commit_tx.c \ common/iso4217.c \ + common/json_filter.c \ common/json_param.c \ common/json_parse.c \ common/json_parse_simple.c \ diff --git a/common/json_filter.c b/common/json_filter.c new file mode 100644 index 000000000..ed422f32d --- /dev/null +++ b/common/json_filter.c @@ -0,0 +1,192 @@ +#include "config.h" +#include +#include +#include +#include +#include + +/* If they set a filter, we keep it in a tree. */ +struct json_filter { + /* We accumulate errors: if they treat an array as an object */ + bool misused; + + /* Pointer to parent, or NULL at top. */ + struct json_filter *parent; + + /* Tracks how far we are into filter, e.g. + * if they specify "peers.foo" and we're + * in "peer.foo.bar" depth will be 1. */ + size_t depth; + /* If we're in "peer.bar", we're negative */ + bool positive; + + /* If this is an array */ + struct json_filter *filter_array; + + /* Otherwise, object: one per keyword */ + STRMAP(struct json_filter *) filter_map; +}; + +/* Returns true if we should print this member: this is a shortcut for: + * + * json_filter_down(filter, member); + * ret = json_filter_ok(filter, NULL); + * json_filter_up(filter); + * + */ +bool json_filter_ok(const struct json_filter *filter, const char *member) +{ + if (!filter) + return true; + if (filter->depth > 0 || !member) + return filter->positive; + return strmap_get(&filter->filter_map, member) != NULL; +} + +/* Returns true if we should print this new obj/array */ +bool json_filter_down(struct json_filter **filter, const char *member) +{ + struct json_filter *child; + + if (!*filter) + return true; + if ((*filter)->depth > 0) { + (*filter)->depth++; + return (*filter)->positive; + } + + /* If we're a leaf node: all true. */ + if (!(*filter)->filter_array && strmap_empty(&(*filter)->filter_map)) { + assert((*filter)->positive); + (*filter)->depth = 1; + return true; + } + + /* Array? */ + if (!member) { + if (!(*filter)->filter_array) { + (*filter)->misused = true; + goto fail; + } + child = (*filter)->filter_array; + } else { + if ((*filter)->filter_array) { + (*filter)->misused = true; + goto fail; + } + child = strmap_get(&(*filter)->filter_map, member); + } + + if (child) { + /* Should have been cleaned up last time. */ + assert(child->depth == 0); + /* We only have positive filters natively. */ + assert(child->positive == true); + *filter = child; + return true; + } + + /* OK, this path wasn't specified. */ +fail: + (*filter)->positive = false; + (*filter)->depth = 1; + return false; +} + +/* Returns true if we were printing (i.e. close object/arr) */ +bool json_filter_up(struct json_filter **filter) +{ + if (!*filter) + return true; + + if ((*filter)->depth == 0) { + assert((*filter)->parent); + assert((*filter)->parent->depth == 0); + /* Reset for next time */ + (*filter)->positive = true; + *filter = (*filter)->parent; + return true; + } + + (*filter)->depth--; + return (*filter)->positive; +} + +static void destroy_json_filter(struct json_filter *filter) +{ + strmap_clear(&filter->filter_map); +} + +struct json_filter *json_filter_new(const tal_t *ctx) +{ + struct json_filter *filter = tal(ctx, struct json_filter); + filter->misused = false; + filter->parent = NULL; + filter->depth = 0; + filter->positive = true; + filter->filter_array = NULL; + strmap_init(&filter->filter_map); + tal_add_destructor(filter, destroy_json_filter); + return filter; +} + +struct json_filter *json_filter_subobj(struct json_filter *filter, + const char *fieldname, + size_t fieldnamelen) +{ + struct json_filter *subfilter = json_filter_new(filter); + subfilter->parent = filter; + strmap_add(&filter->filter_map, + tal_strndup(filter, fieldname, fieldnamelen), + subfilter); + return subfilter; +} + +struct json_filter *json_filter_subarr(struct json_filter *filter) +{ + struct json_filter *subfilter = json_filter_new(filter); + subfilter->parent = filter; + filter->filter_array = subfilter; + return subfilter; +} + +bool json_filter_finished(const struct json_filter *filter) +{ + return !filter->parent && filter->depth == 0; +} + +static bool strmap_filter_misused(const char *member, + struct json_filter *filter, + const char **ret) +{ + *ret = json_filter_misused(tmpctx, filter); + if (*ret == NULL) + return true; + + /* If there was a problem, prepend member and stop iterating */ + *ret = tal_fmt(tmpctx, ".%s%s", member, *ret); + return false; +} + +const char *json_filter_misused(const tal_t *ctx, const struct json_filter *f) +{ + const char *ret; + + if (f->misused) { + if (f->filter_array) + return tal_fmt(ctx, " is an object"); + else + return tal_fmt(ctx, " is an array"); + } + + if (f->filter_array) { + ret = json_filter_misused(tmpctx, f->filter_array); + if (ret) + return tal_fmt(ctx, "[]%s", ret); + return NULL; + } else { + ret = NULL; + strmap_iterate(&f->filter_map, strmap_filter_misused, &ret); + return tal_steal(ctx, ret); + } +} diff --git a/common/json_filter.h b/common/json_filter.h new file mode 100644 index 000000000..0bf298687 --- /dev/null +++ b/common/json_filter.h @@ -0,0 +1,34 @@ +/* + * Helpers for filtering JSON results while generating. + */ +#ifndef LIGHTNING_COMMON_JSON_FILTER_H +#define LIGHTNING_COMMON_JSON_FILTER_H +#include "config.h" +#include +#include + +struct json_filter; + +/* Print this? */ +bool json_filter_ok(const struct json_filter *filter, const char *member); + +/* Returns true if we should print this new obj/array */ +bool json_filter_down(struct json_filter **filter, const char *member); + +/* Returns true if we were printing (i.e. close object/arr) */ +bool json_filter_up(struct json_filter **filter); + +/* Is filter finished (i.e. balanced!) */ +bool json_filter_finished(const struct json_filter *filter); + +/* Has filter been misused? If so, returns explanatory string, otherwise NULL */ +const char *json_filter_misused(const tal_t *ctx, const struct json_filter *f); + +/* Filter allocation */ +struct json_filter *json_filter_new(const tal_t *ctx); +struct json_filter *json_filter_subobj(struct json_filter *filter, + const char *fieldname, + size_t fieldnamelen); +struct json_filter *json_filter_subarr(struct json_filter *filter); + +#endif /* LIGHTNING_COMMON_JSON_FILTER_H */ diff --git a/common/test/run-json.c b/common/test/run-json.c index d283fbcd5..84e672d2a 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,18 @@ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *record UNNEEDED, struct tlv_field **fields UNNEEDED, const u64 *extra_types UNNEEDED, size_t *err_off UNNEEDED, u64 *err_type UNNEEDED) { fprintf(stderr, "fromwire_tlv called!\n"); abort(); } +/* Generated stub for json_filter_down */ +bool json_filter_down(struct json_filter **filter UNNEEDED, const char *member UNNEEDED) +{ fprintf(stderr, "json_filter_down called!\n"); abort(); } +/* Generated stub for json_filter_finished */ +bool json_filter_finished(const struct json_filter *filter UNNEEDED) +{ fprintf(stderr, "json_filter_finished called!\n"); abort(); } +/* Generated stub for json_filter_ok */ +bool json_filter_ok(const struct json_filter *filter UNNEEDED, const char *member UNNEEDED) +{ fprintf(stderr, "json_filter_ok called!\n"); abort(); } +/* Generated stub for json_filter_up */ +bool json_filter_up(struct json_filter **filter UNNEEDED) +{ fprintf(stderr, "json_filter_up called!\n"); abort(); } /* Generated stub for towire_tlv */ void towire_tlv(u8 **pptr UNNEEDED, const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED, diff --git a/lightningd/test/run-jsonrpc.c b/lightningd/test/run-jsonrpc.c index 8a0a3f9b6..0919a3a93 100644 --- a/lightningd/test/run-jsonrpc.c +++ b/lightningd/test/run-jsonrpc.c @@ -1,4 +1,5 @@ #include "config.h" +#include "../../common/json_filter.c" #include "../../common/json_stream.c" #include "../jsonrpc.c" #include "../feerate.c" diff --git a/plugins/Makefile b/plugins/Makefile index d6d7f3bec..e6c703869 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -140,6 +140,7 @@ PLUGIN_COMMON_OBJS := \ common/json_param.o \ common/json_parse.o \ common/json_parse_simple.o \ + common/json_filter.o \ common/json_stream.o \ common/lease_rates.o \ common/memleak.o \