* Identity/identityref mapped between XML and JSON
This commit is contained in:
parent
ccc95b2826
commit
70ebfa4d80
22 changed files with 779 additions and 133 deletions
|
|
@ -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}`
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) */
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
151
apps/restconf/restconf_methods_patch.c
Normal file
151
apps/restconf/restconf_methods_patch.c
Normal 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‑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;
|
||||||
|
}
|
||||||
|
|
||||||
48
apps/restconf/restconf_methods_patch.h
Normal file
48
apps/restconf/restconf_methods_patch.h
Normal 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_ */
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>$"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" ,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue