I've got a model that looks like this
grouping index {
leaf index {
type uint16 {
range '1..max';
}
}
must 'INSERT RESTRICTION HERE';
}
list my-list-1 {
key index;
uses index;
// ... other nodes
}
list my-list-2 {
key index;
uses index;
// ... other nodes
}
I want to make sure this particular list has an node present for each index from 1 to the max in the list. If there's 1 item in the list, it has index 1. If there are 3 items in the list, they should have indices 1, 2, and 3. In other words, the node should act like an array index. I want this as a grouping so that I can use it in many areas of my model.
The following must
restriction works when put inside of my-list-1
.
must "current()/index = 1 or ../my-list-1/index[. = current()/index- 1]";
However, notice that the xpath specifies my-list-1
inside it. I don't want this because I need to do it generically for the grouping.
I tried all of the following in the grouping and they didn't work
must "current() = 1 or parent::*/index[. = current() - 1]";
must "current() = 1 or parent::*[index = current() - 1]";
must "current() = 1 or ../index[. = current() - 1]";
These will reject all input that isn't exactly 1
.
I also tried messing around with following-sibling::*
, but I couldn't get that to work either. Is there a way to achieve what I want?
Technically, you can achieve what you want using a must
expression. But you should note that whatever expression you choose will rely on document order, which may or may not be problematic - depending on implementations of YANG validation engines. You should also take into account what a constraint like this means for people that will need to actually create configuration based on your model - you are essentially forcing them to act as an auto- incrementing index assigner. At least to me, this seems like a job for an automated process. You may be looking at this from the wrong angle. There is also some performance overhead to consider when relying on any XPath constraint in YANG if the constraint is not hardcoded within an implementation.
That being said, here's a model that works for me:
module a {
yang-version 1.1;
namespace "example:uri:a";
prefix "a";
grouping index {
leaf index {
must "(count(../preceding-sibling::*[local-name()=local-name(current()/..) and namespace-uri()=namespace-uri(current()/..)]) = 0 and . = 1)
or ../preceding-sibling::*[local-name()=local-name(current()/..) and namespace-uri()=namespace-uri(current()/..)][1]/index + 1 = .";
type uint16 {
range '1..max';
}
}
}
list my-list-1 {
ordered-by user;
key index;
uses index;
// ... other nodes
}
list my-list-2 {
ordered-by user;
key index;
uses index;
// ... other nodes
}
}
Simple, right? The expression essentially asserts that the first list data node in document order that matches the qualified name of the parent list data node and is a preceding sibling of said parent has an index child data node value that is one less than our current index data node value (the value of the context node for the expression). Unless the parent data node is the first in the sequence in which case the index data node may only have a value of 1.
The preceding-sibling::
axis selects all preceding siblings, regardless of their name, which is why you need to filter those that match the qualified name of the parent list data node, hence the first predicate ([local-name()=local-name(current()/..) and namespace-uri()=namespace-uri(current()/..)]
). The second predicate selects the first match ([1]
). This should translate into the list data node that is before the current one in document order.
This would be a valid XML encoded instance representing a config datastore:
<?xml version="1.0" encoding="utf-8"?>
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
xmlns:a="example:uri:a">
<a:my-list-1 >
<a:index>1</a:index>
</a:my-list-1>
<a:my-list-2>
<a:index>1</a:index>
</a:my-list-2>
<a:my-list-2>
<a:index>2</a:index>
</a:my-list-2>
<a:my-list-1>
<a:index>2</a:index>
</a:my-list-1>
</config>
But if you change the latter two index values to anything else, a must constraint violation will be reported. The same applies if you change the initial values to anything other than 1.
I would not consider the above grouping to be reusable - it only makes sense for a uses inside a list schema node.
You should make both lists ordered by user, so implementations don't get to choose the order messing up configured data nodes.
In this case, the ordered by user statement plays no role an may be removed. The condition needs to be changed so that for each index data node there's an equivalent data node with a value of one less in one of the siblings of the parent list data node. With the exception of the data node with a value of 1. Assuming the grouping is only to be used inside list schema nodes and the index schema node is to be used as a list key, we can further simplify the condition (uniqueness is guaranteed elsewhere):
. = 1
or (../preceding-sibling::*|../following-sibling::*)[
local-name()=local-name(current()/..)
and namespace-uri()=namespace-uri(current()/..)
and index = current() - 1
]
The ../preceding-sibling::*|../following-sibling::*
filter expression selects all siblings of the parent list data node - a union of both the ones that appear before the current parent and the ones that appear after in document order. The resulting node set is then further filtered in a similar way as in the first case. The index = current() - 1
expression represents the actual condition the index must satisfy.
module a {
yang-version 1.1;
namespace "example:uri:a";
prefix "a";
grouping index {
leaf index {
must ". = 1
or (../preceding-sibling::*|../following-sibling::*)[
local-name()=local-name(current()/..)
and namespace-uri()=namespace-uri(current()/..)
and index = current() - 1
]";
type uint16 {
range '1..max';
}
}
}
list my-list-1 {
key index;
uses index;
// ... other nodes
}
list my-list-2 {
key index;
uses index;
// ... other nodes
}
}