diff --git a/apps/backend/backend_clixon_lib.c b/apps/backend/backend_clixon_lib.c index 64dd33806..61bebc243 100644 --- a/apps/backend/backend_clixon_lib.c +++ b/apps/backend/backend_clixon_lib.c @@ -389,7 +389,8 @@ from_client_restart_plugin(clixon_handle h, * from y is used. Unless the element is .., . * @param[in,out] cb0 Result XPath as cbuf * @param[in] xpath1 Input XPath - * @param[in] y Yang of xpath1 + * @param[in] y Context node for unprefixed identifiers + * @param[in] ypath Path statement defining explicit prefixes * @param[in,out] nsc Namespace * @retval 0 OK * @retval -1 Error @@ -407,6 +408,7 @@ static int xpath_append(cbuf *cb0, char *xpath1, yang_stmt *y, + yang_stmt *ypath, cvec *nsc) { int retval = -1; @@ -471,7 +473,7 @@ xpath_append(cbuf *cb0, /* If prefix is not in nsc, it needs to be added */ if (prefix && cvec_find(nsc, prefix) == NULL){ ns = NULL; - if ((ret = yang_find_namespace_by_prefix(y, prefix, &ns)) < 0) + if ((ret = yang_find_namespace_by_prefix(ypath, prefix, &ns)) < 0) goto done; if (ret == 0){ clixon_err(OE_DB, 0, "Prefix %s does not have an associated namespace", prefix); @@ -545,7 +547,7 @@ leafref_append_path(yang_stmt *yn, clixon_err(OE_DB, 0, "Leafref %s requires path statement", resolved_name); goto done; } - if (xpath_append(cbxpath, yang_argument_get(ypath), yn, nsc) < 0) + if (xpath_append(cbxpath, yang_argument_get(ypath), yn, ypath, nsc) < 0) goto done; } else if (strcmp(resolved_name, "union") == 0){ @@ -577,7 +579,7 @@ leafref_append_path(yang_stmt *yn, goto done; } cbuf_append_str(cb_one, cbuf_get(cb_orig)); - if (xpath_append(cb_one, yang_argument_get(ypath), yn, nsc) < 0) + if (xpath_append(cb_one, yang_argument_get(ypath), yn, ypath, nsc) < 0) goto done; if (!first) cprintf(cbxpath, " | "); diff --git a/lib/src/clixon_autocli_generate.c b/lib/src/clixon_autocli_generate.c index fd6917fac..e30943912 100644 --- a/lib/src/clixon_autocli_generate.c +++ b/lib/src/clixon_autocli_generate.c @@ -239,6 +239,7 @@ gen_mtpoint_add(cbuf *cb, * @retval -1 Error * @param[in] leafref_refer If set, add "leafref-refer" as a 3rd argument to * expand_dbvar() to signal that leafrefs should be followed. + * @param[in] preference Optional CLIgen preference override (zero uses the default) * @see expand_dbvar This is where the expand string is used * @see CLIXON_AUTOCLI_NS "expand" extension Overrides expand_dbvar() with a custom callback * @note XXX only fraction_digits handled,should also have mincv, maxcv, pattern @@ -251,6 +252,7 @@ cli_expand_var_generate(clixon_handle h, uint8_t fraction_digits, int pre, int leafref_refer, + uint32_t preference, cbuf *cb) { int retval = -1; @@ -271,6 +273,8 @@ cli_expand_var_generate(clixon_handle h, if (pre) cprintf(cb, "|"); cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr); + if (preference) + cprintf(cb, " preference:%u", preference); if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); cprintf(cb, " %s(\"candidate\",\"%s\"", @@ -909,6 +913,10 @@ yang2cli_var_union_one(clixon_handle h, else { if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0) goto done; + /* Leafrefs in unions are single values, not the rest of the command. + * The backend validates the value against the referenced type. */ + if (restype && strcmp(restype, "leafref") == 0) + cvtype = CGV_STRING; if ((retval = yang2cli_leaf_var_sub(h, ys, ytype, helptext, cvtype, options, cvv, patterns, fraction_digits, patpref, cb)) < 0) goto done; @@ -1187,11 +1195,16 @@ yang2cli_leaf_var(clixon_handle h, * Note: also set when restype=="leafref" because yang_path_arg may fail to resolve the * referred node client-side (e.g. grouping with submodule prefix scope); the backend * handles leafref resolution independently. */ + /* Preserve child commands, but retain the rest variable's preference + * so expanded values do not compete with explicit union alternatives. */ + if (strcmp(restype, "union") == 0) + cvtypestr = "string"; if ((ret = cli_expand_var_generate(h, ys, cvtypestr, options, fraction_digits, regular_value, leafref_refer || strcmp(restype, "union") == 0 || strcmp(restype, "leafref") == 0, + strcmp(restype, "union") == 0 ? COV_PREF_REST : 0, cb)) < 0) goto done; if (ret == 1) diff --git a/test/test_cli_union_leafref.sh b/test/test_cli_union_leafref.sh new file mode 100755 index 000000000..8aad84997 --- /dev/null +++ b/test/test_cli_union_leafref.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Union leafref keys must preserve child commands and the typedef's prefix scope. + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example +cfg=$dir/conf_yang.xml + +cat < $dir/example-targets.yang +module example-targets { + yang-version 1.1; + namespace "urn:example:targets"; + prefix t; + list port { + key name; + leaf name { type string; } + } + list lag { + key name; + leaf name { type string; } + } + typedef port-ref { + type leafref { path "/t:port/t:name"; } + } + typedef relative-ref { + type leafref { path "../name"; } + } + typedef endpoint-ref { + type union { + type port-ref; + type leafref { path "/t:lag/t:name"; } + } + } +} +EOF1 + +cat < $dir/example.yang +module example { + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + // The defining module's prefix t is deliberately unavailable here. + import example-targets { prefix target; } + leaf primary { type target:port-ref; } + list endpoint { + key name; + leaf name { type target:endpoint-ref; } + leaf cost { type uint32; } + leaf alias { type target:relative-ref; } + } +} +EOF1 + +AUTOCLI=$(autocli_config 'example*' kw-nokey false | sed 's||true|') +cat < $cfg + + $cfg + ${YANG_INSTALLDIR} + $dir + example + $dir + $dir/backend.sock + $dir/backend.pid + $dir + false + ${AUTOCLI} + +EOF1 + +cat < $dir/example.cli +CLICON_MODE="example"; +set @datamodel, cli_auto_set(); +validate, cli_validate(); +show, cli_show_auto_mode("candidate", "xml", false, false); +quit, cli_quit(); +EOF1 + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "start backend" + start_backend -s init -f $cfg +fi +new "wait backend" +wait_backend + +new "populate both leafref targets" +expectpart "$($clixon_cli -1 -f $cfg set port p1 2>&1)" 0 '^$' +expectpart "$($clixon_cli -1 -f $cfg set lag lag1 2>&1)" 0 '^$' + +new "completion of imported union leafref typedef" +expectpart "$(printf 'set endpoint ?\025quit\n' | $clixon_cli -f $cfg 2>&1)" 0 'p1' 'lag1' --not-- 'Netconf error' 'Syntax error' + +new "completion of imported ordinary leafref typedef" +expectpart "$(printf 'set primary ?\025quit\n' | $clixon_cli -f $cfg 2>&1)" 0 'p1' --not-- 'Netconf error' 'Syntax error' + +for completion in false true; do + new "union keys retain child commands with completion=$completion" + sed -i "s|.*|$completion|" $cfg + expectpart "$($clixon_cli -1 -f $cfg set endpoint p1 cost 10 2>&1)" 0 '^$' + expectpart "$($clixon_cli -1 -f $cfg set endpoint lag1 cost 20 2>&1)" 0 '^$' + expectpart "$($clixon_cli -1 -f $cfg show 2>&1)" 0 'p110' 'lag120' +done + +new "unprefixed typedef paths use the consuming node namespace" +expectpart "$(printf 'set endpoint p1 alias ?\025quit\n' | $clixon_cli -f $cfg 2>&1)" 0 ' p1' --not-- 'Netconf error' 'Syntax error' + +new "union leafrefs validate against existing targets" +expectpart "$($clixon_cli -1 -f $cfg validate 2>&1)" 0 '^$' +expectpart "$($clixon_cli -1 -f $cfg set endpoint absent cost 30 2>&1)" 0 '^$' +expectpart "$($clixon_cli -1 -f $cfg validate 2>&1)" 255 'instance-required' + +if [ $BE -ne 0 ]; then + new "kill backend" + stop_backend -f $cfg +fi +rm -rf $dir +new "endtest" +endtest