* Identity/identityref mapped between XML and JSON

This commit is contained in:
Olof hagsand 2019-07-28 18:09:55 +02:00
parent ccc95b2826
commit 70ebfa4d80
22 changed files with 779 additions and 133 deletions

View file

@ -7,12 +7,13 @@
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources * `201 Created` for created resources
* `204 No Content` for replaced resources. * `204 No Content` for replaced resources.
* identity/identityref mapped between XML and JSON
* XML uses prefixes, JSON uses module-names (previously prefixes were used in both cases)
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56) * See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
* Implementation detail: due to difference between RESTCONF and NETCONF semantics, a PUT first to make en internal netconf edit-config create operation; if that fails, a replace operation is tried. * Implementation detail: due to difference between RESTCONF and NETCONF semantics, a PUT first to make en internal netconf edit-config create operation; if that fails, a replace operation is tried.
* HTTP `Location:` fields added in RESTCONF POST replies * HTTP `Location:` fields added in RESTCONF POST replies
* HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5) * HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5)
* Restconf monitoring capabilities (RFC Section 9.1) * Restconf monitoring capabilities (RFC Section 9.1)
*
* Yang extensions support * Yang extensions support
* New plugin callback: ca_extension * New plugin callback: ca_extension
* The main example explains how to implement a Yang extension in a backend plugin. * The main example explains how to implement a Yang extension in a backend plugin.
@ -21,6 +22,8 @@
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources * `201 Created` for created resources
* `204 No Content` for replaced resources. * `204 No Content` for replaced resources.
* RESTCONF identities has been changed to use module names instead of prefixes.
* Eg, `curl -X POST -d '{"type":"ex:eth"}` --> `curl -X POST -d '{"type":"ietf-interfaces:eth"`}
* JSON changes * JSON changes
* Non-pretty-print output removed all extra spaces. * Non-pretty-print output removed all extra spaces.
* Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}` * Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`

View file

@ -78,6 +78,7 @@ APPSRC = restconf_main.c
APPSRC += restconf_methods.c APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c APPSRC += restconf_methods_get.c
APPSRC += restconf_methods_patch.c
APPSRC += restconf_stream.c APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o) APPOBJ = $(APPSRC:.c=.o)

View file

@ -78,8 +78,9 @@
/* restconf */ /* restconf */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_methods.h" #include "restconf_methods.h"
#include "restconf_methods_post.h"
#include "restconf_methods_get.h" #include "restconf_methods_get.h"
#include "restconf_methods_post.h"
#include "restconf_methods_patch.h"
#include "restconf_stream.h" #include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */

View file

@ -288,7 +288,7 @@ api_data_put(clicon_handle h,
char *namespace0; char *namespace0;
char *dname; char *dname;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"",
__FUNCTION__, api_path0, data); __FUNCTION__, api_path0, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
@ -336,6 +336,12 @@ api_data_put(clicon_handle h,
} }
} }
else{ else{
/* Data here cannot cannot be Yang populated since it is loosely
* hanging without top symbols.
* And if it is not yang populated, it cant be translated properly
* from JSON to XML.
* Therefore, yang population is done later after addsub below
*/
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done; goto done;
@ -487,6 +493,21 @@ api_data_put(clicon_handle h,
xml_purge(xbot); xml_purge(xbot);
if (xml_addsub(xparent, xdata) < 0) if (xml_addsub(xparent, xdata) < 0)
goto done; goto done;
/* xbot is already populated, resolve yang for added xdata too */
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (!parse_xml){
/* json2xml decode could not be done above in json_parse,
need to be done here instead */
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
goto done;
if (ret == 0){
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* If we already have that default namespace, remove it in child */ /* If we already have that default namespace, remove it in child */
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){ if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){
if (xml2ns(xparent, NULL, &namespace0) < 0) if (xml2ns(xparent, NULL, &namespace0) < 0)
@ -495,6 +516,7 @@ api_data_put(clicon_handle h,
if (strcmp(namespace0, xml_value(xa))==0) if (strcmp(namespace0, xml_value(xa))==0)
xml_purge(xa); xml_purge(xa);
} }
} }
/* Create text buffer for transfer to backend */ /* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
@ -613,29 +635,6 @@ api_data_put(clicon_handle h,
return retval; return retval;
} /* api_data_put */ } /* api_data_put */
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8040 Sec 4.6
*/
int
api_data_patch(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
notimplemented(r);
return 0;
}
/*! Generic REST DELETE method translated to edit-config /*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle * @param[in] h CLIXON handle

View file

@ -46,9 +46,6 @@ int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi, cvec *pcvec, int pi,
cvec *qvec, char *data, cvec *qvec, char *data,
int pretty, int use_xml, int parse_xml); int pretty, int use_xml, int parse_xml);
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data);
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
int pretty, int use_xml); int pretty, int use_xml);

View file

@ -60,7 +60,6 @@
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */ #include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_methods.h"
#include "restconf_methods_get.h" #include "restconf_methods_get.h"
/*! Generic GET (both HEAD and GET) /*! Generic GET (both HEAD and GET)
@ -187,6 +186,17 @@ api_data_get2(clicon_handle h,
goto done; goto done;
} }
else{ else{
#if 0
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0)
goto done;
clicon_debug(1, "%s xret: %s",
__FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
if (xml2json_cbuf(cbx, xret, pretty) < 0) if (xml2json_cbuf(cbx, xret, pretty) < 0)
goto done; goto done;
} }

View file

@ -0,0 +1,151 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
/*
* See rfc8040
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
* sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
* api/test
+----------------------------+--------------------------------------+
| 100 Continue | POST accepted, 201 should follow |
| 200 OK | Success with response message-body |
| 201 Created | POST to create a resource success |
| 204 No Content | Success without response message- |
| | body |
| 304 Not Modified | Conditional operation not done |
| 400 Bad Request | Invalid request message |
| 401 Unauthorized | Client cannot be authenticated |
| 403 Forbidden | Access to resource denied |
| 404 Not Found | Resource target or resource node not |
| | found |
| 405 Method Not Allowed | Method not allowed for target |
| | resource |
| 409 Conflict | Resource or lock in use |
| 412 Precondition Failed | Conditional method is false |
| 413 Request Entity Too | too-big error |
| Large | |
| 414 Request-URI Too Large | too-big error |
| 415 Unsupported Media Type | non RESTCONF media type |
| 500 Internal Server Error | operation-failed |
| 501 Not Implemented | unknown-operation |
| 503 Service Unavailable | Recoverable server error |
+----------------------------+--------------------------------------+
Mapping netconf error-tag -> status code
+-------------------------+-------------+
| <error&#8209;tag> | status code |
+-------------------------+-------------+
| in-use | 409 |
| invalid-value | 400 |
| too-big | 413 |
| missing-attribute | 400 |
| bad-attribute | 400 |
| unknown-attribute | 400 |
| bad-element | 400 |
| unknown-element | 400 |
| unknown-namespace | 400 |
| access-denied | 403 |
| lock-denied | 409 |
| resource-denied | 409 |
| rollback-failed | 500 |
| data-exists | 409 |
| data-missing | 409 |
| operation-not-supported | 501 |
| operation-failed | 500 |
| partial-operation | 500 |
| malformed-message | 400 |
+-------------------------+-------------+
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_methods_patch.h"
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8040 Sec 4.6
*/
int
api_data_patch(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
notimplemented(r);
return 0;
}

View file

@ -0,0 +1,48 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Restconf method implementation
*/
#ifndef _RESTCONF_METHODS_PATCH_H_
#define _RESTCONF_METHODS_PATCH_H_
/*
* Prototypes
*/
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data);
#endif /* _RESTCONF_METHODS_PATCH_H_ */

View file

@ -62,7 +62,6 @@
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */ #include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
/*! Generic REST POST method /*! Generic REST POST method
@ -129,9 +128,10 @@ api_data_post(clicon_handle h,
cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xe; /* dont free */ cxobj *xe; /* dont free */
char *username; char *username;
int nullspec = 0;
int ret; int ret;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"",
__FUNCTION__, __FUNCTION__,
api_path, data); api_path, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -178,6 +178,15 @@ api_data_post(clicon_handle h,
} }
} }
else { else {
/* Data here cannot cannot (always) be Yang populated since it is
* loosely hanging without top symbols.
* And if it is not yang populated, it cant be translated properly
* from JSON to XML.
* Therefore, yang population is done later after addsub below
* Further complication is that if data is root resource, then it will
* work, so I need to check below that it didnt.
* THIS could be simplified.
*/
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done; goto done;
@ -245,9 +254,24 @@ api_data_post(clicon_handle h,
/* Replace xbot with x, ie bottom of api-path with data */ /* Replace xbot with x, ie bottom of api-path with data */
if (xml_addsub(xbot, xdata) < 0) if (xml_addsub(xbot, xdata) < 0)
goto done; goto done;
/* xbot is already populated, resolve yang for added xdata too */ /* xbot is already populated, resolve yang for added xdata too
if (xml_spec_populate(xdata, yspec) < 0) */
nullspec = (xml_spec(xdata) == NULL);
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done; goto done;
if (!parse_xml && nullspec){
/* json2xml decode may not have been done above in json_parse,
need to be done here instead
UNLESS it is a root resource, then json-parse does right
*/
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
goto done;
if (ret == 0){
if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* Create text buffer for transfer to backend */ /* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL){ if ((cbx = cbuf_new()) == NULL){

View file

@ -57,4 +57,4 @@
* The easy way to do this is to always generate all prefix/namespace bindings * The easy way to do this is to always generate all prefix/namespace bindings
* on the top-level for the modules involved in the netconf operation. * on the top-level for the modules involved in the netconf operation.
*/ */
#undef IDENTITYREF_KLUDGE #define IDENTITYREF_KLUDGE

View file

@ -41,6 +41,7 @@
/* /*
* Prototypes * Prototypes
*/ */
int json2xml_decode(cxobj *x, cxobj **xerr);
int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty);
int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty);
int xml2json(FILE *f, cxobj *x, int pretty); int xml2json(FILE *f, cxobj *x, int pretty);

View file

@ -170,6 +170,7 @@ yang_stmt *ys_module(yang_stmt *ys);
yang_stmt *ys_real_module(yang_stmt *ys); yang_stmt *ys_real_module(yang_stmt *ys);
yang_stmt *ys_spec(yang_stmt *ys); yang_stmt *ys_spec(yang_stmt *ys);
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
yang_stmt *yang_find_module_by_prefix_yspec(yang_stmt *yspec, char *prefix);
yang_stmt *yang_find_module_by_namespace(yang_stmt *yspec, char *namespace); yang_stmt *yang_find_module_by_namespace(yang_stmt *yspec, char *namespace);
yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name); yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name);
yang_stmt *yang_find(yang_stmt *yn, int keyword, const char *argument); yang_stmt *yang_find(yang_stmt *yn, int keyword, const char *argument);
@ -178,6 +179,7 @@ yang_stmt *yang_find_datanode(yang_stmt *yn, char *argument);
yang_stmt *yang_find_schemanode(yang_stmt *yn, char *argument); yang_stmt *yang_find_schemanode(yang_stmt *yn, char *argument);
char *yang_find_myprefix(yang_stmt *ys); char *yang_find_myprefix(yang_stmt *ys);
char *yang_find_mynamespace(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys);
int yang_find_prefix_by_namespace(yang_stmt *ys, char *namespace, char **prefix);
yang_stmt *yang_choice(yang_stmt *y); yang_stmt *yang_choice(yang_stmt *y);
int yang_order(yang_stmt *y); int yang_order(yang_stmt *y);
int yang_print(FILE *f, yang_stmt *yn); int yang_print(FILE *f, yang_stmt *yn);

View file

@ -36,6 +36,10 @@
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
*/ */
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -64,6 +68,7 @@
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xml_sort.h" #include "clixon_xml_sort.h"
#include "clixon_xml_map.h" #include "clixon_xml_map.h"
#include "clixon_xml_nsctx.h" /* namespace context */
#include "clixon_netconf_lib.h" #include "clixon_netconf_lib.h"
#include "clixon_json.h" #include "clixon_json.h"
#include "clixon_json_parse.h" #include "clixon_json_parse.h"
@ -224,7 +229,8 @@ array_eval(cxobj *xprev,
} }
/*! Escape a json string as well as decode xml cdata /*! Escape a json string as well as decode xml cdata
* And a * @param[out] cb cbuf (encoded)
* @param[in] str string (unencoded)
*/ */
static int static int
json_str_escape_cdata(cbuf *cb, json_str_escape_cdata(cbuf *cb,
@ -272,25 +278,287 @@ json_str_escape_cdata(cbuf *cb,
return retval; return retval;
} }
/*! If set, quoute the json value with double quotes /*! Decode types from JSON to XML identityrefs
* @þaram[in] xb XML body object * Assume an xml tree where prefix:name have been split into "module":"name"
@ @retval 0 Value should not be quouted, XML value is int, boolean,.. * In other words, from JSON RFC7951 to XML namespace trees
@ @retval 1 Value should be quouted, XML value is string,.. * @param[in] x XML tree. Must be yang populated.
* @param[in] yspec Yang spec
* @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL
* @retval 1 OK
* @retval 0 Invalid, wrt namespace. xerr set
* @retval -1 Error
* @see RFC7951 Sec 4 and 6.8
*/ */
static int static int
jsonvaluestr(cxobj *xb) json2xml_decode_identityref(cxobj *x,
yang_stmt *y,
cxobj **xerr)
{ {
int retval = 1; int retval = -1;
char *namespace;
char *body;
cxobj *xb;
cxobj *xa;
char *prefix = NULL;
char *id = NULL;
yang_stmt *ymod;
yang_stmt *yspec;
cvec *nsc = NULL;
char *prefix2 = NULL;
cbuf *cbv = NULL;
clicon_debug(1, "%s", __FUNCTION__);
yspec = ys_spec(y);
if ((xb = xml_body_get(x)) == NULL)
goto ok;
body = xml_value(xb);
if (nodeid_split(body, &prefix, &id) < 0)
goto done;
/* prefix is a module name -> find module */
if (prefix){
if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL){
namespace = yang_find_mynamespace(ymod);
/* Is this namespace in the xml context?
* (yes) use its prefix (unless it is NULL)
* (no) insert a xmlns:<prefix> statement
* Get the whole namespace context from x
*/
if (xml_nsctx_node(x, &nsc) < 0)
goto done;
clicon_debug(1, "%s prefix:%s body:%s namespace:%s",
__FUNCTION__, prefix, body, namespace);
if (!xml_nsctx_get_prefix(nsc, namespace, &prefix2)){
/* (no) insert a xmlns:<prefix> statement
* Get yang prefix from import statement of my mod */
if (yang_find_prefix_by_namespace(y, namespace, &prefix2) == 0){
#ifndef IDENTITYREF_KLUDGE
/* Just get the prefix from the module's own namespace */
if (netconf_unknown_namespace_xml(xerr, "application",
namespace,
"No local prefix corresponding to namespace") < 0)
goto done;
goto fail;
#endif
}
/* if prefix2 is NULL here, we get the canonical prefix */
if (prefix2 == NULL)
prefix2 = yang_find_myprefix(ymod);
/* Add "xmlns:prefix2=namespace" */
if ((xa = xml_new(prefix2, x, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
if (xml_value_set(xa, namespace) < 0)
goto done;
}
/* Here prefix2 is valid and can be NULL
Change body prefix to prefix2:id */
if ((cbv = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (prefix2)
cprintf(cbv, "%s:%s", prefix2, id);
else
cprintf(cbv, "%s", id);
if (xml_value_set(xb, cbuf_get(cbv)) < 0)
goto done;
}
else{
if (netconf_unknown_namespace_xml(xerr, "application",
prefix,
"No module corresponding to prefix") < 0)
goto done;
goto fail;
}
} /* prefix */
ok:
retval = 1;
done:
if (prefix)
free(prefix);
if (id)
free(id);
if (nsc)
xml_nsctx_free(nsc);
if (cbv)
cbuf_free(cbv);
return retval;
fail:
retval = 0;
goto done;
}
/*! Decode leaf/leaf_list types from JSON to XML after parsing and yang
*
* Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON RFC7951 to XML namespace trees
*
* @param[in] x XML tree. Must be yang populated. After json parsing
* @param[in] yspec Yang spec
* @param[out] xerr Reason for invalid tree returned as netconf err msg or NULL
* @retval 1 OK
* @retval 0 Invalid, wrt namespace. xerr set
* @retval -1 Error
* @see RFC7951 Sec 4 and 6.8
*/
int
json2xml_decode(cxobj *x,
cxobj **xerr)
{
int retval = -1;
yang_stmt *y;
enum rfc_6020 keyword;
cxobj *xc;
int ret;
yang_stmt *ytype;
if ((y = xml_spec(x)) != NULL){
keyword = yang_keyword_get(y);
if (keyword == Y_LEAF || keyword == Y_LEAF_LIST){
if (yang_type_get(y, NULL, &ytype, NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (ytype)
if (strcmp(yang_argument_get(ytype),"identityref")==0){
if ((ret = json2xml_decode_identityref(x, y, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
}
}
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){
if ((ret = json2xml_decode(xc, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Encode leaf/leaf_list identityref type from XML to JSON
* @param[in] x XML body node
* @param[in] body body string
* @param[in] ys Yang spec of parent
* @param[out] cb Encoded string
*/
static int
xml2json_encode_identityref(cxobj *xb,
char *body,
yang_stmt *yp,
cbuf *cb)
{
int retval = -1;
char *prefix = NULL;
char *id = NULL;
char *namespace = NULL;
yang_stmt *ymod;
yang_stmt *yspec;
yang_stmt *my_ymod;
my_ymod = ys_module(yp);
yspec = ys_spec(yp);
if (nodeid_split(body, &prefix, &id) < 0)
goto done;
/* prefix is xml local -> get namespace */
if (xml2ns(xb, prefix, &namespace) < 0)
goto done;
/* We got the namespace, now get the module */
// clicon_debug(1, "%s body:%s prefix:%s namespace:%s", __FUNCTION__, body, prefix, namespace);
#ifdef IDENTITYREF_KLUDGE
if (namespace == NULL){
/* If we dont find namespace here, we assume it is because of a missing
* xmlns that should be there, as a kludge we search for its (own)
* prefix in mymodule.
*/
if ((ymod = yang_find_module_by_prefix_yspec(yspec, prefix)) != NULL)
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
else
cprintf(cb, "%s", id);
}
else
#endif
{
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){
if (ymod == my_ymod)
cprintf(cb, "%s", id);
else{
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
}
}
else
cprintf(cb, "%s", id);
}
retval = 0;
done:
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}
/*! Encode leaf/leaf_list types from XML to JSON
* @param[in] x XML body
* @param[in] ys Yang spec of parent
* @param[out] cb0 Encoded string
*/
static int
xml2json_encode(cxobj *xb,
cbuf *cb0)
{
int retval = -1;
cxobj *xp; cxobj *xp;
yang_stmt *yp; yang_stmt *yp;
enum rfc_6020 keyword; enum rfc_6020 keyword;
yang_stmt *ytype;
char *restype; /* resolved type */
char *origtype=NULL; /* original type */
char *body;
enum cv_type cvtype;
int quote = 1; /* Quote value w string: "val" */
cbuf *cb = NULL; /* the variable itself */
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
body = xml_value(xb);
if ((xp = xml_parent(xb)) == NULL || if ((xp = xml_parent(xb)) == NULL ||
(yp = xml_spec(xp)) == NULL) (yp = xml_spec(xp)) == NULL){
goto done; /* unknown */ cprintf(cb, "%s", body);
goto ok; /* unknown */
}
keyword = yang_keyword_get(yp); keyword = yang_keyword_get(yp);
if ((keyword == Y_LEAF || keyword == Y_LEAF_LIST)) switch (keyword){
switch (yang_type2cv(yp)){ case Y_LEAF:
case Y_LEAF_LIST:
if (yang_type_get(yp, &origtype, &ytype, NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
restype = ytype?yang_argument_get(ytype):NULL;
cvtype = yang_type2cv(yp);
switch (cvtype){
case CGV_STRING:
if (ytype){
if (strcmp(restype, "identityref")==0){
if (xml2json_encode_identityref(xb, body, yp, cb) < 0)
goto done;
}
else
cprintf(cb, "%s", body);
}
else
cprintf(cb, "%s", body);
break;
case CGV_INT8: case CGV_INT8:
case CGV_INT16: case CGV_INT16:
case CGV_INT32: case CGV_INT32:
@ -301,18 +569,42 @@ jsonvaluestr(cxobj *xb)
case CGV_UINT64: case CGV_UINT64:
case CGV_DEC64: case CGV_DEC64:
case CGV_BOOL: case CGV_BOOL:
retval = 0; cprintf(cb, "%s", body);
quote = 0;
break; break;
default: default:
cprintf(cb, "%s", body);
}
break;
default:
cprintf(cb, "%s", body);
break; break;
} }
ok:
/* write into original cb0
* includign quoting and encoding
*/
if (quote){
cprintf(cb0, "\"");
json_str_escape_cdata(cb0, cbuf_get(cb));
}
else
cprintf(cb0, "%s", cbuf_get(cb));
if (quote)
cprintf(cb0, "\"");
retval = 0;
done: done:
if (cb)
cbuf_free(cb);
if (origtype)
free(origtype);
return retval; return retval;
} }
/*! Do the actual work of translating XML to JSON /*! Do the actual work of translating XML to JSON
* @param[out] cb Cligen text buffer containing json on exit * @param[out] cb Cligen text buffer containing json on exit
* @param[in] x XML tree structure containing XML to translate * @param[in] x XML tree structure containing XML to translate
* @param[in] yp Parent yang spec needed for body
* @param[in] arraytype Does x occur in a array (of its parent) and how? * @param[in] arraytype Does x occur in a array (of its parent) and how?
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] pretty Pretty-print output (2 means debug) * @param[in] pretty Pretty-print output (2 means debug)
@ -381,17 +673,10 @@ xml2json1_cbuf(cbuf *cb,
arraytype2str(arraytype), arraytype2str(arraytype),
childtype2str(childt)); childtype2str(childt));
switch(arraytype){ switch(arraytype){
case BODY_ARRAY:{ case BODY_ARRAY: /* Only place in fn where body is printed */
if (jsonvaluestr(x)) { /* Only print quotation if string-type */ if (xml2json_encode(x, cb) < 0)
cprintf(cb, "\"");
if (json_str_escape_cdata(cb, xml_value(x)) < 0)
goto done; goto done;
cprintf(cb, "\"");
}
else /* No quotation marks */
cprintf(cb, "%s", xml_value(x));
break; break;
}
case NO_ARRAY: case NO_ARRAY:
if (!flat){ if (!flat){
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
@ -739,7 +1024,7 @@ xml2json_vec(FILE *f,
/*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively /*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively
* Assume an xml tree where prefix:name have been split into "module":"name" * Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON RFC7951 to XML namespace trees * In other words, from JSON to XML namespace trees
* *
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[in,out] x XML tree. Translate it in-line * @param[in,out] x XML tree. Translate it in-line
@ -749,8 +1034,9 @@ xml2json_vec(FILE *f,
* @retval -1 Error * @retval -1 Error
* @note the opposite - xml2ns is made inline in xml2json1_cbuf * @note the opposite - xml2ns is made inline in xml2json1_cbuf
* Example: <top><module:input> --> <top><input xmlns=""> * Example: <top><module:input> --> <top><input xmlns="">
* @see RFC7951 Sec 4
*/ */
int static int
json_xmlns_translate(yang_stmt *yspec, json_xmlns_translate(yang_stmt *yspec,
cxobj *x, cxobj *x,
cxobj **xerr) cxobj **xerr)
@ -855,6 +1141,12 @@ json_parse(char *str,
goto done; goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done; goto done;
/* Now find leafs with identityrefs (+transitive) and translate
* prefixes in values to XML namespaces */
if ((ret = json2xml_decode(xt, xerr)) < 0)
goto done;
if (ret == 0) /* XXX necessary? */
goto fail;
} }
retval = 1; retval = 1;
done: done:
@ -871,7 +1163,7 @@ json_parse(char *str,
* *
* @param[in] str String containing JSON * @param[in] str String containing JSON
* @param[in] yspec Yang specification, or NULL * @param[in] yspec Yang specification, or NULL
* @param[in,out] xt On success a top of XML parse tree is created with name 'top' * @param[in,out] xt Top object, if not exists, on success it is created with name 'top'
* @param[out] xerr Reason for invalid returned as netconf err msg * @param[out] xerr Reason for invalid returned as netconf err msg
* *
* @code * @code

View file

@ -279,7 +279,7 @@ nscache_set(cxobj *x,
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @see xmlns_check XXX can these be merged? * @see xmlns_check
* @see xmlns_set cache is set * @see xmlns_set cache is set
* @note, this function uses a cache. * @note, this function uses a cache.
*/ */
@ -307,8 +307,12 @@ xml2ns(cxobj *x,
} }
/* If no parent, return default namespace if defined */ /* If no parent, return default namespace if defined */
#ifdef USE_NETCONF_NS_AS_DEFAULT #ifdef USE_NETCONF_NS_AS_DEFAULT
else else{
if (prefix == NULL)
ns = NETCONF_BASE_NAMESPACE; ns = NETCONF_BASE_NAMESPACE;
else
ns = NULL;
}
#endif #endif
} }
/* Set default namespace cache (since code is at this point, /* Set default namespace cache (since code is at this point,

View file

@ -332,24 +332,6 @@ validate_leafref(cxobj *xt,
goto done; goto done;
} }
/* Get module from its own prefix
* This is really not a valid usecase, a kludge for the identityref derived
* list workaround (IDENTITYREF_KLUDGE)
*/
static yang_stmt *
yang_find_module_by_prefix_yspec(yang_stmt *yspec,
char *prefix)
{
yang_stmt *ymod = NULL;
yang_stmt *yprefix;
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (ymod->ys_keyword == Y_MODULE &&
(yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yang_argument_get(yprefix), prefix) == 0)
return ymod;
return NULL;
}
/*! Validate xml node of type identityref, ensure value is a defined identity /*! Validate xml node of type identityref, ensure value is a defined identity
* Check if a given node has value derived from base identity. This is * Check if a given node has value derived from base identity. This is
@ -2340,6 +2322,7 @@ xml_spec_populate_rpc(clicon_handle h,
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode * @endcode
*/ */
#undef DEBUG
int int
xml_spec_populate(cxobj *x, xml_spec_populate(cxobj *x,
void *arg) void *arg)
@ -2355,17 +2338,36 @@ xml_spec_populate(cxobj *x,
yspec = (yang_stmt*)arg; yspec = (yang_stmt*)arg;
xp = xml_parent(x); xp = xml_parent(x);
name = xml_name(x); name = xml_name(x);
#ifdef DEBUG
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
#endif
if (xp && (yparent = xml_spec(xp)) != NULL) if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode(yparent, name); y = yang_find_datanode(yparent, name);
else if (yspec){ else if (yspec){
if (ys_module_by_xml(yspec, x, &ymod) < 0) if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done; goto done;
/* ymod is "real" module, name may belong to included submodule */ /* ymod is "real" module, name may belong to included submodule */
if (ymod != NULL) if (ymod != NULL){
#ifdef DEBUG
clicon_debug(1, "%s %s mod:%s", __FUNCTION__, name, yang_argument_get(ymod));
#endif
y = yang_find_schemanode(ymod, name); y = yang_find_schemanode(ymod, name);
} }
if (y) #ifdef DEBUG
else
clicon_debug(1, "%s %s mod:NULL", __FUNCTION__, name);
#endif
}
if (y) {
#ifdef DEBUG
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
#endif
xml_spec_set(x, y); xml_spec_set(x, y);
}
#ifdef DEBUG
else
clicon_debug(1, "%s y:NULL", __FUNCTION__);
#endif
retval = 0; retval = 0;
done: done:
return retval; return retval;

View file

@ -795,6 +795,64 @@ yang_find_mynamespace(yang_stmt *ys)
return namespace; return namespace;
} }
/*! Given a yang statement and namespace, find local prefix valid in module
* This is useful if you want to make a "reverse" lookup, you know the
* (global) namespace of a module, but you do not know the local prefix
* used to access it in XML.
* @param[in] ys Yang statement in module tree (or module itself)
* @param[in] namespace Namspace URI as char* pointer into yang tree
* @param[out] prefix Local prefix to access module with (direct pointer)
* @retval 0 not found
* @retval -1 found
* @code
* @note prefix NULL is not returned, if own module, then return its prefix
* char *pfx = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix);
* @endcode
*/
int
yang_find_prefix_by_namespace(yang_stmt *ys,
char *namespace,
char **prefix)
{
int retval = 0; /* not found */
yang_stmt *my_ymod; /* My module */
char *myns; /* My ns */
yang_stmt *yspec;
yang_stmt *ymod;
char *modname = NULL;
yang_stmt *yimport;
yang_stmt *yprefix;
clicon_debug(1, "%s", __FUNCTION__);
/* First check if namespace is my own module */
myns = yang_find_mynamespace(ys);
if (strcmp(myns, namespace) == 0){
*prefix = yang_find_myprefix(ys); /* or NULL? */
goto found;
}
/* Next, find namespaces in imported modules */
yspec = ys_spec(ys);
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL)
goto notfound;
modname = yang_argument_get(ymod);
my_ymod = ys_module(ys);
/* Loop through import statements to find a match with ymod */
yimport = NULL;
while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
if (yang_keyword_get(yimport) == Y_IMPORT &&
strcmp(modname, yang_argument_get(yimport)) == 0){ /* match */
yprefix = yang_find(yimport, Y_PREFIX, NULL);
*prefix = yang_argument_get(yprefix);
goto found;
}
}
notfound:
return retval;
found:
assert(*prefix);
return 1;
}
/*! If a given yang stmt has a choice/case as parent, return the choice statement /*! If a given yang stmt has a choice/case as parent, return the choice statement
*/ */
yang_stmt * yang_stmt *
@ -985,16 +1043,8 @@ ys_module_by_xml(yang_stmt *ysp,
if (ymodp) if (ymodp)
*ymodp = NULL; *ymodp = NULL;
prefix = xml_prefix(xt); prefix = xml_prefix(xt);
if (prefix){ if (xml2ns(xt, prefix, &namespace) < 0) /* prefix may be NULL */
/* Get namespace for prefix */
if (xml2ns(xt, prefix, &namespace) < 0)
goto done; goto done;
}
else{
/* Get default namespace */
if (xml2ns(xt, NULL, &namespace) < 0)
goto done;
}
/* No namespace found, give up */ /* No namespace found, give up */
if (namespace == NULL) if (namespace == NULL)
goto ok; goto ok;
@ -1134,8 +1184,7 @@ yang_find_module_by_prefix(yang_stmt *ys,
} }
yimport = NULL; yimport = NULL;
while ((yimport = yn_each(my_ymod, yimport)) != NULL) { while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
if (yang_keyword_get(yimport) != Y_IMPORT && if (yang_keyword_get(yimport) != Y_IMPORT)
yang_keyword_get(yimport) != Y_INCLUDE)
continue; continue;
if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL && if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL &&
strcmp(yang_argument_get(yprefix), prefix) == 0){ strcmp(yang_argument_get(yprefix), prefix) == 0){
@ -1143,8 +1192,7 @@ yang_find_module_by_prefix(yang_stmt *ys,
} }
} }
if (yimport){ if (yimport){
if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL && if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL){
(ymod = yang_find(yspec, Y_SUBMODULE, yang_argument_get(yimport))) == NULL){
clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
prefix); prefix);
yimport = NULL; yimport = NULL;
@ -1155,6 +1203,25 @@ yang_find_module_by_prefix(yang_stmt *ys,
return ymod; return ymod;
} }
/* Get module from its own prefix
* This is really not a valid usecase, a kludge for the identityref derived
* list workaround (IDENTITYREF_KLUDGE)
*/
yang_stmt *
yang_find_module_by_prefix_yspec(yang_stmt *yspec,
char *prefix)
{
yang_stmt *ymod = NULL;
yang_stmt *yprefix;
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (ymod->ys_keyword == Y_MODULE &&
(yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
strcmp(yang_argument_get(yprefix), prefix) == 0)
return ymod;
return NULL;
}
/*! Given a yang spec and a namespace, return yang module /*! Given a yang spec and a namespace, return yang module
* *
* @param[in] yspec A yang specification * @param[in] yspec A yang specification

View file

@ -8,12 +8,26 @@
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
: ${clixon_util_json:=clixon_util_json} : ${clixon_util_json:=clixon_util_json}
: ${clixon_util_xml:=clixon_util_xml}
fyang=$dir/json.yang fyang=$dir/json.yang
cat <<EOF > $fyang cat <<EOF > $fyang
module json{ module json{
prefix ex; prefix ex;
namespace "urn:example:clixon"; namespace "urn:example:clixon";
identity genre {
description
"From RFC8040 jukebox example.
Identity prefixes are translated from module-name to xml prefix";
}
identity blues {
base genre;
}
typedef gtype{
type identityref{
base genre;
}
}
leaf a{ leaf a{
type int32; type int32;
} }
@ -25,6 +39,14 @@ module json{
type string; type string;
} }
} }
leaf g1 {
description "direct type";
type identityref { base genre; }
}
leaf g2 {
description "indirect type";
type gtype;
}
} }
EOF EOF
@ -68,18 +90,42 @@ expecteofeq "$clixon_util_json -jpy $fyang" 0 "$JSON" "$JSONP"
JSON='{"json:a":-23}' JSON='{"json:a":-23}'
new "json leaf back to json" new "json leaf back to json"
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" "$JSON"
JSON='{"json:c":{"a":937}}' JSON='{"json:c":{"a":937}}'
new "json parse container back to json" new "json parse container back to json"
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" "$JSON"
# This should work # identities translation json -> xml is tricky wrt prefixes, json uses module
# name, xml uses xml namespace prefixes (or default)
JSON='{"json:g1":"json:blues"}'
new "json identity to xml"
expecteofx "$clixon_util_json -y $fyang" 0 "$JSON" '<g1 xmlns="urn:example:clixon">blues</g1>'
new "json identity back to json"
expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" '{"json:g1":"blues"}'
new "xml identity with explicit ns to json"
expecteofx "$clixon_util_xml -ovjy $fyang" 0 '<g1 xmlns="urn:example:clixon" xmlns:ex="urn:example:clixon">ex:blues</g1>' '{"json:g1":"blues"}'
# Same with indirect type
JSON='{"json:g2":"json:blues"}'
new "json indirect identity to xml"
expecteofx "$clixon_util_json -y $fyang" 0 "$JSON" '<g2 xmlns="urn:example:clixon">blues</g2>'
new "json indirect identity back to json"
expecteofx "$clixon_util_json -jy $fyang" 0 "$JSON" '{"json:g2":"blues"}'
new "xml indirect identity with explicit ns to json"
expecteofx "$clixon_util_xml -ojvy $fyang" 0 '<g2 xmlns="urn:example:clixon" xmlns:ex="urn:example:clixon">ex:blues</g2>' '{"json:g2":"blues"}'
# XXX CDATA translation, should work bit does not
if false; then if false; then
JSON='{"json:c": {"s": "<![CDATA[ z > x & x < y ]]>"}}' JSON='{"json:c": {"s": "<![CDATA[ z > x & x < y ]]>"}}'
new "json parse cdata xml" new "json parse cdata xml"
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
fi fi
rm -rf $dir rm -rf $dir

View file

@ -98,8 +98,8 @@ new "netconf get $perfreq single reqs"
done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# RESTCONF get # RESTCONF get
new "restconf get test single req XXX" new "restconf get test single req"
expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"ex:eth","enabled":true,"oper-status":"up"}]} expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}
' '
new "restconf get $perfreq single reqs" new "restconf get $perfreq single reqs"

View file

@ -160,16 +160,16 @@ expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)"
# Exact match # Exact match
new "restconf Add subtree eth/0/0 to datastore using POST" new "restconf Add subtree eth/0/0 to datastore using POST"
expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created' expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created'
new "restconf Re-add subtree eth/0/0 which should give error" new "restconf Re-add subtree eth/0/0 which should give error"
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
# XXX Cant get this to work # XXX Cant get this to work
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"clixon-example:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
new "restconf Check interfaces eth/0/0 added" new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}}} expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}}
' '
new "restconf delete interfaces" new "restconf delete interfaces"
@ -179,15 +179,12 @@ new "restconf Check empty config"
expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state
" "
# XXX: gives <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
# <interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
new "restconf Add interfaces subtree eth/0/0 using POST" new "restconf Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 ""
# XXX cant get this to work #expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' 0 ""
#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" 0 ""
new "restconf Check eth/0/0 added config" new "restconf Check eth/0/0 added config"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}} expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
' '
new "restconf Check eth/0/0 added state" new "restconf Check eth/0/0 added state"
@ -195,7 +192,7 @@ expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 '
' '
new "restconf Re-post eth/0/0 which should generate error" new "restconf Re-post eth/0/0 which should generate error"
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} '
new "Add leaf description using POST" new "Add leaf description using POST"
expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
@ -204,7 +201,7 @@ new "Add nothing using POST"
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:' expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:'
new "restconf Check description added" new "restconf Check description added"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"ex:eth","enabled":true,"oper-status":"up"}]}} expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
' '
new "restconf delete eth/0/0" new "restconf delete eth/0/0"
@ -217,10 +214,10 @@ new "restconf Re-Delete eth/0/0 using none should generate error"
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} '
new "restconf Add subtree eth/0/0 using PUT" new "restconf Add subtree eth/0/0 using PUT"
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
new "restconf get subtree" new "restconf get subtree"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"ex:eth","enabled":true,"oper-status":"up"}]}} expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
' '
new "restconf rpc using POST json" new "restconf rpc using POST json"
@ -262,10 +259,10 @@ if [ -z "$match" ]; then
fi fi
new "restconf Add subtree without key (expected error)" new "restconf Add subtree without key (expected error)"
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}} ' expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}} '
new "restconf Add subtree with too many keys (expected error)" new "restconf Add subtree with too many keys (expected error)"
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} '
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -337,13 +337,12 @@ expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' ht
new "4.5. PUT replace content" new "4.5. PUT replace content"
# XXX should be: jbox:alternative --> example-jukebox:alternative # XXX should be: jbox:alternative --> example-jukebox:alternative
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"jbox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT replace content (xml encoding)" new "4.5. PUT replace content (xml encoding)"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT create new" new "4.5. PUT create new identity"
# XXX should be: jbox:alternative --> example-jukebox:alternative
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"

View file

@ -8,6 +8,8 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
: ${clixon_util_xml:=clixon_util_xml -o} # -o is output : ${clixon_util_xml:=clixon_util_xml -o} # -o is output
sleep 1 # mysterious fail, maybe this helps?
new "xml parse" new "xml parse"
expecteof "$clixon_util_xml" 0 "<a><b/></a>" "^<a><b/></a>$" expecteof "$clixon_util_xml" 0 "<a><b/></a>" "^<a><b/></a>$"

View file

@ -74,7 +74,7 @@ usage(char *argv0)
"where options are\n" "where options are\n"
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n" "\t-D <level> \tDebug\n"
"\t-j \t\tOutput as JSON\n" "\t-j \t\tOutput as JSON (default is as XML)\n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n" "\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
"\t-p \t\tPretty-print output\n" "\t-p \t\tPretty-print output\n"
"\t-y <filename> \tyang filename to parse (must be stand-alone)\n" , "\t-y <filename> \tyang filename to parse (must be stand-alone)\n" ,