Skip to content

Commit d07191f

Browse files
authored
Add YAML validator with JSON Schema support (#187)
* Implement new YAML validator Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Adding exceptions Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Update YAML validator Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Update YamlReader_participants Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Adding tests for YAML validator Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Adding nlohmann json schema as thirdparty Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Adding nlohmann json also as thirdparty Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Fixing nlohmann json.hpp as thirdparty Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Fix CI: uncrustify Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Fix CI: Windows tests Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Remove router specific part Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Fix typo Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Allow loading schema from raw string Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Try to fix installation in router Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Try to fix installation in router 2 Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Try to fix router CI Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Simplify tests and fix CI on Windows Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Mode dependency from nlomann to CPP Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Fix uncrustify Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Better checks of types and formats Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> * Windows needs 'uint64_t' instead of 'u_int64_t' Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com> --------- Signed-off-by: David Laseca Perez <davidlaseca@eprosima.com>
1 parent 8eaf047 commit d07191f

36 files changed

Lines changed: 29398 additions & 155 deletions

ddspipe_participants/src/cpp/xml/XmlHandlerConfiguration.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ bool XmlHandlerConfiguration::is_valid(
3030
{
3131
if (!utils::is_file_accessible(file.c_str(), utils::FileAccessMode::read))
3232
{
33-
error_msg << "File " << file << " has not exist or does not have read access. ";
33+
error_msg << "File " << file << " does not exist or does not have read access. ";
3434
return false;
3535
}
3636
}

ddspipe_yaml/CMakeLists.txt

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,47 @@ project(
6060
# - Configure log depending on LOG_INFO flag and CMake type
6161
configure_project_cpp()
6262

63+
file(
64+
GLOB_RECURSE SOURCE_FILES
65+
"${PROJECT_SOURCE_DIR}/src/cpp/*.c*"
66+
"${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json_schema_validator/*.cpp"
67+
)
68+
69+
file(
70+
GLOB_RECURSE INCLUDE_FILES
71+
"${PROJECT_SOURCE_DIR}/include/*.hpp"
72+
"${PROJECT_SOURCE_DIR}/include/testing/*.hpp"
73+
"${PROJECT_SOURCE_DIR}/include/impl/*.ipp"
74+
"${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json/nlohmann/*.hpp"
75+
"${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json_schema_validator/*.hpp"
76+
"${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json_schema_validator/nlohmann/*.hpp"
77+
)
78+
79+
#set(NLOHMANN_JSON_INCLUDE_DIR
80+
# "${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json/nlohmann")
81+
#
82+
#set(NLOHMANN_JSON_SCHEMA_INCLUDE_DIR
83+
# "${PROJECT_SOURCE_DIR}/../thirdparty/nlohmann_json_schema_validator/nlohmann")
84+
#
85+
#install(
86+
# DIRECTORY
87+
# "${NLOHMANN_JSON_INCLUDE_DIR}"
88+
# "${NLOHMANN_JSON_SCHEMA_INCLUDE_DIR}"
89+
# DESTINATION
90+
# ${INCLUDE_INSTALL_DIR}
91+
# COMPONENT
92+
# headers
93+
# FILES_MATCHING
94+
# PATTERN "*.h"
95+
# PATTERN "*.hpp"
96+
#)
97+
6398
# Compile C++ library
6499
compile_library(
65-
"${PROJECT_SOURCE_DIR}/src/cpp" # Source directory
66-
"${PROJECT_SOURCE_DIR}/include" # Include directory
100+
"${PROJECT_SOURCE_DIR}/src/cpp" # Source directory (not used because the list in ${SOURCE_FILES} is used instead)
101+
"${PROJECT_SOURCE_DIR}/include" # Include directory (not used because the list in ${INCLUDE_FILES} is used instead)
102+
"${SOURCE_FILES}" # List of source files to compile
103+
"${INCLUDE_FILES}" # List of include files
67104
)
68105

69106
###############################################################################

ddspipe_yaml/include/ddspipe_yaml/YamlReader.hpp

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ enum YamlReaderVersion
127127
* Every method is implemented
128128
*/
129129
class
130-
DDSPIPE_YAML_DllAPI
131-
YamlReader
130+
DDSPIPE_YAML_DllAPI
131+
YamlReader
132132
{
133133
public:
134134

@@ -167,14 +167,32 @@ class
167167
const Yaml& yml,
168168
const TagType& tag);
169169

170-
//! TODO comment
171-
template <typename T>
170+
/**
171+
* @brief Takes the element \c yml and builds the object \c T
172+
*
173+
* This method takes a yaml node and then builds and return and object of type \c T.
174+
*
175+
* @tparam T type of the object to build
176+
* @param yml base yaml
177+
* @param version configuration version
178+
*/
179+
template<typename T>
172180
static T get(
173181
const Yaml& yml,
174182
const YamlReaderVersion version);
175183

176-
//! Get element inside \c tag
177-
template <typename T>
184+
/**
185+
* @brief Extracts the sub-yaml from the \c tag and then builds the object \c T
186+
*
187+
* This method calls \c get_value_in_tag to extract the sub-yaml from the \c tag and then it calls \c get to build
188+
* the object \c T.
189+
*
190+
* @tparam T type of the object to build
191+
* @param yml base yaml
192+
* @param tag key to yaml containing the object
193+
* @param version configuration version
194+
*/
195+
template<typename T>
178196
static T get(
179197
const Yaml& yml,
180198
const TagType& tag,
@@ -190,28 +208,28 @@ class
190208
* the default values are initialized with the default constructor, and then are overwritten by the yaml.
191209
* [this problem arises because C++ does not allow different order of parameters in method call]
192210
*/
193-
template <typename T>
211+
template<typename T>
194212
static void fill(
195213
T& object,
196214
const Yaml& yml,
197215
const YamlReaderVersion version);
198216

199217
//! Fill an element given by parameter with the values inside \c tag in \c yml
200-
template <typename T>
218+
template<typename T>
201219
static void fill(
202220
T& object,
203221
const Yaml& yml,
204222
const TagType& tag,
205223
const YamlReaderVersion version);
206224

207225
//! TODO comment
208-
template <typename T>
226+
template<typename T>
209227
static std::list<T> get_list(
210228
const Yaml& yml,
211229
const YamlReaderVersion version);
212230

213231
//! Get list inside \c tag
214-
template <typename T>
232+
template<typename T>
215233
static std::list<T> get_list(
216234
const Yaml& yml,
217235
const TagType& tag,
@@ -228,19 +246,19 @@ class
228246
* @param version configuration version
229247
* @return set of elements
230248
*/
231-
template <typename T>
249+
template<typename T>
232250
static std::set<T> get_set(
233251
const Yaml& yml,
234252
const TagType& tag,
235253
const YamlReaderVersion version);
236254

237255
//! TODO comment
238-
template <typename T>
256+
template<typename T>
239257
static T get_scalar(
240258
const Yaml& yml);
241259

242260
//! Get scalar value inside \c tag
243-
template <typename T>
261+
template<typename T>
244262
static T get_scalar(
245263
const Yaml& yml,
246264
const TagType& tag);
@@ -276,26 +294,26 @@ class
276294
const TagType& tag);
277295

278296
//! TODO comment
279-
template <typename T>
297+
template<typename T>
280298
static T get_enumeration(
281299
const Yaml& yml,
282300
const std::map<TagType, T>& enum_values);
283301

284302
//! Get enumeration value inside \c tag
285-
template <typename T>
303+
template<typename T>
286304
static T get_enumeration(
287305
const Yaml& yml,
288306
const TagType& tag,
289307
const std::map<TagType, T>& enum_values);
290308

291309
//! TODO comment
292-
template <typename T>
310+
template<typename T>
293311
static T get_enumeration_from_builder(
294312
const Yaml& yml,
295313
const utils::EnumBuilder<T>& enum_builder);
296314

297315
//! Get enumeration value inside \c tag
298-
template <typename T>
316+
template<typename T>
299317
static T get_enumeration_from_builder(
300318
const Yaml& yml,
301319
const TagType& tag,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2026 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <memory>
18+
19+
#include <ddspipe_yaml/library/library_dll.h>
20+
#include <ddspipe_yaml/Yaml.hpp>
21+
22+
namespace nlohmann {
23+
namespace json_schema {
24+
class json_validator;
25+
} // namespace json_schema
26+
} // namespace nlohmann
27+
28+
namespace eprosima {
29+
namespace ddspipe {
30+
namespace yaml {
31+
32+
/**
33+
* @brief Validates YAML objects against a JSON Schema (draft-07).
34+
*
35+
* Loads a schema from a file path or a raw string via \c InputType, then
36+
* validates \c Yaml nodes against it. Custom format checkers (e.g. IPv4/IPv6)
37+
* are applied automatically during validation.
38+
*/
39+
class DDSPIPE_YAML_DllAPI YamlValidator
40+
{
41+
private:
42+
43+
std::unique_ptr<nlohmann::json_schema::json_validator> validator;
44+
45+
protected:
46+
47+
/**
48+
* @brief Custom format checker registered with the JSON Schema validator.
49+
*
50+
* Called automatically during validation when the schema uses the \c format keyword.
51+
* Supported format values are \c "v4" (IPv4) and \c "v6" (IPv6).
52+
*
53+
* @param format Name of the format to check (e.g. \c "v4" or \c "v6").
54+
* @param value Value to validate against the given format.
55+
*
56+
* @throws std::invalid_argument if \c value does not conform to \c format.
57+
*/
58+
static void format_checker(
59+
const std::string& format,
60+
const std::string& value);
61+
62+
public:
63+
64+
enum class InputType
65+
{
66+
FROM_STRING,
67+
FROM_FILE
68+
};
69+
70+
/**
71+
* @brief Default constructor. Creates a \c YamlValidator with no schema loaded.
72+
*
73+
* \c validate_YAML will return \c false until a schema is set via \c set_schema.
74+
*/
75+
YamlValidator();
76+
77+
/**
78+
* @brief Construct a \c YamlValidator and load a JSON Schema (draft-07).
79+
*
80+
* @param input_type Whether \c schema_string is a file path (\c InputType::FROM_FILE)
81+
* or raw JSON content (\c InputType::FROM_STRING).
82+
* @param schema_string File path or raw JSON string of the schema to load.
83+
*
84+
* @throws utils::ConfigurationException if the schema cannot be read, parsed, or is not
85+
* a valid JSON Schema draft-07.
86+
*/
87+
explicit YamlValidator(
88+
InputType input_type,
89+
const std::string& schema_string);
90+
91+
/**
92+
* @brief Default destructor. Needed because \c std::unique_ptr needs the full definition
93+
* of \c nlohmann::json_schema::json_validator when destroying it.
94+
*/
95+
~YamlValidator();
96+
97+
/**
98+
* @brief Move assignment operator. Needed because \c std::unique_ptr needs the full definition
99+
* of \c nlohmann::json_schema::json_validator when move-assigning (the old value is destroyed).
100+
*/
101+
YamlValidator& operator =(
102+
YamlValidator&&);
103+
104+
/**
105+
* @brief Set or replace the root schema of the validator.
106+
*
107+
* @param input_type Whether \c schema_string is a file path (\c InputType::FROM_FILE)
108+
* or raw JSON content (\c InputType::FROM_STRING).
109+
* @param schema_string File path or raw JSON string of the schema to load.
110+
*
111+
* @throws utils::ConfigurationException if the schema cannot be read, parsed, or is not
112+
* a valid JSON Schema draft-07.
113+
*/
114+
void set_schema(
115+
InputType input_type,
116+
const std::string& schema_string);
117+
118+
/**
119+
* @brief Validate a YAML object against the loaded schema.
120+
*
121+
* Returns \c false immediately if no schema has been loaded.
122+
*
123+
* @param yml Yaml object to validate.
124+
* @param display_errors If \c true, prints validation errors to \c stderr.
125+
* @return \c true if \c yml conforms to the schema, \c false otherwise.
126+
*
127+
* @throws utils::ConfigurationException if \c yml cannot be converted to JSON.
128+
*/
129+
bool validate_YAML(
130+
const Yaml& yml,
131+
bool display_errors = true);
132+
};
133+
134+
} /* namespace yaml */
135+
} /* namespace ddspipe */
136+
} /* namespace eprosima */

ddspipe_yaml/include/ddspipe_yaml/YamlWriter.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Yaml add_tag(
6060
* - native types (int, string, boolean)
6161
* - collections (vector, set, map)
6262
*/
63-
template <typename T>
63+
template<typename T>
6464
void set(
6565
Yaml& yml,
6666
const T& value);
@@ -78,14 +78,14 @@ void set(
7878
*
7979
* @tparam T type of the value to set in the yaml.
8080
*/
81-
template <typename T>
81+
template<typename T>
8282
void set(
8383
Yaml& yml,
8484
const T& value,
8585
bool is_compact);
8686

8787
//! Set the \c value in a new yaml in \c yml under \c tag .
88-
template <typename T>
88+
template<typename T>
8989
void set_in_tag(
9090
Yaml& yml,
9191
const TagType& tag,

0 commit comments

Comments
 (0)