mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 21:35:11 +01:00
Typesafe callback system for parsing json
This is part of #1464 and incorporates Rusty's suggested updates from #1569. See comment in param.h for description, here's the basics: unsigned cltv; const jsmntok_t *note; u64 msatoshi; struct param * mp; if (!param_parse(cmd, buffer, tokens, param_req("cltv", json_tok_number, &cltv), param_opt("note", json_tok_tok, ¬e), mp = param_opt("msatoshi", json_tok_u64, &msatoshi), NULL)) return; if (param_is_set(mp)) do_something() There is a lot of developer mode code to make sure we don't make mistakes, like trying to unmarshal into the same variable twice or adding a required param after optional. During testing, I found a bug (of sorts) in the current system. It allows you to provide two named parameters with the same name without error; e.g.: # cli/lightning-cli -k newaddr addresstype=p2sh-segwit addresstype=bech32 { "address": "2N3r6fT65PhfhE1mcMS6TtcdaEurud6M7pA" } It just takes the first and ignores the second. The new system reports this as an error for now. We can always change this later.
This commit is contained in:
parent
fed5a117e7
commit
4d1d0438e1
@ -143,6 +143,13 @@ bool json_tok_bool(const char *buffer, const jsmntok_t *tok, bool *b)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool json_tok_tok(const char *buffer, const jsmntok_t * tok,
|
||||
const jsmntok_t **out)
|
||||
{
|
||||
*out = tok;
|
||||
return true;
|
||||
}
|
||||
|
||||
const jsmntok_t *json_next(const jsmntok_t *tok)
|
||||
{
|
||||
const jsmntok_t *t;
|
||||
|
@ -42,6 +42,13 @@ bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok,
|
||||
/* Extract boolean this (must be a true or false) */
|
||||
bool json_tok_bool(const char *buffer, const jsmntok_t *tok, bool *b);
|
||||
|
||||
/*
|
||||
* Set the address of @out to @tok. Used as a param_table callback by handlers that
|
||||
* want to unmarshal @tok themselves.
|
||||
*/
|
||||
bool json_tok_tok(const char *buffer, const jsmntok_t * tok,
|
||||
const jsmntok_t **out);
|
||||
|
||||
/* Is this the null primitive? */
|
||||
bool json_tok_is_null(const char *buffer, const jsmntok_t *tok);
|
||||
|
||||
|
@ -70,6 +70,7 @@ LIGHTNINGD_SRC := \
|
||||
lightningd/onchain_control.c \
|
||||
lightningd/opening_control.c \
|
||||
lightningd/options.c \
|
||||
lightningd/params.c \
|
||||
lightningd/pay.c \
|
||||
lightningd/payalgo.c \
|
||||
lightningd/peer_control.c \
|
||||
|
297
lightningd/params.c
Normal file
297
lightningd/params.c
Normal file
@ -0,0 +1,297 @@
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <common/utils.h>
|
||||
#include <lightningd/json.h>
|
||||
#include <lightningd/jsonrpc.h>
|
||||
#include <lightningd/jsonrpc_errors.h>
|
||||
#include <lightningd/lightningd.h>
|
||||
#include <lightningd/params.h>
|
||||
|
||||
struct param {
|
||||
char *name;
|
||||
bool required;
|
||||
bool is_set;
|
||||
param_cb cb;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
void *param_is_set(struct param *def)
|
||||
{
|
||||
return def->is_set ? def->arg : NULL;
|
||||
}
|
||||
|
||||
struct param *param_add_(bool required, char *name, param_cb cb, void *arg)
|
||||
{
|
||||
#if DEVELOPER
|
||||
assert(name);
|
||||
assert(cb);
|
||||
assert(arg);
|
||||
#endif
|
||||
struct param *last = tal(tmpctx, struct param);
|
||||
last->is_set = false;
|
||||
last->name = tal_strdup(last, name);
|
||||
last->cb = cb;
|
||||
last->arg = arg;
|
||||
last->required = required;
|
||||
return last;
|
||||
}
|
||||
|
||||
struct fail_format {
|
||||
void *cb;
|
||||
const char *format;
|
||||
};
|
||||
|
||||
static struct fail_format fail_formats[] = {
|
||||
{json_tok_bool, "'%s' should be 'true' or 'false', not '%.*s'"},
|
||||
{json_tok_double, "'%s' should be a double, not '%.*s'"},
|
||||
{json_tok_u64, "'%s' should be an unsigned 64 bit integer, not '%.*s'"},
|
||||
{json_tok_number, "'%s' should be an integer, not '%.*s'"},
|
||||
{json_tok_wtx,
|
||||
"'%s' should be 'all' or a positive integer greater than "
|
||||
"545, not '%.*s'"},
|
||||
{NULL, "'%s' of '%.*s' is invalid'"}
|
||||
};
|
||||
|
||||
static const char *find_fail_format(param_cb cb)
|
||||
{
|
||||
struct fail_format *fmt = fail_formats;
|
||||
while (fmt->cb != NULL) {
|
||||
if (fmt->cb == cb)
|
||||
break;
|
||||
fmt++;
|
||||
}
|
||||
return fmt->format;
|
||||
}
|
||||
|
||||
static bool make_callback(struct command *cmd,
|
||||
struct param *def,
|
||||
const char *buffer, const jsmntok_t * tok)
|
||||
{
|
||||
def->is_set = true;
|
||||
if (!def->cb(buffer, tok, def->arg)) {
|
||||
struct json_result *data = new_json_result(cmd);
|
||||
const char *val = tal_fmt(cmd, "%.*s", tok->end - tok->start,
|
||||
buffer + tok->start);
|
||||
json_object_start(data, NULL);
|
||||
json_add_string(data, def->name, val);
|
||||
json_object_end(data);
|
||||
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, data,
|
||||
find_fail_format(def->cb), def->name,
|
||||
tok->end - tok->start,
|
||||
buffer + tok->start);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct param **post_check(struct command *cmd, struct param **params)
|
||||
{
|
||||
struct param **first = params;
|
||||
struct param **last = first + tal_count(params);
|
||||
|
||||
/* Make sure required params were provided. */
|
||||
while (first != last && (*first)->required) {
|
||||
if (!(*first)->is_set) {
|
||||
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"missing required parameter: '%s'",
|
||||
(*first)->name);
|
||||
return NULL;
|
||||
}
|
||||
first++;
|
||||
}
|
||||
|
||||
/* Set optional missing jsmntok_t args to NULL. */
|
||||
while (first != last) {
|
||||
struct param *p = *first;
|
||||
if (!p->is_set && (p->cb == (param_cb) json_tok_tok)) {
|
||||
jsmntok_t **tok = p->arg;
|
||||
*tok = NULL;
|
||||
}
|
||||
first++;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
static struct param **parse_by_position(struct command *cmd,
|
||||
struct param **params,
|
||||
const char *buffer,
|
||||
const jsmntok_t tokens[])
|
||||
{
|
||||
const jsmntok_t *tok = tokens + 1;
|
||||
const jsmntok_t *end = json_next(tokens);
|
||||
struct param **first = params;
|
||||
struct param **last = first + tal_count(params);
|
||||
|
||||
while (first != last && tok != end) {
|
||||
if (!json_tok_is_null(buffer, tok))
|
||||
if (!make_callback(cmd, *first, buffer, tok))
|
||||
return NULL;
|
||||
tok = json_next(tok);
|
||||
first++;
|
||||
}
|
||||
|
||||
/* check for unexpected trailing params */
|
||||
if (tok != end) {
|
||||
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"too many parameters:"
|
||||
" got %u, expected %zu",
|
||||
tokens->size, tal_count(params));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return post_check(cmd, params);
|
||||
}
|
||||
|
||||
static struct param *find_param(struct param **params, const char *start,
|
||||
size_t n)
|
||||
{
|
||||
struct param **first = params;
|
||||
struct param **last = first + tal_count(params);
|
||||
|
||||
while (first != last) {
|
||||
if (strncmp((*first)->name, start, n) == 0)
|
||||
if (strlen((*first)->name) == n)
|
||||
return *first;
|
||||
first++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct param **parse_by_name(struct command *cmd,
|
||||
struct param **params,
|
||||
const char *buffer,
|
||||
const jsmntok_t tokens[])
|
||||
{
|
||||
const jsmntok_t *first = tokens + 1;
|
||||
const jsmntok_t *last = json_next(tokens);
|
||||
|
||||
while (first != last) {
|
||||
struct param *p = find_param(params, buffer + first->start,
|
||||
first->end - first->start);
|
||||
if (!p) {
|
||||
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"unknown parameter: '%.*s'",
|
||||
first->end - first->start,
|
||||
buffer + first->start);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (p->is_set) {
|
||||
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"duplicate json names: '%s'", p->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!make_callback(cmd, p, buffer, first + 1))
|
||||
return NULL;
|
||||
first = json_next(first + 1);
|
||||
}
|
||||
return post_check(cmd, params);
|
||||
}
|
||||
|
||||
#if DEVELOPER
|
||||
static int comp_by_name(const void *a, const void *b)
|
||||
{
|
||||
const char *x = (*(const struct param **) a)->name;
|
||||
const char *y = (*(const struct param **) b)->name;
|
||||
return strcmp(x, y);
|
||||
}
|
||||
|
||||
static int comp_by_arg(const void *a, const void *b)
|
||||
{
|
||||
size_t x = (size_t) ((*(const struct param **) a)->arg);
|
||||
size_t y = (size_t) ((*(const struct param **) b)->arg);
|
||||
return x - y;
|
||||
}
|
||||
|
||||
/* This comparator is a bit different, but works well.
|
||||
* Return 0 if @a is optional and @b is required. Otherwise return 1.
|
||||
*/
|
||||
static int comp_req_order(const void *a, const void *b)
|
||||
{
|
||||
bool x = (bool) ((*(const struct param **) a)->required);
|
||||
bool y = (bool) ((*(const struct param **) b)->required);
|
||||
if (!x && y)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure 2 sequential items in @params are not equal (based on
|
||||
* provided comparator).
|
||||
*/
|
||||
static void check_distinct(struct param **params,
|
||||
int (*compar) (const void *, const void *))
|
||||
{
|
||||
struct param **first = params;
|
||||
struct param **last = first + tal_count(params);
|
||||
first++;
|
||||
while (first != last) {
|
||||
assert(compar(first - 1, first) != 0);
|
||||
first++;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_unique(struct param **copy,
|
||||
int (*compar) (const void *, const void *))
|
||||
{
|
||||
qsort(copy, tal_count(copy), sizeof(struct param *), compar);
|
||||
check_distinct(copy, compar);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify consistent internal state.
|
||||
*/
|
||||
static void check_params(struct param **params)
|
||||
{
|
||||
if (tal_count(params) < 2)
|
||||
return;
|
||||
|
||||
/* make sure there are no required params following optional */
|
||||
check_distinct(params, comp_req_order);
|
||||
|
||||
/* duplicate so we can sort */
|
||||
struct param **copy = tal_dup_arr(params, struct param *,
|
||||
params, tal_count(params), 0);
|
||||
|
||||
/* check for repeated names and args */
|
||||
check_unique(copy, comp_by_name);
|
||||
check_unique(copy, comp_by_arg);
|
||||
|
||||
tal_free(copy);
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct param **param_parse_arr(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t tokens[],
|
||||
struct param **params)
|
||||
{
|
||||
#if DEVELOPER
|
||||
check_params(params);
|
||||
#endif
|
||||
if (tokens->type == JSMN_ARRAY)
|
||||
return parse_by_position(cmd, params, buffer, tokens);
|
||||
else if (tokens->type == JSMN_OBJECT)
|
||||
return parse_by_name(cmd, params, buffer, tokens);
|
||||
|
||||
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Expected array or object for params");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct param **param_parse(struct command *cmd, const char *buffer,
|
||||
const jsmntok_t tokens[], ...)
|
||||
{
|
||||
struct param *def;
|
||||
struct param **params = tal_arr(cmd, struct param *, 0);
|
||||
va_list ap;
|
||||
va_start(ap, tokens);
|
||||
while ((def = va_arg(ap, struct param *)) != NULL) {
|
||||
tal_steal(params, def);
|
||||
tal_resize(¶ms, tal_count(params) + 1);
|
||||
params[tal_count(params) - 1] = def;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
return param_parse_arr(cmd, buffer, tokens, params);
|
||||
}
|
93
lightningd/params.h
Normal file
93
lightningd/params.h
Normal file
@ -0,0 +1,93 @@
|
||||
#ifndef LIGHTNING_LIGHTNINGD_PARAMS_H
|
||||
#define LIGHTNING_LIGHTNINGD_PARAMS_H
|
||||
#include "config.h"
|
||||
#include <ccan/ccan/typesafe_cb/typesafe_cb.h>
|
||||
|
||||
struct param;
|
||||
|
||||
/*
|
||||
Typesafe callback system for unmarshalling and validating json parameters.
|
||||
|
||||
Typical usage:
|
||||
unsigned cltv;
|
||||
const jsmntok_t *note;
|
||||
u64 msatoshi;
|
||||
struct param * mp;
|
||||
|
||||
if (!param_parse(cmd, buffer, tokens,
|
||||
param_req("cltv", json_tok_number, &cltv),
|
||||
param_opt("note", json_tok_tok, ¬e),
|
||||
mp = param_opt("msatoshi", json_tok_u64, &msatoshi),
|
||||
NULL))
|
||||
return;
|
||||
|
||||
At this point in the code you can be assured the json tokens were successfully
|
||||
parsed. If not, param_parse() returned NULL, having already called
|
||||
command_fail() with a descriptive error message. The data section of the json
|
||||
result contains the offending parameter and its value.
|
||||
|
||||
cltv is a required parameter, and is set correctly.
|
||||
|
||||
note and msatoshi are optional parameters. You can see if they have been set
|
||||
by calling param_is_set(); e.g.:
|
||||
|
||||
if (param_is_set(mp))
|
||||
do_something()
|
||||
|
||||
The note parameter uses a special callback, json_tok_tok(). It
|
||||
simply sets seedtok to the appropriate value and lets the handler do the
|
||||
validating. It has the added feature of setting seedtok to NULL if it is null
|
||||
or not specified.
|
||||
|
||||
There are canned failure messages for common callbacks. An example:
|
||||
|
||||
'msatoshi' should be an unsigned 64 bit integer, not '123z'
|
||||
|
||||
Otherwise a generic message is provided.
|
||||
*/
|
||||
struct param **param_parse(struct command *cmd, const char *buffer,
|
||||
const jsmntok_t params[], ...);
|
||||
|
||||
/*
|
||||
* This callback provided must follow this signature; e.g.,
|
||||
* bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *arg)
|
||||
*/
|
||||
typedef bool(*param_cb)(const char *buffer, const jsmntok_t *tok, void *arg);
|
||||
|
||||
/*
|
||||
* Add a handler to unmarshal a required json token into @arg. The handler must
|
||||
* return true on success and false on failure. Upon failure, command_fail will be
|
||||
* called with a descriptive error message.
|
||||
*
|
||||
* This operation is typesafe; i.e., a compilation error will occur if the types
|
||||
* of @arg and the last parameter of @cb do not match.
|
||||
*
|
||||
* Returns an opaque pointer that can be later used in param_is_set().
|
||||
*/
|
||||
#define param_req(name, cb, arg) \
|
||||
param_add_(true, name, \
|
||||
typesafe_cb_preargs(bool, void *, \
|
||||
(cb), (arg), \
|
||||
const char *, \
|
||||
const jsmntok_t *), \
|
||||
(arg))
|
||||
/*
|
||||
* Same as above but for optional parameters.
|
||||
*/
|
||||
#define param_opt(name, cb, arg) \
|
||||
param_add_(false, name, \
|
||||
typesafe_cb_preargs(bool, void *, \
|
||||
(cb), (arg), \
|
||||
const char *, \
|
||||
const jsmntok_t *), \
|
||||
(arg))
|
||||
struct param * param_add_(bool required, char *name, param_cb cb, void *arg);
|
||||
|
||||
/*
|
||||
* Check to see if an optional parameter was set during parsing (although it
|
||||
* works for all parameters).
|
||||
* Returns the @arg if set, otherwise NULL.
|
||||
*/
|
||||
void * param_is_set(struct param *p);
|
||||
|
||||
#endif /* LIGHTNING_LIGHTNINGD_PARAMS_H */
|
446
lightningd/test/run-params.c
Normal file
446
lightningd/test/run-params.c
Normal file
@ -0,0 +1,446 @@
|
||||
#include <signal.h>
|
||||
#include <setjmp.h>
|
||||
#include <lightningd/jsonrpc.h>
|
||||
|
||||
#include <lightningd/params.c>
|
||||
#include <common/json.c>
|
||||
#include <common/json_escaped.c>
|
||||
#include <ccan/array_size/array_size.h>
|
||||
|
||||
bool failed;
|
||||
char *fail_msg;
|
||||
|
||||
struct command *cmd;
|
||||
|
||||
void command_fail(struct command *cmd, int code, const char *fmt, ...)
|
||||
{
|
||||
failed = true;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
fail_msg = tal_vfmt(cmd, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void command_fail_detailed(struct command *cmd, int code,
|
||||
const struct json_result *data, const char *fmt, ...)
|
||||
{
|
||||
failed = true;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
fail_msg = tal_vfmt(cmd, fmt, ap);
|
||||
fail_msg =
|
||||
tal_fmt(cmd, "%s data: %s", fail_msg, json_result_string(data));
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* AUTOGENERATED MOCKS START */
|
||||
/* Generated stub for json_tok_wtx */
|
||||
bool json_tok_wtx(struct wallet_tx *tx UNNEEDED, const char *buffer UNNEEDED,
|
||||
const jsmntok_t * sattok UNNEEDED)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
/* AUTOGENERATED MOCKS END */
|
||||
|
||||
struct json {
|
||||
jsmntok_t *toks;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
static void convert_quotes(char *first)
|
||||
{
|
||||
while (*first != '\0') {
|
||||
if (*first == '\'')
|
||||
*first = '"';
|
||||
first++;
|
||||
}
|
||||
}
|
||||
|
||||
static struct json *json_parse(const tal_t * ctx, const char *str)
|
||||
{
|
||||
struct json *j = tal(ctx, struct json);
|
||||
j->buffer = tal_strdup(j, str);
|
||||
convert_quotes(j->buffer);
|
||||
|
||||
j->toks = tal_arr(j, jsmntok_t, 50);
|
||||
assert(j->toks);
|
||||
jsmn_parser parser;
|
||||
|
||||
again:
|
||||
jsmn_init(&parser);
|
||||
int ret = jsmn_parse(&parser, j->buffer, strlen(j->buffer), j->toks,
|
||||
tal_count(j->toks));
|
||||
if (ret == JSMN_ERROR_NOMEM) {
|
||||
tal_resize(&j->toks, tal_count(j->toks) * 2);
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
assert(0);
|
||||
}
|
||||
failed = false;
|
||||
return j;
|
||||
}
|
||||
|
||||
static void zero_params(void)
|
||||
{
|
||||
struct json *j = json_parse(cmd, "{}");
|
||||
assert(param_parse(cmd, j->buffer, j->toks, NULL));
|
||||
|
||||
j = json_parse(cmd, "[]");
|
||||
assert(param_parse(cmd, j->buffer, j->toks, NULL));
|
||||
}
|
||||
|
||||
struct sanity {
|
||||
char *str;
|
||||
bool failed;
|
||||
int ival;
|
||||
double dval;
|
||||
char *fail_str;
|
||||
};
|
||||
|
||||
struct sanity buffers[] = {
|
||||
// pass
|
||||
{"['42', '3.15']", false, 42, 3.15, NULL},
|
||||
{"{ 'u64' : '42', 'double' : '3.15' }", false, 42, 3.15, NULL},
|
||||
|
||||
// fail
|
||||
{"{'u64':'42', 'double':'3.15', 'extra':'stuff'}", true, 0, 0,
|
||||
"unknown parameter"},
|
||||
{"['42', '3.15', 'stuff']", true, 0, 0, "too many"},
|
||||
{"['42', '3.15', 'null']", true, 0, 0, "too many"},
|
||||
|
||||
// not enough
|
||||
{"{'u64':'42'}", true, 0, 0, "missing required"},
|
||||
{"['42']", true, 0, 0, "missing required"},
|
||||
|
||||
// fail wrong type
|
||||
{"{'u64':'hello', 'double':'3.15'}", true, 0, 0, "\"u64\": \"hello\""},
|
||||
{"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"},
|
||||
};
|
||||
|
||||
static void stest(const struct json *j, struct sanity *b)
|
||||
{
|
||||
u64 ival;
|
||||
double dval;
|
||||
if (!param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", json_tok_u64, &ival),
|
||||
param_req("double", json_tok_double, &dval), NULL)) {
|
||||
assert(failed == true);
|
||||
assert(b->failed == true);
|
||||
assert(strstr(fail_msg, b->fail_str));
|
||||
} else {
|
||||
assert(b->failed == false);
|
||||
assert(ival == 42);
|
||||
assert(dval > 3.1499 && b->dval < 3.1501);
|
||||
}
|
||||
}
|
||||
|
||||
static void sanity(void)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(buffers); ++i) {
|
||||
struct json *j = json_parse(cmd, buffers[i].str);
|
||||
assert(j->toks->type == JSMN_OBJECT
|
||||
|| j->toks->type == JSMN_ARRAY);
|
||||
stest(j, &buffers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure toks are passed through correctly, and also make sure
|
||||
* optional missing toks are set to NULL.
|
||||
*/
|
||||
static void tok_tok(void)
|
||||
{
|
||||
{
|
||||
unsigned int n;
|
||||
const jsmntok_t *tok = NULL;
|
||||
struct json *j = json_parse(cmd, "{ 'satoshi', '546' }");
|
||||
|
||||
assert(param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("satoshi", json_tok_tok,
|
||||
&tok), NULL));
|
||||
assert(tok);
|
||||
assert(json_tok_number(j->buffer, tok, &n));
|
||||
assert(n == 546);
|
||||
}
|
||||
// again with missing optional parameter
|
||||
{
|
||||
/* make sure it is *not* NULL */
|
||||
const jsmntok_t *tok = (const jsmntok_t *) 65535;
|
||||
|
||||
struct json *j = json_parse(cmd, "{}");
|
||||
assert(param_parse(cmd, j->buffer, j->toks,
|
||||
param_opt("satoshi", json_tok_tok,
|
||||
&tok), NULL));
|
||||
|
||||
/* make sure it *is* NULL */
|
||||
assert(tok == NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* check for valid but duplicate json name-value pairs */
|
||||
static void dup(void)
|
||||
{
|
||||
struct json *j =
|
||||
json_parse(cmd,
|
||||
"{ 'u64' : '42', 'u64' : '43', 'double' : '3.15' }");
|
||||
|
||||
u64 i;
|
||||
double d;
|
||||
assert(!param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", json_tok_u64, &i),
|
||||
param_req("double", json_tok_double, &d), NULL));
|
||||
}
|
||||
|
||||
static void null_params(void)
|
||||
{
|
||||
uint64_t *ints = tal_arr(cmd, uint64_t, 7);
|
||||
/* no null params */
|
||||
struct json *j =
|
||||
json_parse(cmd, "[ '10', '11', '12', '13', '14', '15', '16']");
|
||||
for (int i = 0; i < tal_count(ints); ++i)
|
||||
ints[i] = i;
|
||||
|
||||
assert(param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("0", json_tok_u64, &ints[0]),
|
||||
param_req("1", json_tok_u64, &ints[1]),
|
||||
param_req("2", json_tok_u64, &ints[2]),
|
||||
param_req("3", json_tok_u64, &ints[3]),
|
||||
param_opt("4", json_tok_u64, &ints[4]),
|
||||
param_opt("5", json_tok_u64, &ints[5]),
|
||||
param_opt("6", json_tok_u64, &ints[6]), NULL));
|
||||
for (int i = 0; i < tal_count(ints); ++i)
|
||||
assert(ints[i] == i + 10);
|
||||
|
||||
/* missing at end */
|
||||
for (int i = 0; i < tal_count(ints); ++i)
|
||||
ints[i] = 42;
|
||||
|
||||
j = json_parse(cmd, "[ '10', '11', '12', '13', '14']");
|
||||
struct param *four, *five;
|
||||
assert(param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("0", json_tok_u64, &ints[0]),
|
||||
param_req("1", json_tok_u64, &ints[1]),
|
||||
param_req("2", json_tok_u64, &ints[2]),
|
||||
param_req("3", json_tok_u64, &ints[3]),
|
||||
four = param_opt("4", json_tok_u64, &ints[4]),
|
||||
five = param_opt("5", json_tok_u64, &ints[5]),
|
||||
param_opt("6", json_tok_u64, &ints[6]), NULL));
|
||||
assert(ints[4] == 14);
|
||||
assert(param_is_set(four) == &ints[4]);
|
||||
assert(!param_is_set(five));
|
||||
assert(ints[5] == 42);
|
||||
assert(ints[6] == 42);
|
||||
}
|
||||
|
||||
#if DEVELOPER
|
||||
jmp_buf jump;
|
||||
static void handle_abort(int sig)
|
||||
{
|
||||
longjmp(jump, 1);
|
||||
}
|
||||
|
||||
static void set_assert(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
memset(&act, '\0', sizeof(act));
|
||||
act.sa_handler = &handle_abort;
|
||||
if (sigaction(SIGABRT, &act, NULL) < 0) {
|
||||
perror("set_assert");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_assert(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
memset(&act, '\0', sizeof(act));
|
||||
act.sa_handler = SIG_DFL;
|
||||
if (sigaction(SIGABRT, &act, NULL) < 0) {
|
||||
perror("reset_assert");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to make sure there are no programming mistakes.
|
||||
*/
|
||||
static void bad_programmer(void)
|
||||
{
|
||||
u64 ival;
|
||||
u64 ival2;
|
||||
double dval;
|
||||
set_assert();
|
||||
struct json *j = json_parse(cmd, "[ '25', '546', '26' ]");
|
||||
|
||||
/* check for repeated names */
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("repeat", json_tok_u64, &ival),
|
||||
param_req("double", json_tok_double, &dval),
|
||||
param_req("repeat", json_tok_u64, &ival2), NULL);
|
||||
/* shouldn't get here */
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("repeat", json_tok_u64, &ival),
|
||||
param_req("double", json_tok_double, &dval),
|
||||
param_req("repeat", json_tok_u64, &ival), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", json_tok_u64, &ival),
|
||||
param_req("repeat", json_tok_double, &dval),
|
||||
param_req("repeat", json_tok_double, &dval), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/* check for repeated arguments */
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", json_tok_u64, &ival),
|
||||
param_req("repeated-arg", json_tok_u64, &ival),
|
||||
NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/* check for NULL input */
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req(NULL, json_tok_u64, &ival), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req(NULL, json_tok_u64, &ival), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (setjmp(jump) == 0) {
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", NULL, &ival), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (setjmp(jump) == 0) {
|
||||
/* Add required param after optional */
|
||||
struct json *j =
|
||||
json_parse(cmd, "[ '25', '546', '26', '1.1' ]");
|
||||
unsigned int msatoshi;
|
||||
double riskfactor;
|
||||
param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("u64", json_tok_u64, &ival),
|
||||
param_req("double", json_tok_double, &dval),
|
||||
param_opt("msatoshi", json_tok_number, &msatoshi),
|
||||
param_req("riskfactor", json_tok_double,
|
||||
&riskfactor), NULL);
|
||||
restore_assert();
|
||||
assert(false);
|
||||
}
|
||||
restore_assert();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void add_members(struct param **params,
|
||||
struct json_result *obj,
|
||||
struct json_result *arr, unsigned int *ints)
|
||||
{
|
||||
char name[256];
|
||||
for (int i = 0; i < tal_count(ints); ++i) {
|
||||
sprintf(name, "%d", i);
|
||||
json_add_num(obj, name, i);
|
||||
json_add_num(arr, NULL, i);
|
||||
params[i] = param_req(name, json_tok_number, &ints[i]);
|
||||
tal_steal(params, params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A roundabout way of initializing an array of ints to:
|
||||
* ints[0] = 0, ints[1] = 1, ... ints[499] = 499
|
||||
*/
|
||||
static void five_hundred_params(void)
|
||||
{
|
||||
struct param **params = tal_arr(NULL, struct param *, 500);
|
||||
|
||||
unsigned int *ints = tal_arr(params, unsigned int, 500);
|
||||
struct json_result *obj = new_json_result(params);
|
||||
struct json_result *arr = new_json_result(params);
|
||||
json_object_start(obj, NULL);
|
||||
json_array_start(arr, NULL);
|
||||
add_members(params, obj, arr, ints);
|
||||
json_object_end(obj);
|
||||
json_array_end(arr);
|
||||
|
||||
/* first test object version */
|
||||
struct json *j = json_parse(params, obj->s);
|
||||
assert(param_parse_arr(cmd, j->buffer, j->toks, params));
|
||||
for (int i = 0; i < tal_count(ints); ++i) {
|
||||
assert(ints[i] == i);
|
||||
ints[i] = 65535;
|
||||
}
|
||||
|
||||
/* now test array */
|
||||
j = json_parse(params, arr->s);
|
||||
assert(param_parse_arr(cmd, j->buffer, j->toks, params));
|
||||
for (int i = 0; i < tal_count(ints); ++i) {
|
||||
assert(ints[i] == i);
|
||||
}
|
||||
|
||||
tal_free(params);
|
||||
}
|
||||
|
||||
static void sendpay(void)
|
||||
{
|
||||
struct json *j = json_parse(cmd, "[ 'A', '123', 'hello there' '547']");
|
||||
|
||||
const jsmntok_t *routetok, *note;
|
||||
u64 msatoshi;
|
||||
unsigned cltv;
|
||||
struct param * mp;
|
||||
|
||||
if (!param_parse(cmd, j->buffer, j->toks,
|
||||
param_req("route", json_tok_tok, &routetok),
|
||||
param_req("cltv", json_tok_number, &cltv),
|
||||
param_opt("note", json_tok_tok, ¬e),
|
||||
mp = param_opt("msatoshi", json_tok_u64, &msatoshi),
|
||||
NULL))
|
||||
assert(false);
|
||||
|
||||
assert(param_is_set(mp));
|
||||
assert(msatoshi == 547);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setup_locale();
|
||||
cmd = tal(NULL, struct command);
|
||||
fail_msg = tal_arr(cmd, char, 10000);
|
||||
|
||||
zero_params();
|
||||
sanity();
|
||||
tok_tok();
|
||||
null_params();
|
||||
#if DEVELOPER
|
||||
bad_programmer();
|
||||
#endif
|
||||
dup();
|
||||
five_hundred_params();
|
||||
sendpay();
|
||||
tal_free(cmd);
|
||||
printf("run-params ok\n");
|
||||
}
|
Loading…
Reference in New Issue
Block a user