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' ""