diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ea76a9f..60efc564 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
### Major New features
* Restconf RFC 8040 increased feature compliance
* RESTCONF "insert" and "point" query parameters supported
+ * Applies to ordered-by-user leaf and leaf-lists
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.
@@ -22,6 +23,9 @@
* The main example explains how to implement a Yang extension in a backend plugin.
### API changes on existing features (you may need to change your code)
+* Netconf edit-config "operation" attribute namespace check is enforced
+ * This is enforced: `
+ * This was previously allowed: `
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.
diff --git a/README.md b/README.md
index fac2ee1e..3980efa8 100644
--- a/README.md
+++ b/README.md
@@ -77,8 +77,7 @@ Clixon interaction is best done posting issues, pull requests, or joining the
Clixon provides a core system and can be used as-is using available
Yang specifications. However, an application very quickly needs to
specialize functions. Clixon is extended by writing
-plugins for cli and backend. Extensions for netconf and restconf
-are also available.
+plugins for cli, backend, netconf and restconf.
Plugins are written in C and easiest is to look at
[example](example/README.md) or consulting the [FAQ](doc/FAQ.md).
@@ -100,12 +99,12 @@ However, the following YANG syntax modules are not implemented (reference to RFC
- action (7.15)
- augment in a uses sub-clause (7.17) (module-level augment is implemented)
- require-instance
-- instance-identifier type
+- instance-identifier type (9.13)
- status (7.21.2)
- YIN (13)
- Yang extended Xpath functions: re-match(), deref)(), derived-from(), derived-from-or-self(), enum-value(), bit-is-set() (10.2-10.6)
- Default values on leaf-lists are not supported (7.7.2)
-- instance-identifier type
+- Lists without keys (non-config lists may lack keys)
### Yang patterns
Yang type patterns use regexps defined in [W3C XML XSD](http://www.w3.org/TR/2004/REC-xmlschema-2-20041028). XSD regexp:s are
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index f29f1b25..2e8966fb 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -558,6 +558,22 @@ restconf_terminate(clicon_handle h)
* @param[in] qvec Query parameters (eg where insert/point should be)
* @retval 0 OK
* @retval -1 Error
+ * A difficulty is that RESTCONF 'point' is a complete identity-identifier encoded
+ * as an uri-path
+ * wheras NETCONF key and value are:
+ * The value of the "key" attribute is the key predicates of the full instance identifier
+ * the "value" attribute MUST also be used to specify an existing entry in the
+ * leaf-list.
+ * This means that api-path is first translated to xpath, and then strip everything
+ * except the key values (or leaf-list values).
+ * Example:
+ * RESTCONF URI: point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1
+ * Leaf example:
+ * instance-id: /ex:system/ex:service[ex:name='foo'][ex:enabled='']
+ * NETCONF: yang:key="[ex:name='foo'][ex:enabled='']
+ * Leaf-list example:
+ * instance-id: /ex:system/ex:service[.='foo']
+ * NETCONF: yang:value="foo"
*/
int
restconf_insert_attributes(cxobj *xdata,
@@ -570,7 +586,11 @@ restconf_insert_attributes(cxobj *xdata,
yang_stmt *y;
char *attrname;
int ret;
-
+ char *xpath = NULL;
+ char *namespace = NULL;
+ cbuf *cb = NULL;
+ char *p;
+
y = xml_spec(xdata);
if ((instr = cvec_find_str(qvec, "insert")) != NULL){
/* First add xmlns:yang attribute */
@@ -591,9 +611,6 @@ restconf_insert_attributes(cxobj *xdata,
goto done;
}
if ((pstr = cvec_find_str(qvec, "point")) != NULL){
- char *xpath = NULL;
- char *namespace = NULL;
- cbuf *cb = NULL;
if (y == NULL){
clicon_err(OE_YANG, 0, "Cannot yang resolve %s", xml_name(xdata));
goto done;
@@ -614,16 +631,36 @@ restconf_insert_attributes(cxobj *xdata,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
- cprintf(cb, "/%s", xpath); /* XXX: also prefix/namespace? */
+ if (yang_keyword_get(y) == Y_LIST){
+ /* translate /../x[] --> []*/
+ if ((p = rindex(xpath,'/')) == NULL)
+ p = xpath;
+ p = index(p, '[');
+ cprintf(cb, "%s", p);
+ }
+ else{ /* LEAF_LIST */
+ /* translate /../x[.='x'] --> x */
+ if ((p = rindex(xpath,'\'')) == NULL){
+ clicon_err(OE_YANG, 0, "Translated api->xpath %s->%s not on leaf-list canonical form: ../[.='x']", pstr, xpath);
+ goto done;
+ }
+ *p = '\0';
+ if ((p = rindex(xpath,'\'')) == NULL){
+ clicon_err(OE_YANG, 0, "Translated api->xpath %s->%s not on leaf-list canonical form: ../[.='x']", pstr, xpath);
+ goto done;
+ }
+ p++;
+ cprintf(cb, "%s", p);
+ }
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
- if (xpath)
- free(xpath);
- if (cb)
- cbuf_free(cb);
}
retval = 0;
done:
+ if (xpath)
+ free(xpath);
+ if (cb)
+ cbuf_free(cb);
return retval;
}
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index ba9b8435..bd685164 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -487,6 +487,15 @@ xml_search(cxobj *xp,
* @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
* @retval i Order where xn should be inserted into xp:s children
* @retval -1 Error
+ * LIST: RFC 7950 7.8.6:
+ * The value of the "key" attribute is the key predicates of the
+ * full instance identifier (see Section 9.13) for the list entry.
+ * This means the value can be [x='a'] but the full instance-id should be prepended,
+ * such as /ex:system/ex:services[x='a']
+ *
+ * LEAF-LIST: RFC7950 7.7.9
+ * yang:insert="after"
+ * yang:value="3des-cbc">blowfish-cbc)
*/
static int
@@ -501,7 +510,6 @@ xml_insert_userorder(cxobj *xp,
int i;
cxobj *xc;
yang_stmt *yc;
- char *kludge = ""; /* Cant get instance-id generic of [.. and /.. case */
switch (ins){
case INS_FIRST:
@@ -534,7 +542,7 @@ xml_insert_userorder(cxobj *xp,
else{
switch (yang_keyword_get(yn)){
case Y_LEAF_LIST:
- if ((xc = xpath_first(xp, "%s", key_val)) == NULL)
+ if ((xc = xpath_first(xp, "%s[.='%s']", xml_name(xn),key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: value, missing-instance: %s", key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
@@ -544,10 +552,8 @@ xml_insert_userorder(cxobj *xp,
}
break;
case Y_LIST:
- if (strlen(key_val) && key_val[0] == '[')
- kludge = xml_name(xn);
- if ((xc = xpath_first(xp, "%s%s", kludge, key_val)) == NULL)
- clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s%s", xml_name(xn), key_val);
+ if ((xc = xpath_first(xp, "%s%s", xml_name(xn), key_val)) == NULL)
+ clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s", key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");
diff --git a/test/test_order.sh b/test/test_order.sh
index 7dcf82a0..3e69645c 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -324,17 +324,17 @@ expecteof "$clixon_netconf -qf $cfg" 0 '71]]>]]>"
+XML="71]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^]]>]]>$"
new "add one entry 42 after b"
-XML="42]]>]]>"
+XML="42]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^]]>]]>$"
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry 99 after Q (not found, error)"
-XML="99]]>]]>"
-RES="^protocoloperation-failederrorbad-attribute: value, missing-instance: y0\[.='Q'\]]]>]]>$"
+XML="99]]>]]>"
+RES="^protocoloperation-failederrorbad-attribute: value, missing-instance: Q]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "$RES"
new "check ordered-by-user: e,a,71,b,42,c,d"
@@ -384,7 +384,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry key 99 after Q (not found, error)"
-expecteof "$clixon_netconf -qf $cfg" 0 "99bar]]>]]>" "^protocoloperation-failederrorbad-attribute: key, missing-instance: y2\[k='Q'\]]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 "99bar]]>]]>" "^protocoloperation-failederrorbad-attribute: key, missing-instance: Q]]>]]>$"
new "check ordered-by-user: e,a,71,b,42,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^ebarafoo71fiebbar42fumcfoodfie]]>]]>$'
diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh
index 702d19ce..5dbb7b93 100755
--- a/test/test_restconf_jukebox.sh
+++ b/test/test_restconf_jukebox.sh
@@ -360,11 +360,11 @@ expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert" Parameter'
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
new 'B.3.4. "insert" Parameter first (RFC example says after)'
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
new 'B.3.4. "insert" Parameter check order'
RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]"
@@ -372,13 +372,11 @@ expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:j
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" check order (0,2,1)'
RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]"
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
-
-#XXX 'Location: https://example.com/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
+expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
@@ -392,23 +390,23 @@ new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
new 'B.3.4. "insert/point" leaf-list 2 first'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
new 'B.3.4. "insert/point" leaf-list 1 last'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
#new 'B.3.4. "insert/point" move leaf-list 1 last'
#- restconf cannot move a leaf-list(list?) item
-#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
+#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231'
new 'B.3.5. "point" Parameter leaf-list 4 before 3'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431'