feat(yang-push): normalize target xpath filters and add failed xpath resolution check with libyang - #45
Draft
rodonile wants to merge 11 commits into
Draft
feat(yang-push): normalize target xpath filters and add failed xpath resolution check with libyang#45rodonile wants to merge 11 commits into
rodonile wants to merge 11 commits into
Conversation
Fetching a YANG Library by subscription id failed for devices that send an inline datastore-xpath-filter without xmlns bindings (e.g. Cisco IOS-XR), because module resolution only looked at declared namespace prefixes. The target module was silently dropped and every subsequent notification failed validation. Resolve xpath-filter modules per the RFC 8641 XPath context: use a declared xmlns binding when present (e.g. Huawei), otherwise treat the path prefix as the YANG module name (e.g. Cisco IOS-XR). Both are conformant. Subtree and stream filters keep namespace-based lookup. An empty resolution result is now a hard error instead of silently caching an incomplete library, and errors are now typed instead of generic IO errors. Add unit tests covering the two resolution cases, and extra trace-level logging for debugging.
The Cisco-style xpath prefix fallback (prefix == module name) resolved modules via YangLibrary::find_module, which searches every module set in the library regardless of datastore. A module name can be pinned at different revisions in different module sets (RFC 8525), so an unscoped lookup could silently fetch and cache the wrong revision for a subscription's target datastore. Add YangLibrary::find_module_by_datastore_and_name, mirroring the existing namespace-scoped lookup, and use it for the prefix-as-name fallback so both resolution paths are scoped consistently. Add a regression test with the same module name at two revisions in two datastores.
Move find_xpath_prefixes out of xml_utils.rs (a broad, unrelated XML-parsing grab-bag) into a new xpath.rs module, the shared home for XPath 1.0 subset text utilities. Register the module in lib.rs and update its two call sites. Pure refactor, no behavior change. Sets up xpath.rs as the target for the normalize_path engine added next.
Add DatastoreXPathFilter::normalize_path: converts an xpath to RFC 8641's canonical, module-name-qualified, prefix-on-change form (matches libyang's SchemaPathFormat::DATA), whether the source path uses declared xmlns prefixes or bare module-name prefixes. Backed by three new pure helpers in xpath.rs: split_location_path, parse_node_test, is_ncname. Bails to None (caller keeps the original path) for unsupported XPath 1.0 constructs or an unresolvable declared prefix. Not wired into any caller yet.
Apply DatastoreXPathFilter::normalize_path to the datastore xpath filter fetched via get_yang_push_subscription_by_id, so the cached target is always in canonical module-name-qualified form regardless of how the device encoded prefixes (declared xmlns vs. bare module name). Falls back to the original path (with a warning) when the path can't be confidently normalized.
JSON-encoded SubscriptionStarted/Modified notifications carry their datastore-xpath-filter as a plain string with no xmlns table. Normalize it the same way as the NETCONF/XML path, in build_subscription_info, via normalize_json_target_xpath. Needs no schema access: passing an empty namespace table makes normalize_path treat every prefix as already-resolved. Reduces noise in the canonical-form diagnostic and avoids spurious subscription-changed refetches from pure prefix-style variance.
Add check_xpath_target_resolves: after a schema loads, evaluate the subscription's datastore-xpath-filter against the libyang context and warn if it does not resolve to a schema node, or resolves but isn't in libyang's canonical (SchemaPathFormat:: DATA) form. Diagnostic only, never mutates the target. Move xpath_diff and strip_xpath_predicates into netconf-proto's xpath module so they're reusable and directly testable.
find_xpath_prefixes previously skipped all string-literal content when extracting xpath prefixes, so a module referenced only inside a predicate's identityref-shaped literal (e.g. hw-hwt in 'hw-hwt:ethernetCsmacd-xcvr-link') was never returned. This made the fetcher silently drop that module from the schema fetch list, and the parser drop the matching xmlns binding on deserialize. find_xpath_prefixes now also reports a literal's prefix when its entire content is shaped like one QName. normalize_path's predicate rewriter mirrors this: such a literal is now resolved and rewritten to its full module name too, keeping the canonical xpath self-contained once the fetcher wipes the namespace map.
rodonile
force-pushed
the
xpath-normalization
branch
from
September 2, 2026 11:30
6a66ed9 to
bd93ce9
Compare
normalize_path treated every empty path segment as valid, letting unsupported `//` and trailing `/` paths through instead of bailing. strip_xpath_predicates never checked bracket/quote balance, silently truncating malformed input instead of returning it unchanged.
rodonile
force-pushed
the
xpath-normalization
branch
from
September 2, 2026 12:31
0d03516 to
39e8aeb
Compare
find_xpath_prefixes now splits structural (node-name) prefixes from ones seen only inside a whole-QName-shaped literal. path_prefixes() drops an undeclared literal-shaped prefix instead of treating it as a required module, so an incidental value like an interface name 'ge:0' no longer fails the fetch when no such module exists.
rodonile
force-pushed
the
xpath-normalization
branch
from
September 2, 2026 12:42
39e8aeb to
4289526
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR depends on #43.
Summary
Normalizes
datastore-xpath-filtertargets — from both NETCONF/XMLsubscription fetches and JSON-encoded
SubscriptionStarted/SubscriptionModifiednotifications — to RFC 8641's module-name-qualified,prefix-on-change form, and adds a diagnostic that warns when a
target xpath doesn't resolve against the loaded schema or isn't in libyang's
canonical form.
Motivation
Different publishers/transports encode the same xpath target
differently: some use declared
xmlnsprefixes, some use baremodule-name prefixes, some repeat the prefix on every step instead
of only on module change. This caused issues to non-YANG-aware
post processing engines that are relying on xpath matching
for filtering messages.
Changes
new
crates/netconf-proto/src/xpath.rsmodule.DatastoreXPathFilter::normalize_path, the canonicalizationengine (bails safely to the original path on anything unsupported).
declared prefixes via the router's YANG library) and JSON-encoded
SubscriptionStarted/Modifiedtargets.check_xpath_target_resolves, a diagnostic that warns when atarget xpath doesn't resolve against the loaded schema or isn't in
libyang's canonical form.