diff --git a/apps/backend/backend_state.c b/apps/backend/backend_state.c
index 34e49403..0ca210d4 100644
--- a/apps/backend/backend_state.c
+++ b/apps/backend/backend_state.c
@@ -105,6 +105,7 @@ restconf_client_get_capabilities(clixon_handle h,
cprintf(cb, "");
cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit");
cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0");
+ cprintf(cb, "urn:ietf:params:restconf:capability:fields:1.0");
cprintf(cb, "urn:ietf:params:restconf:capability:with-defaults:1.0");
cprintf(cb, "");
if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0)
diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c
index 70c5e965..4b5ad43d 100644
--- a/apps/restconf/restconf_methods_get.c
+++ b/apps/restconf/restconf_methods_get.c
@@ -66,6 +66,79 @@
/* Forward */
static int api_data_pagination(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out);
+/*! Filter top-level data nodes based on "fields" query parameter (RFC 8040 Sec 4.8.3)
+ *
+ * Simple implementation: only top-level module:node or module:* selection at data root.
+ * Removes children of xdata that do not match any entry in the semicolon-separated
+ * fields list.
+ * @param[in] xdata XML data root whose children are top-level data nodes
+ * @param[in] fields_str Value of the "fields" query parameter, e.g. "mod1:node1;mod2:node2"
+ * @retval 0 OK
+ * @retval -1 Error
+ * @see RFC 8040 Sec 4.8.3
+ */
+static int
+restconf_fields_filter(cxobj *xdata,
+ char *fields_str)
+{
+ int retval = -1;
+ char *str = NULL;
+ char *s;
+ char *saveptr;
+ char *colon;
+ cxobj *x;
+ yang_stmt *ynode;
+ yang_stmt *ymod;
+ char *modname;
+ char *nodename;
+ int i;
+ int found;
+
+ for (i = xml_child_nr(xdata)-1; i >= 0; i--){
+ x = xml_child_i(xdata, i);
+ if (xml_type(x) != CX_ELMNT)
+ continue;
+ ynode = xml_spec(x);
+ ymod = (ynode != NULL) ? ys_module(ynode) : NULL;
+ modname = (ymod != NULL) ? yang_argument_get(ymod) : NULL;
+ nodename = xml_name(x);
+ found = 0;
+ if ((str = strdup(fields_str)) == NULL){
+ clixon_err(OE_UNIX, errno, "strdup");
+ goto done;
+ }
+ s = strtok_r(str, ";", &saveptr);
+ while (s != NULL){
+ colon = strchr(s, ':');
+ if (colon != NULL){
+ *colon = '\0';
+ if (modname != NULL &&
+ strcmp(s, modname) == 0 &&
+ strcmp(colon+1, nodename) == 0)
+ found = 1;
+ *colon = ':';
+ }
+ else{
+ if (modname != NULL && strcmp(s, modname) == 0)
+ found = 1;
+ }
+ if (found)
+ break;
+ s = strtok_r(NULL, ";", &saveptr);
+ }
+ free(str);
+ str = NULL;
+ if (!found)
+ if (xml_purge(x) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (str)
+ free(str);
+ return retval;
+}
+
/*! Generic GET (both HEAD and GET)
*
* According to restconf
@@ -125,6 +198,7 @@ api_data_get2(clixon_handle h,
cxobj *xtop = NULL;
yang_stmt *y = NULL;
char *defaults = NULL;
+ char *fields_str = NULL;
cvec *nscd = NULL;
int ret;
@@ -207,6 +281,11 @@ api_data_get2(clixon_handle h,
clixon_debug(CLIXON_DBG_RESTCONF, "with_defaults=%s", attr);
defaults = attr;
}
+ /* Check for fields attribute: simple root-level module:node filter, RFC 8040 Sec 4.8.3 */
+ if ((attr = cvec_find_str(qvec, "fields")) != NULL){
+ clixon_debug(CLIXON_DBG_RESTCONF, "fields=%s", attr);
+ fields_str = attr;
+ }
clixon_debug(CLIXON_DBG_RESTCONF, "path:%s", xpath);
if ((ret = clicon_rpc_get(h, xpath, nsc, content, depth, defaults, &xret)) < 0){
@@ -235,6 +314,11 @@ api_data_get2(clixon_handle h,
goto done;
}
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
+ /* Apply fields filter if requested */
+ if (fields_str != NULL){
+ if (restconf_fields_filter(xret, fields_str) < 0)
+ goto done;
+ }
switch (media_out){
case YANG_DATA_XML:
if (clixon_xml2cbuf(cbx, xret, 0, pretty, NULL, -1, 0) < 0) /* Dont print top object? */
diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh
index 3e19d0ee..9f564ebb 100755
--- a/test/test_restconf_jukebox.sh
+++ b/test/test_restconf_jukebox.sh
@@ -189,13 +189,30 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCP
# Maybe this is not correct w [null,null]but I have no good examples
new 'B.3.2. "depth" Parameter depth=3'
-expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/$HVER 200" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}}
-
'
+expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/$HVER 200" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}}
+
+'
new "restconf DELETE whole datastore"
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204"
-#new 'B.3.3. "fields" Parameter'
+new 'B.3.3. "fields" Parameter: preamble (create system and jukebox data)'
+expectpart "$(curl $CURLOPTS -X PATCH -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d 'trueFields Artist')" 0 "HTTP/$HVER 204"
+
+new 'B.3.3. "fields" Parameter: system only'
+expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data?fields=example-system:system)" 0 "HTTP/$HVER 200" \
+ '"example-system:system"' --not-- '"example-jukebox:jukebox"'
+
+new 'B.3.3. "fields" Parameter: jukebox only'
+expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data?fields=example-jukebox:jukebox)" 0 "HTTP/$HVER 200" \
+ '"example-jukebox:jukebox"' --not-- '"example-system:system"'
+
+new 'B.3.3. "fields" Parameter: two modules'
+expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' "$RCPROTO://localhost/restconf/data?fields=example-system:system%3Bexample-jukebox:jukebox")" 0 "HTTP/$HVER 200" \
+ '"example-system:system"' '"example-jukebox:jukebox"'
+
+new "restconf DELETE whole datastore (after fields test)"
+expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204"
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']\"}]}"
@@ -251,7 +268,6 @@ new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '2431'
#new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed
-#new 'B.3.3. "fields" Parameter'
#new 'B.3.6. "filter" Parameter'
#new 'B.3.7. "start-time" Parameter'
#new 'B.3.8. "stop-time" Parameter'
diff --git a/test/test_yang_with_defaults.sh b/test/test_yang_with_defaults.sh
index e8ce796e..eebeed60 100755
--- a/test/test_yang_with_defaults.sh
+++ b/test/test_yang_with_defaults.sh
@@ -509,14 +509,14 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCP
0 \
"HTTP/$HVER 200" "Content-Type: application/yang-data+json" \
'Cache-Control: no-cache' \
-'{"ietf-restconf-monitoring:capabilities":{"capability":\["urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit","urn:ietf:params:restconf:capability:depth:1.0","urn:ietf:params:restconf:capability:with-defaults:1.0"\]}}'
+'{"ietf-restconf-monitoring:capabilities":{"capability":\["urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit","urn:ietf:params:restconf:capability:depth:1.0","urn:ietf:params:restconf:capability:fields:1.0","urn:ietf:params:restconf:capability:with-defaults:1.0"\]}}'
new "rfc8040 B.1.3. Retrieve the Server Capability Information xml"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" \
0 \
"HTTP/$HVER 200" "Content-Type: application/yang-data+xml" \
'Cache-Control: no-cache' \
-'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=expliciturn:ietf:params:restconf:capability:depth:1.0urn:ietf:params:restconf:capability:with-defaults:1.0'
+'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=expliciturn:ietf:params:restconf:capability:depth:1.0urn:ietf:params:restconf:capability:fields:1.0urn:ietf:params:restconf:capability:with-defaults:1.0'
new "rfc8040 B.3.9. RESTCONF with-defaults parameter = report-all json"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example:interfaces/interface=eth1?with-defaults=report-all)" \