Skip to content

Commit 2cf0cc0

Browse files
committed
Implement extension plugins for YANG versioning
Add validation plugins for ietf-yang-semver and ietf-yang-revisions extensions: yang_semver.c: - Implements validation for ysv:version extension * Validates MAJOR.MINOR.PATCH format with optional modifiers * Ensures version uniqueness across module revisions * Checks for duplicate versions in same revision - Implements validation for ysv:recommended-min-version extension * Validates strict MAJOR.MINOR.PATCH format (no modifiers) * Ensures correct placement in import statements * Prevents duplicate declarations yang_revisions.c: - Implements validation for rev:non-backwards-compatible extension * Validates placement in revision statements * Prevents duplicate declarations - Implements validation for rev:recommended-min-date extension * Validates YYYY-MM-DD date format * Ensures correct placement in import statements * Prevents duplicate declarations
1 parent 792ed13 commit 2cf0cc0

File tree

2 files changed

+462
-0
lines changed

2 files changed

+462
-0
lines changed

src/plugins_exts/yang_revisions.c

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/**
2+
* @file ang_revisions.c
3+
* @author Joe Clarke <[email protected]>
4+
* @brief libyang extension plugin - YANG Revisions (ietf-yang-revisions)
5+
*
6+
* Copyright (c) 2025 Cisco Systems, Inc.
7+
*
8+
* This source code is licensed under BSD 3-Clause License (the "License").
9+
* You may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://opensource.org/licenses/BSD-3-Clause
13+
*/
14+
15+
#include <ctype.h>
16+
#include <stdint.h>
17+
#include <stdlib.h>
18+
#include <string.h>
19+
20+
#include "compat.h"
21+
#include "libyang.h"
22+
#include "plugins_exts.h"
23+
24+
/**
25+
* @brief Parse non-backwards-compatible extension instances.
26+
*
27+
* Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse.
28+
*/
29+
static LY_ERR
30+
non_backwards_compatible_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext)
31+
{
32+
struct lysp_revision *parent = NULL;
33+
LY_ARRAY_COUNT_TYPE u;
34+
35+
/* Check that the extension is instantiated at an allowed place - revision statement */
36+
if (ext->parent_stmt != LY_STMT_REVISION) {
37+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
38+
"Extension %s is allowed only in a revision statement, but it is placed in \"%s\" statement.",
39+
ext->name, lyplg_ext_stmt2str(ext->parent_stmt));
40+
return LY_EVALID;
41+
}
42+
43+
/* This extension should not have an argument */
44+
if (ext->argument && *ext->argument) {
45+
lyplg_ext_parse_log(pctx, ext, LY_LLWRN, 0,
46+
"Extension %s should not have an argument, but has: %s", ext->name, ext->argument);
47+
}
48+
49+
parent = ext->parent;
50+
51+
/* Check for duplication within the same revision */
52+
LY_ARRAY_FOR(parent->exts, u) {
53+
if ((&parent->exts[u] != ext) && parent->exts[u].record &&
54+
!strcmp(parent->exts[u].record->plugin.id, ext->record->plugin.id)) {
55+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
56+
"Extension %s is instantiated multiple times in the same revision.", ext->name);
57+
return LY_EVALID;
58+
}
59+
}
60+
61+
return LY_SUCCESS;
62+
}
63+
64+
/**
65+
* @brief Validate revision-date format (YYYY-MM-DD).
66+
*
67+
* @param[in] date Date string to validate.
68+
* @return LY_SUCCESS if valid, LY_EVALID otherwise.
69+
*/
70+
static LY_ERR
71+
revision_date_validate(const char *date)
72+
{
73+
int year, month, day;
74+
75+
if (!date || strlen(date) != 10) {
76+
return LY_EVALID;
77+
}
78+
79+
if (date[4] != '-' || date[7] != '-') {
80+
return LY_EVALID;
81+
}
82+
83+
if (sscanf(date, "%4d-%2d-%2d", &year, &month, &day) != 3) {
84+
return LY_EVALID;
85+
}
86+
87+
if (year < 1000 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31) {
88+
return LY_EVALID;
89+
}
90+
91+
return LY_SUCCESS;
92+
}
93+
94+
/**
95+
* @brief Parse recommended-min-date extension instances.
96+
*
97+
* Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse.
98+
*/
99+
static LY_ERR
100+
recommended_min_date_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext)
101+
{
102+
struct lysp_import *parent = NULL;
103+
LY_ARRAY_COUNT_TYPE u;
104+
105+
/* Check that the extension is instantiated at an allowed place - import statement */
106+
if (ext->parent_stmt != LY_STMT_IMPORT) {
107+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
108+
"Extension %s is allowed only in an import statement, but it is placed in \"%s\" statement.",
109+
ext->name, lyplg_ext_stmt2str(ext->parent_stmt));
110+
return LY_EVALID;
111+
}
112+
113+
/* Validate the argument format - must be revision-date (YYYY-MM-DD) */
114+
if (!ext->argument) {
115+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
116+
"Extension %s requires a revision-date argument (YYYY-MM-DD).", ext->name);
117+
return LY_EVALID;
118+
}
119+
120+
if (revision_date_validate(ext->argument) != LY_SUCCESS) {
121+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
122+
"Extension %s has invalid revision-date format: %s (expected YYYY-MM-DD)",
123+
ext->name, ext->argument);
124+
return LY_EVALID;
125+
}
126+
127+
parent = ext->parent;
128+
129+
/* Check for duplication within the same import */
130+
LY_ARRAY_FOR(parent->exts, u) {
131+
if ((&parent->exts[u] != ext) && parent->exts[u].record &&
132+
!strcmp(parent->exts[u].record->plugin.id, ext->record->plugin.id)) {
133+
lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID,
134+
"Extension %s is instantiated multiple times in the same import.", ext->name);
135+
return LY_EVALID;
136+
}
137+
}
138+
139+
return LY_SUCCESS;
140+
}
141+
142+
/**
143+
* @brief Plugin descriptions for the ietf-yang-revisions extensions
144+
*
145+
* Note that external plugins are supposed to use:
146+
*
147+
* LYPLG_EXTENSIONS = {
148+
*/
149+
const struct lyplg_ext_record plugins_yang_revisions[] = {
150+
{
151+
.module = "ietf-yang-revisions",
152+
.revision = "2025-01-28",
153+
.name = "non-backwards-compatible",
154+
155+
.plugin.id = "ly2 ietf-yang-revisions v1",
156+
.plugin.parse = non_backwards_compatible_parse,
157+
.plugin.compile = NULL,
158+
.plugin.printer_info = NULL,
159+
.plugin.printer_ctree = NULL,
160+
.plugin.printer_ptree = NULL,
161+
.plugin.node = NULL,
162+
.plugin.snode = NULL,
163+
.plugin.validate = NULL,
164+
.plugin.pfree = NULL,
165+
.plugin.cfree = NULL
166+
},
167+
{
168+
.module = "ietf-yang-revisions",
169+
.revision = "2025-01-28",
170+
.name = "recommended-min-date",
171+
172+
.plugin.id = "ly2 ietf-yang-revisions v1",
173+
.plugin.parse = recommended_min_date_parse,
174+
.plugin.compile = NULL,
175+
.plugin.printer_info = NULL,
176+
.plugin.printer_ctree = NULL,
177+
.plugin.printer_ptree = NULL,
178+
.plugin.node = NULL,
179+
.plugin.snode = NULL,
180+
.plugin.validate = NULL,
181+
.plugin.pfree = NULL,
182+
.plugin.cfree = NULL
183+
},
184+
{0} /* terminating zeroed record */
185+
};

0 commit comments

Comments
 (0)