Skip to content

Commit 01f2633

Browse files
schema: Make choice and case statements available
Make choice and case statements available so that they can be accessed if end-user requires reading schema nodes. By design, choice and case statements do not exist in data tree directly. Only children of one case can be present in the data tree at one time. That means that choice and case children are not instantiable, thus `SchemaNode::immediateChildren` must be used (instead of `SchemaNode::childInstantibles`) if end-user wants to access choice and case substatements. Change-Id: Ib089672ad21dda8a0344895835d92d3432fcccb8 Co-authored-by: Jan Kundrát <[email protected]>
1 parent a5e5f67 commit 01f2633

5 files changed

Lines changed: 284 additions & 27 deletions

File tree

include/libyang-cpp/SchemaNode.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class AnyDataAnyXML;
2222
class ActionRpc;
2323
class ActionRpcInput;
2424
class ActionRpcOutput;
25+
class Case;
26+
class Choice;
2527
class Container;
2628
class Leaf;
2729
class LeafList;
@@ -62,6 +64,8 @@ class LIBYANG_CPP_EXPORT SchemaNode {
6264
// drectly by the user.
6365
// TODO: turn these into a templated `as<>` method.
6466
AnyDataAnyXML asAnyDataAnyXML() const;
67+
Case asCase() const;
68+
Choice asChoice() const;
6569
Container asContainer() const;
6670
Leaf asLeaf() const;
6771
LeafList asLeafList() const;
@@ -129,6 +133,36 @@ class LIBYANG_CPP_EXPORT AnyDataAnyXML : public SchemaNode {
129133
using SchemaNode::SchemaNode;
130134
};
131135

136+
/**
137+
* @brief Class representing a schema definition of a `case` node.
138+
*
139+
* Wraps `lysc_node_case`.
140+
*/
141+
class LIBYANG_CPP_EXPORT Case : public SchemaNode {
142+
public:
143+
friend SchemaNode;
144+
friend Choice;
145+
146+
private:
147+
using SchemaNode::SchemaNode;
148+
};
149+
150+
/**
151+
* @brief Class representing a schema definition of a `choice` node.
152+
*
153+
* Wraps `lysc_node_choice`.
154+
*/
155+
class LIBYANG_CPP_EXPORT Choice : public SchemaNode {
156+
public:
157+
bool isMandatory() const;
158+
std::vector<Case> cases() const;
159+
std::optional<Case> defaultCase() const;
160+
friend SchemaNode;
161+
162+
private:
163+
using SchemaNode::SchemaNode;
164+
};
165+
132166
/**
133167
* @brief Class representing a schema definition of a `container` node.
134168
*/

src/SchemaNode.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,32 @@ NodeType SchemaNode::nodeType() const
191191
return utils::toNodeType(m_node->nodetype);
192192
}
193193

194+
/**
195+
* @brief Try to cast this SchemaNode to a Case node.
196+
* @throws Error If this node is not a case.
197+
*/
198+
Case SchemaNode::asCase() const
199+
{
200+
if (nodeType() != NodeType::Case) {
201+
throw Error("Schema node is not a case: " + path());
202+
}
203+
204+
return Case{m_node, m_ctx};
205+
}
206+
207+
/**
208+
* @brief Try to cast this SchemaNode to a Choice node.
209+
* @throws Error If this node is not a choice.
210+
*/
211+
Choice SchemaNode::asChoice() const
212+
{
213+
if (nodeType() != NodeType::Choice) {
214+
throw Error("Schema node is not a choice: " + path());
215+
}
216+
217+
return Choice{m_node, m_ctx};
218+
}
219+
194220
/**
195221
* @brief Try to cast this SchemaNode to a Container node.
196222
* @throws Error If this node is not a container.
@@ -401,6 +427,48 @@ bool AnyDataAnyXML::isMandatory() const
401427
return m_node->flags & LYS_MAND_TRUE;
402428
}
403429

430+
/**
431+
* @brief Checks whether this choice is mandatory.
432+
*
433+
* Wraps flag `LYS_MAND_TRUE`.
434+
*/
435+
bool Choice::isMandatory() const
436+
{
437+
return m_node->flags & LYS_MAND_TRUE;
438+
}
439+
440+
/**
441+
* @brief Retrieves the list of cases for this choice.
442+
*
443+
* Wraps `lysc_node_choice::cases`.
444+
*/
445+
std::vector<Case> Choice::cases() const
446+
{
447+
auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
448+
auto cases = reinterpret_cast<lysc_node*>(choice->cases);
449+
std::vector<Case> res;
450+
lysc_node* elem;
451+
LY_LIST_FOR(cases, elem)
452+
{
453+
res.emplace_back(Case(elem, m_ctx));
454+
}
455+
return res;
456+
}
457+
458+
/**
459+
* @brief Retrieves the default case for this choice.
460+
*
461+
* Wraps `lysc_node_choice::dflt`.
462+
*/
463+
std::optional<Case> Choice::defaultCase() const
464+
{
465+
auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
466+
if (!choice->dflt) {
467+
return std::nullopt;
468+
}
469+
return Case{reinterpret_cast<lysc_node*>(choice->dflt), m_ctx};
470+
}
471+
404472
/**
405473
* @brief Checks whether this container is mandatory.
406474
*

tests/context.cpp

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -709,33 +709,56 @@ TEST_CASE("context")
709709
auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG);
710710

711711
REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module
712-
+--rw anydataBasic? anydata
713-
+--rw anydataWithMandatoryChild anydata
714-
+--rw anyxmlBasic? anyxml
715-
+--rw anyxmlWithMandatoryChild anyxml
716-
+--rw leafBinary? binary
717-
+--rw leafBits? bits
718-
+--rw leafEnum? enumeration
719-
+--rw leafEnum2? enumeration
720-
+--rw leafNumber? int32
721-
+--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
722-
+--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
723-
+--rw leafString? string
724-
+--rw leafUnion? union
725-
+--rw meal? identityref
726-
+--ro leafWithConfigFalse? string
727-
+--rw leafWithDefaultValue? string
728-
+--rw leafWithDescription? string
729-
+--rw leafWithMandatoryTrue string
730-
x--rw leafWithStatusDeprecated? string
731-
o--rw leafWithStatusObsolete? string
732-
+--rw leafWithUnits? int32
733-
+--rw iid-valid? instance-identifier
734-
+--rw iid-relaxed? instance-identifier
735-
+--rw leafListBasic* string
736-
+--rw leafListWithDefault* int32
737-
+--rw leafListWithMinMaxElements* int32
738-
+--rw leafListWithUnits* int32
712+
+--rw anydataBasic? anydata
713+
+--rw anydataWithMandatoryChild anydata
714+
+--rw anyxmlBasic? anyxml
715+
+--rw anyxmlWithMandatoryChild anyxml
716+
+--rw choiceBasicContainer
717+
| +--rw (choiceBasic)?
718+
| +--:(case1)
719+
| | +--rw l? string
720+
| | +--rw ll* string
721+
| +--:(case2)
722+
| +--rw l2? string
723+
+--rw choiceWithMandatoryContainer
724+
| +--rw (choiceWithMandatory)
725+
| +--:(case3)
726+
| | +--rw l3? string
727+
| +--:(case4)
728+
| +--rw l4? string
729+
+--rw choiceWithDefaultContainer
730+
| +--rw (choiceWithDefault)?
731+
| +--:(case5)
732+
| | +--rw l5? string
733+
| +--:(case6)
734+
| +--rw l6? string
735+
+--rw implicitCaseContainer
736+
| +--rw (implicitCase)?
737+
| +--:(implicitLeaf)
738+
| +--rw implicitLeaf? string
739+
+--rw leafBinary? binary
740+
+--rw leafBits? bits
741+
+--rw leafEnum? enumeration
742+
+--rw leafEnum2? enumeration
743+
+--rw leafNumber? int32
744+
+--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
745+
+--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
746+
+--rw leafString? string
747+
+--rw leafUnion? union
748+
+--rw meal? identityref
749+
+--ro leafWithConfigFalse? string
750+
+--rw leafWithDefaultValue? string
751+
+--rw leafWithDescription? string
752+
+--rw leafWithMandatoryTrue string
753+
x--rw leafWithStatusDeprecated? string
754+
o--rw leafWithStatusObsolete? string
755+
+--rw leafWithUnits? int32
756+
+--rw iid-valid? instance-identifier
757+
+--rw iid-relaxed? instance-identifier
758+
+--rw leafListBasic* string
759+
+--rw leafListWithDefault* int32
760+
+--rw leafListWithMinMaxElements* int32
761+
+--rw leafListWithUnits* int32
739762
+--rw listBasic* [primary-key]
740763
| +--rw primary-key string
741764
+--rw listAdvancedWithOneKey* [lol]

tests/example_schema.hpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,66 @@ module type_module {
390390
mandatory true;
391391
}
392392
393+
container choiceBasicContainer {
394+
choice choiceBasic {
395+
case case1 {
396+
leaf l {
397+
type string;
398+
}
399+
leaf-list ll {
400+
type string;
401+
ordered-by user;
402+
}
403+
}
404+
case case2 {
405+
leaf l2 {
406+
type string;
407+
}
408+
}
409+
}
410+
}
411+
412+
container choiceWithMandatoryContainer {
413+
choice choiceWithMandatory {
414+
mandatory true;
415+
case case3 {
416+
leaf l3 {
417+
type string;
418+
}
419+
}
420+
case case4 {
421+
leaf l4 {
422+
type string;
423+
}
424+
}
425+
}
426+
}
427+
428+
container choiceWithDefaultContainer {
429+
choice choiceWithDefault {
430+
default case5;
431+
case case5 {
432+
leaf l5 {
433+
type string;
434+
}
435+
}
436+
case case6 {
437+
leaf l6 {
438+
type string;
439+
}
440+
}
441+
}
442+
}
443+
444+
container implicitCaseContainer {
445+
choice implicitCase {
446+
leaf implicitLeaf {
447+
type string;
448+
}
449+
}
450+
}
451+
452+
393453
leaf leafBinary {
394454
type binary;
395455
}

tests/schema_node.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ TEST_CASE("SchemaNode")
5858
],
5959
"type_module:anydataWithMandatoryChild": {"content": "test-string"},
6060
"type_module:anyxmlWithMandatoryChild": {"content": "test-string"},
61+
"type_module:choiceWithMandatoryContainer": {
62+
"l4": "test-string"
63+
},
6164
"type_module:containerWithMandatoryChild": {
6265
"leafWithMandatoryTrue": "test-string"
6366
},
@@ -180,6 +183,10 @@ TEST_CASE("SchemaNode")
180183
"/type_module:anydataWithMandatoryChild",
181184
"/type_module:anyxmlBasic",
182185
"/type_module:anyxmlWithMandatoryChild",
186+
"/type_module:choiceBasicContainer",
187+
"/type_module:choiceWithMandatoryContainer",
188+
"/type_module:choiceWithDefaultContainer",
189+
"/type_module:implicitCaseContainer",
183190
"/type_module:leafBinary",
184191
"/type_module:leafBits",
185192
"/type_module:leafEnum",
@@ -417,6 +424,71 @@ TEST_CASE("SchemaNode")
417424
REQUIRE(!ctx->findPath("/type_module:anyxmlBasic").asAnyDataAnyXML().isMandatory());
418425
}
419426

427+
DOCTEST_SUBCASE("Choice and Case")
428+
{
429+
std::string xpath;
430+
bool isMandatory = false;
431+
std::optional<std::string> defaultCase;
432+
std::vector<std::string> caseNames;
433+
std::optional<libyang::SchemaNode> root;
434+
435+
DOCTEST_SUBCASE("two cases with nothing fancy")
436+
{
437+
root = ctx->findPath("/type_module:choiceBasicContainer");
438+
caseNames = {"case1", "case2"};
439+
}
440+
441+
DOCTEST_SUBCASE("mandatory choice") {
442+
root = ctx->findPath("/type_module:choiceWithMandatoryContainer");
443+
isMandatory = true;
444+
caseNames = {"case3", "case4"};
445+
}
446+
447+
DOCTEST_SUBCASE("default choice") {
448+
root = ctx->findPath("/type_module:choiceWithDefaultContainer");
449+
defaultCase = "case5";
450+
caseNames = {"case5", "case6"};
451+
}
452+
453+
DOCTEST_SUBCASE("implicit case") {
454+
root = ctx->findPath("/type_module:implicitCaseContainer");
455+
caseNames = {"implicitLeaf"};
456+
}
457+
458+
// For testing purposes, we have each choice in its own container. As choice and case are not directly instantiable,
459+
// we wrap them in a container to simplify the testing process. It allows us to simply address the choice by its
460+
// container and then get the choice from it. It also prevents polluting the test schema with unnecessary nodes
461+
// and isolates the choice from other nodes.
462+
auto container = root->asContainer();
463+
auto choice = container.immediateChildren().begin()->asChoice();
464+
REQUIRE(choice.isMandatory() == isMandatory);
465+
REQUIRE(!!choice.defaultCase() == !!defaultCase);
466+
if (defaultCase) {
467+
REQUIRE(choice.defaultCase()->name() == *defaultCase);
468+
}
469+
std::vector<std::string> actualCaseNames;
470+
for (const auto& case_ : choice.cases()) {
471+
actualCaseNames.push_back(case_.name());
472+
}
473+
REQUIRE(actualCaseNames == caseNames);
474+
475+
// Also test child node access for one arbitrary choice/case combination
476+
if (root->path() == "/type_module:choiceBasicContainer") {
477+
REQUIRE(choice.cases().size() == 2);
478+
auto case1 = choice.cases()[0];
479+
auto children = case1.immediateChildren();
480+
auto it = children.begin();
481+
REQUIRE(it->asLeaf().name() == "l");
482+
++it;
483+
REQUIRE(it->asLeafList().name() == "ll");
484+
485+
auto case2 = choice.cases()[1];
486+
children = case2.immediateChildren();
487+
it = children.begin();
488+
REQUIRE(it->asLeaf().name() == "l2");
489+
}
490+
}
491+
420492
DOCTEST_SUBCASE("Container::isMandatory")
421493
{
422494
REQUIRE(ctx->findPath("/type_module:containerWithMandatoryChild").asContainer().isMandatory());

0 commit comments

Comments
 (0)