From 9914847d6a88dd563a4ad88ddaac0f987cf6a94b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 27 Jan 2018 13:32:51 +0100 Subject: [PATCH] GET Single element JSON lists use {list:[element]}, not {list:element}. --- CHANGELOG.md | 8 +++- lib/src/clixon_json.c | 106 +++++++++++++++++++++++------------------ test/test_restconf.sh | 12 ++--- test/test_restconf2.sh | 10 ++-- 4 files changed, 76 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e57114f..ccd08e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## 3.5.0 (Upcoming) ### Major changes: +* Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right. + * GET well-known, top-level resource, yang library version, + * PUT whole datastore, check for different keys in put lists. + * GET Single element JSON lists use {list:[element]}, not {list:element}. + ### Minor changes: @@ -11,9 +16,8 @@ * Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed. * Clixon XML C-lib prior to 3.4.0. As enabled with `configure --with-xml-compat` -* new configuration option: CLICON_RESTCONF_PRETTY +* New configuration option: CLICON_RESTCONF_PRETTY * Changed restconf GET to return object referenced. ie, GET /restconf/data/X returns X. Thanks Stephen Jones for getting this right. -* Restconf: get well-known, top-level resource, yang library version, put whole datastore, check for different keys in put lists. * Default configure file added by Matt Smith. Config file is selected in the following priority order: * Provide -f option when starting a program. diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 50fd06a4..2605468e 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -77,9 +77,10 @@ enum array_element_type{ NO_ARRAY=0, - FIRST_ARRAY, - MIDDLE_ARRAY, - LAST_ARRAY, + FIRST_ARRAY, /* [a, */ + MIDDLE_ARRAY, /* a, */ + LAST_ARRAY, /* a] */ + SINGLE_ARRAY, /* [a] */ BODY_ARRAY }; @@ -143,6 +144,9 @@ arraytype2str(enum array_element_type lt) case LAST_ARRAY: return "last"; break; + case SINGLE_ARRAY: + return "single"; + break; case BODY_ARRAY: return "body"; break; @@ -152,21 +156,23 @@ arraytype2str(enum array_element_type lt) static enum array_element_type array_eval(cxobj *xprev, - cxobj *x, - cxobj *xnext) + cxobj *x, + cxobj *xnext) { enum array_element_type array = NO_ARRAY; - int eqprev=0; - int eqnext=0; + int eqprev=0; + int eqnext=0; + yang_stmt *ys; if (xml_type(x)!=CX_ELMNT){ array=BODY_ARRAY; goto done; } + ys = xml_spec(x); if (xnext && xml_type(xnext)==CX_ELMNT && strcmp(xml_name(x),xml_name(xnext))==0) - eqnext++; + eqnext++; if (xprev && xml_type(xprev)==CX_ELMNT && strcmp(xml_name(x),xml_name(xprev))==0) @@ -177,6 +183,8 @@ array_eval(cxobj *xprev, array = LAST_ARRAY; else if (eqnext) array = FIRST_ARRAY; + else if (ys && ys->ys_keyword == Y_LIST) + array = SINGLE_ARRAY; else array = NO_ARRAY; done: @@ -228,29 +236,29 @@ json_escape(char *str) * The following matrix explains how the mapping is done. * You need to understand what arraytype means (no/first/middle/last) * and what childtype is (null,body,any) - +---------+--------------+--------------+--------------+ + +----------+--------------+--------------+--------------+ |array,leaf| null | body | any | - +---------+--------------+--------------+--------------+ - |no | |1 | | - | | | | | - | json: |\ta:null |\ta: |\ta:{\n | - | | | |\n} | - +---------+--------------+--------------+--------------+ - |first |11 |..a>1 | | - | | | | | - | json: |\tnull |\t |\t{a | - | |\n\t] |\n\t] |\n\t}\t] | - +---------+--------------+--------------+--------------+ + +----------+--------------+--------------+--------------+ + |no | |1 | | + | | | | | + | json: |\ta:null |\ta: |\ta:{\n | + | | | |\n} | + +----------+--------------+--------------+--------------+ + |first |11 |..a>1 | | + | | | | | + | json: |\tnull |\t |\t{a | + | |\n\t] |\n\t] |\n\t}\t] | + +----------+--------------+--------------+--------------+ */ static int xml2json1_cbuf(cbuf *cb, @@ -299,6 +307,7 @@ xml2json1_cbuf(cbuf *cb, } break; case FIRST_ARRAY: + case SINGLE_ARRAY: cprintf(cb, "%*s\"%s\": ", pretty?(level*JSON_INDENT):0, "", xml_name(x)); @@ -387,6 +396,7 @@ xml2json1_cbuf(cbuf *cb, break; } break; + case SINGLE_ARRAY: case LAST_ARRAY: switch (childt){ case NULL_CHILD: @@ -419,7 +429,7 @@ xml2json1_cbuf(cbuf *cb, * @param[in,out] cb Cligen buffer to write to * @param[in] x XML tree to translate from * @param[in] pretty Set if output is pretty-printed - * @param[in] top By default only children are printed, set if include top + * @param[in] top By default only children are printed, set if include top * @retval 0 OK * @retval -1 Error * @@ -433,9 +443,9 @@ xml2json1_cbuf(cbuf *cb, * @see clicon_xml2cbuf */ int -xml2json_cbuf(cbuf *cb, - cxobj *x, - int pretty) +xml2json_cbuf(cbuf *cb, + cxobj *x, + int pretty) { int retval = 1; int level = 0; @@ -472,10 +482,10 @@ xml2json_cbuf(cbuf *cb, * @see xml2json1_cbuf */ int -xml2json_cbuf_vec(cbuf *cb, - cxobj **vec, - size_t veclen, - int pretty) +xml2json_cbuf_vec(cbuf *cb, + cxobj **vec, + size_t veclen, + int pretty) { int retval = -1; int level = 0; @@ -519,15 +529,17 @@ xml2json_cbuf_vec(cbuf *cb, * @retval 0 OK * @retval -1 Error * + * @note yang is necessary to translate to one-member lists, + * eg if a is a yang LIST 0 -> {"a":["0"]} and not {"a":"0"} * @code * if (xml2json(stderr, xn, 0) < 0) * goto err; * @endcode */ int -xml2json(FILE *f, - cxobj *x, - int pretty) +xml2json(FILE *f, + cxobj *x, + int pretty) { int retval = 1; cbuf *cb = NULL; @@ -560,10 +572,10 @@ xml2json(FILE *f, * @see xml2json1_cbuf */ int -xml2json_vec(FILE *f, - cxobj **vec, - size_t veclen, - int pretty) +xml2json_vec(FILE *f, + cxobj **vec, + size_t veclen, + int pretty) { int retval = 1; cbuf *cb = NULL; @@ -645,7 +657,7 @@ json_parse_str(char *str, /*! Read a JSON definition from file and parse it into a parse-tree. * * @param[in] fd A file descriptor containing the JSON file (as ASCII characters) - * @param[in] yspec Yang specification, or NULL + * @param[in] yspec Yang specification, or NULL XXX Not yet used * @param[in,out] xt Pointer to (XML) parse tree. If empty, create. * @retval 0 OK * @retval -1 Error with clicon_err called @@ -722,7 +734,7 @@ json_parse_file(int fd, /* * Turn this on to get a json parse and pretty print test program * Usage: xpath - * read xml from input + * read json from input * Example compile: gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen * Example run: diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c1d7ee0c..8f429ef1 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -54,7 +54,7 @@ module example{ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}}' +state='{"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": "42"}\]}}' # kill old backend (if any) new "kill old backend" @@ -109,7 +109,7 @@ if [ -z "$match" ]; then fi new "restconf get data/interfaces-state/interface=eth0 json" -expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0" '{"interface": {"name": "eth0","type": "eth","if-index": "42"}}' +expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0" '{"interface": \[{"name": "eth0","type": "eth","if-index": "42"}\]}' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh @@ -137,7 +137,7 @@ new "restconf Add subtree to datastore using POST" expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": "true"}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": "42"}\]}} $' new "restconf delete interfaces" @@ -150,7 +150,7 @@ new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" new "restconf Check eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": "true"}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": "42"}\]}} $' new "restconf Re-post eth/0/0 which should generate error" @@ -163,7 +163,7 @@ new "Add nothing using POST" expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "data is in some way badly formed" new "restconf Check description added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}} +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}\]} $' new "restconf delete eth/0/0" @@ -179,7 +179,7 @@ new "restconf Add subtree eth/0/0 using PUT" expectfn 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "restconf get subtree" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}},"interfaces-state": {"interface": {"name": "eth0","type": "eth","if-index": "42"}}} +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": "true"}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": "42"}\]}} $' new "restconf rpc using POST json" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index aa939976..e9d709f1 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -66,10 +66,10 @@ new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","type": "regular"}}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0" '{"interface": {"name": "local0","type": "regular"}}' +expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' new "restconf GET if-type" expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0/type" '{"type": "regular"}' @@ -97,13 +97,13 @@ new "restconf PUT initial datastore" expectfn 'curl -s -X PUT -d {"data":{"interfaces-config":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","type": "regular"}}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf PUT change interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/interfaces-config/interface=local0' "" -new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","type": "atm0"}}}}' +new "restconf GET datastore atm" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' new "restconf PUT add interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' ""