diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f922a98..93ec6d711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ Expected: March 2022 Users may have to change how they access the system +* RESTCONF replies on the form: `{"data":...}` changed to: `{"ietf-restconf:data":...}` + * See [restconf GET json response does not encode top level node with namespace as per rfc #303](https://github.com/clicon/clixon/issues/303) * YANG leafref `require-instance` default changed to `true` * This makes leafref validation stricter * See [statement: require-instance should be true if not present according to rfc7950 Sec 9.9.3](https://github.com/clicon/clixon/issues/302) @@ -62,6 +64,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [restconf GET json response does not encode top level node with namespace as per rfc #303](https://github.com/clicon/clixon/issues/303) * Fixed: [statement: require-instance should be true if not present according to rfc7950 Sec 9.9.3](https://github.com/clicon/clixon/issues/302) * See also API changes * Fixed: input RPC validation of choice (non-case) diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index cb27dfb1d..734a30c41 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -238,6 +238,7 @@ api_yang_library_version(clicon_handle h, int retval = -1; cxobj *xt = NULL; cbuf *cb = NULL; + yang_stmt *yspec; clicon_debug(1, "%s", __FUNCTION__); if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) @@ -250,6 +251,9 @@ api_yang_library_version(clicon_handle h, goto done; if (xml_rootchild(xt, 0, &xt) < 0) goto done; + yspec = clicon_dbspec_yang(h); + if (xml_bind_special(xt, yspec, "/rc:restconf/yang-library-version") < 0) + goto done; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; diff --git a/lib/clixon/clixon_xml_bind.h b/lib/clixon/clixon_xml_bind.h index 0d0b05665..1b609128a 100644 --- a/lib/clixon/clixon_xml_bind.h +++ b/lib/clixon/clixon_xml_bind.h @@ -49,5 +49,6 @@ int xml_bind_yang_rpc(cxobj *xrpc, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang_rpc_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang0(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); +int xml_bind_special(cxobj *xd, yang_stmt *yspec, char *schema_nodeid); #endif /* _CLIXON_XML_BIND_H_ */ diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index c10cde885..431b46e58 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -784,6 +784,11 @@ xml2json1_cbuf(cbuf *cb, if (ys_real_module(ys, &ymod) < 0) goto done; modname = yang_argument_get(ymod); + /* Special case for ietf-netconf -> ietf-restconf translation + * A special case is for return data on the form {"data":...} + */ + if (strcmp(modname, "ietf-netconf")==0) + modname = "ietf-restconf"; if (modname0 && strcmp(modname, modname0) == 0) modname=NULL; else @@ -1065,7 +1070,6 @@ xml2json_cbuf(cbuf *cb, pretty?"\n":"", pretty?level*JSON_INDENT:0,"", pretty?"\n":""); - retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 2dd54b331..4df076a8e 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -466,15 +466,19 @@ clicon_rpc_get_config(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; + yspec = clicon_dbspec_yang(h); /* Send xml error back: first check error, then ok */ if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xd = xml_parent(xd); /* point to rpc-reply */ else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){ if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL) goto done; + if (xml_bind_special(xd, yspec, "/nc:get-config/output/data") < 0) + goto done; } else{ - yspec = clicon_dbspec_yang(h); + if (xml_bind_special(xd, yspec, "/nc:get-config/output/data") < 0) + goto done; if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0) goto done; if (ret == 0){ @@ -838,15 +842,19 @@ clicon_rpc_get(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; + yspec = clicon_dbspec_yang(h); /* Send xml error back: first check error, then ok */ if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xd = xml_parent(xd); /* point to rpc-reply */ else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){ if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL) goto done; + if (xml_bind_special(xd, yspec, "/nc:get/output/data") < 0) + goto done; } else{ - yspec = clicon_dbspec_yang(h); + if (xml_bind_special(xd, yspec, "/nc:get/output/data") < 0) + goto done; if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0) goto done; if (ret == 0){ @@ -974,15 +982,19 @@ clicon_rpc_get_pageable_list(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; + yspec = clicon_dbspec_yang(h); /* Send xml error back: first check error, then ok */ if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) xd = xml_parent(xd); /* point to rpc-reply */ else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){ if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL) goto done; + if (xml_bind_special(xd, yspec, "/nc:get/output/data") < 0) + goto done; } else{ - yspec = clicon_dbspec_yang(h); + if (xml_bind_special(xd, yspec, "/nc:get/output/data") < 0) + goto done; if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0) goto done; if (ret == 0){ diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 11c563ac8..57237a863 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -752,3 +752,22 @@ xml_bind_yang_rpc_reply(cxobj *xrpc, retval = 0; goto done; } + +/*! Special case explicit binding + */ +int +xml_bind_special(cxobj *xd, + yang_stmt *yspec, + char *schema_nodeid) +{ + int retval = -1; + yang_stmt *yd; + + if (yang_abs_schema_nodeid(yspec, schema_nodeid, &yd) < 0) + goto done; + if (yd) + xml_spec_set(xd, yd); + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 0a635307e..2513dc254 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1170,7 +1170,9 @@ yang_find_schemanode(yang_stmt *yn, } /* Y_CHOICE */ else if (yang_schemanode(ys)){ - if (yang_keyword_get(ys) == Y_INPUT || yang_keyword_get(ys) == Y_OUTPUT) + if (strcmp(argument, "input") == 0 && yang_keyword_get(ys) == Y_INPUT) + ysmatch = ys; + else if (strcmp(argument, "output") == 0 && yang_keyword_get(ys) == Y_OUTPUT) ysmatch = ys; else if (argument == NULL) ysmatch = ys; @@ -3122,7 +3124,7 @@ schema_nodeid_iterate(yang_stmt *yn, goto ok; } yp = ys; /* ys is matched */ - + ys = NULL; } /* while cv */ assert(yp && yang_schemanode((yang_stmt*)yp)); *yres = (yang_stmt*)yp; diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index d137032c4..f4b0d5c48 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -206,7 +206,7 @@ new "wait restconf" wait_restconf new "auth get" -expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"ietf-restconf:data":{"clixon-example:state":{"op":\["41","42","43"\]}' new "Set x to 0" expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/$HVER 201" diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 17327b229..9d1659c3e 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -229,7 +229,7 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/da new "admin read top ok (all)" ret=$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data) -expect='{"data":{"clixon-example:table":' +expect='{"ietf-restconf:data":{"clixon-example:table":' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -253,7 +253,7 @@ new "limit read state OK" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "limit read top ok (part)" -expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"data":{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]},"clixon-example:state":{"op":\["41","42","43"\]}}}' +expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '{"ietf-restconf:data":{"clixon-example:table":{"parameter":\[{"name":"key42","value":"val42"},{"name":"key43","value":"val43"}\]},"clixon-example:state":{"op":\["41","42","43"\]}}}' #user:guest diff --git a/test/test_pagination_draft.sh b/test/test_pagination_draft.sh index 57f7ab10e..3d3146a31 100755 --- a/test/test_pagination_draft.sh +++ b/test/test_pagination_draft.sh @@ -44,6 +44,7 @@ cat < $cfg EOF # See draft-wwlh-netconf-list-pagination-00 A.2 (except stats and audit-log) +# XXX: "config" without cat <<'EOF' > $dir/startup_db {"config": { diff --git a/test/test_restconf.sh b/test/test_restconf.sh index d75ec0a49..8ab6d680e 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -327,7 +327,7 @@ function testrun() fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" - expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 "HTTP/$HVER 200" '{"yang-library-version":"2019-01-04"}' + expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 "HTTP/$HVER 200" '{"ietf-restconf:yang-library-version":"2019-01-04"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/yang-library-version) diff --git a/test/test_submodule.sh b/test/test_submodule.sh index 12c3de6f1..27e91f8e6 100755 --- a/test/test_submodule.sh +++ b/test/test_submodule.sh @@ -247,7 +247,7 @@ new "restconf edit sub2" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 "HTTP/$HVER 201" new "restconf check main/sub1/sub2 contents" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data?content=config)" 0 "HTTP/$HVER 200" '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data?content=config)" 0 "HTTP/$HVER 200" '{"ietf-restconf:data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}' new "restconf edit augment 0" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/main:sub2 -d '{"main:aug0":"foo"}')" 0 "HTTP/$HVER 201"