Branch data Line data Source code
1 : : // Copyright (C) 2010, 2011 Internet Systems Consortium.
2 : : //
3 : : // Permission to use, copy, modify, and distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
8 : : // DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
9 : : // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
10 : : // INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
11 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
12 : : // FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
13 : : // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
14 : : // WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 : :
16 : : #include <config/module_spec.h>
17 : :
18 : : #include <sstream>
19 : : #include <iostream>
20 : : #include <fstream>
21 : : #include <cerrno>
22 : :
23 : : #include <boost/foreach.hpp>
24 : :
25 : : // todo: add more context to thrown ModuleSpecErrors?
26 : :
27 : : using namespace isc::data;
28 : : using namespace isc::config;
29 : :
30 : : namespace {
31 : : //
32 : : // Private functions
33 : : //
34 : :
35 : : void
36 : 12026 : check_leaf_item(ConstElementPtr spec, const std::string& name,
37 : : Element::types type, bool mandatory)
38 : : {
39 [ + + ]: 12026 : if (spec->contains(name)) {
40 [ + + ]: 23386 : if (spec->get(name)->getType() == type) {
41 : 11993 : return;
42 : : } else {
43 [ + - ][ + - ]: 30 : isc_throw(ModuleSpecError,
[ + - ][ + - ]
[ + - ]
44 : : name + " not of type " + Element::typeToName(type));
45 : : }
46 [ + + ]: 333 : } else if (mandatory) {
47 : : // todo: want parent item name, and perhaps some info about location
48 : : // in list? or just catch and throw new...
49 : : // or make this part non-throwing and check return value...
50 [ + - ][ + - ]: 36 : isc_throw(ModuleSpecError, name + " missing in " + spec->str());
[ + - ][ + - ]
[ + - ]
51 : : }
52 : : }
53 : :
54 : : void check_config_item_list(ConstElementPtr spec);
55 : :
56 : : void
57 : 2496 : check_config_item(ConstElementPtr spec) {
58 [ + + ]: 7488 : check_leaf_item(spec, "item_name", Element::string, true);
59 [ + + ]: 7482 : check_leaf_item(spec, "item_type", Element::string, true);
60 [ + + ]: 7476 : check_leaf_item(spec, "item_optional", Element::boolean, true);
61 : : check_leaf_item(spec, "item_default",
62 [ + - ][ + - ]: 7470 : Element::nameToType(spec->get("item_type")->stringValue()),
63 [ + - ][ + - ]: 4980 : !spec->get("item_optional")->boolValue()
64 [ + - ][ + + ]: 7462 : );
[ + - ][ + + ]
65 : :
66 : : // if list, check the list specification
67 [ + - ][ + - ]: 4944 : if (Element::nameToType(spec->get("item_type")->stringValue()) == Element::list) {
[ + - ][ + + ]
68 [ + - ]: 1044 : check_leaf_item(spec, "list_item_spec", Element::map, true);
69 [ + - ][ + - ]: 696 : check_config_item(spec->get("list_item_spec"));
70 : : }
71 : :
72 [ + - ][ + - ]: 4944 : if (spec->get("item_type")->stringValue() == "map") {
[ + - ][ + + ]
73 [ + - ]: 999 : check_leaf_item(spec, "map_item_spec", Element::list, true);
74 [ + - ][ + - ]: 666 : check_config_item_list(spec->get("map_item_spec"));
75 [ + - ][ + - ]: 4278 : } else if (spec->get("item_type")->stringValue() == "named_set") {
[ + - ][ + + ]
76 [ + - ]: 6 : check_leaf_item(spec, "named_set_item_spec", Element::map, true);
77 [ + - ][ + - ]: 4 : check_config_item(spec->get("named_set_item_spec"));
78 : : }
79 : 2472 : }
80 : :
81 : : void
82 : 641 : check_config_item_list(ConstElementPtr spec) {
83 [ + + ]: 641 : if (spec->getType() != Element::list) {
84 [ + - ][ + - ]: 4 : isc_throw(ModuleSpecError, "config_data is not a list of elements");
85 : : }
86 [ + + + - ]: 8335 : BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
[ + - ][ + + ]
[ + + ]
87 [ + + ]: 1942 : check_config_item(item);
88 : : }
89 : 615 : }
90 : :
91 : : // checks whether the given element is a valid statistics specification
92 : : // returns false if the specification is bad
93 : : bool
94 : 61 : check_format(ConstElementPtr value, ConstElementPtr format_name) {
95 : : typedef std::map<std::string, std::string> format_types;
96 : : format_types time_formats;
97 : : // TODO: should be added other format types if necessary
98 : : time_formats.insert(
99 [ + - ][ + - ]: 122 : format_types::value_type("date-time", "%Y-%m-%dT%H:%M:%SZ") );
[ + - ]
100 : : time_formats.insert(
101 [ + - ][ + - ]: 122 : format_types::value_type("date", "%Y-%m-%d") );
[ + - ]
102 : : time_formats.insert(
103 [ + - ][ + - ]: 122 : format_types::value_type("time", "%H:%M:%S") );
[ + - ]
104 [ + + ][ + - ]: 335 : BOOST_FOREACH (const format_types::value_type& f, time_formats) {
[ + - ][ + + ]
[ + + ]
105 [ + - ][ + + ]: 128 : if (format_name->stringValue() == f.first) {
106 : : struct tm tm;
107 : 55 : std::vector<char> buf(32);
108 : : memset(&tm, 0, sizeof(tm));
109 : : // reverse check
110 : 110 : return (strptime(value->stringValue().c_str(),
111 [ + - ][ # # ]: 55 : f.second.c_str(), &tm) != NULL
112 : 36 : && strftime(&buf[0], buf.size(),
113 : 36 : f.second.c_str(), &tm) != 0
114 : 72 : && strncmp(value->stringValue().c_str(),
115 [ + + + - : 127 : &buf[0], buf.size()) == 0);
+ - ][ + + ]
[ + + ]
116 : : }
117 : : }
118 : : return (false);
119 : : }
120 : :
121 : : void check_statistics_item_list(ConstElementPtr spec);
122 : :
123 : : void
124 : 67 : check_statistics_item_list(ConstElementPtr spec) {
125 [ + + ]: 67 : if (spec->getType() != Element::list) {
126 [ + - ][ + - ]: 6 : isc_throw(ModuleSpecError, "statistics is not a list of elements");
127 : : }
128 [ + + + - ]: 811 : BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
[ + - ][ + + ]
[ + + ]
129 [ + - ]: 204 : check_config_item(item);
130 : : // additional checks for statistics
131 [ + - ][ + + ]: 612 : check_leaf_item(item, "item_title", Element::string, true);
132 [ + - ][ + + ]: 609 : check_leaf_item(item, "item_description", Element::string, true);
133 [ + - ][ + - ]: 606 : check_leaf_item(item, "item_format", Element::string, false);
134 : : // checks name of item_format and validation of item_default
135 [ + - ][ + - ]: 460 : if (item->contains("item_format")
[ + + ][ + + ]
[ + + ][ # # ]
136 [ + - ][ + - ]: 258 : && item->contains("item_default")) {
[ + + ][ # # ]
137 [ + + ]: 156 : if(!check_format(item->get("item_default"),
138 [ + - ]: 156 : item->get("item_format"))) {
[ + - + - ]
[ + - ][ + - ]
139 [ + - ][ + - ]: 277 : isc_throw(ModuleSpecError,
[ + - ]
140 : : "item_default not valid type of item_format");
141 : : }
142 : : }
143 : : }
144 : 41 : }
145 : :
146 : : void
147 : 91 : check_command(ConstElementPtr spec) {
148 [ + + ]: 273 : check_leaf_item(spec, "command_name", Element::string, true);
149 [ + + ]: 267 : check_leaf_item(spec, "command_args", Element::list, true);
150 [ + - ][ + + ]: 170 : check_config_item_list(spec->get("command_args"));
151 : 83 : }
152 : :
153 : : void
154 : 204 : check_command_list(ConstElementPtr spec) {
155 [ + + ]: 204 : if (spec->getType() != Element::list) {
156 [ + - ][ + - ]: 4 : isc_throw(ModuleSpecError, "commands is not a list of elements");
157 : : }
158 [ + + + - ]: 542 : BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
[ + - ][ + + ]
[ + + ]
159 [ + + ]: 91 : check_command(item);
160 : : }
161 : 194 : }
162 : :
163 : : void
164 : 295 : check_data_specification(ConstElementPtr spec) {
165 [ + + ]: 885 : check_leaf_item(spec, "module_name", Element::string, true);
166 [ + + ]: 873 : check_leaf_item(spec, "module_description", Element::string, false);
167 : : // config_data is not mandatory; module could just define
168 : : // commands and have no config
169 [ + - ][ + + ]: 290 : if (spec->contains("config_data")) {
170 [ + - ][ + + ]: 446 : check_config_item_list(spec->get("config_data"));
171 : : }
172 [ + - ][ + + ]: 266 : if (spec->contains("commands")) {
173 [ + - ][ + + ]: 408 : check_command_list(spec->get("commands"));
174 : : }
175 [ + - ][ + + ]: 256 : if (spec->contains("statistics")) {
176 [ + - ][ + + ]: 134 : check_statistics_item_list(spec->get("statistics"));
177 : : }
178 : 230 : }
179 : :
180 : : // checks whether the given element is a valid module specification
181 : : // throws a ModuleSpecError if the specification is bad
182 : : void
183 : 295 : check_module_specification(ConstElementPtr def) {
184 : : try {
185 [ + + ]: 295 : check_data_specification(def);
186 [ + + ]: 69 : } catch (const TypeError& te) {
187 [ - + ][ - + ]: 8 : isc_throw(ModuleSpecError, te.what());
[ - + ]
188 : : }
189 : 230 : }
190 : : }
191 : :
192 : : namespace isc {
193 : : namespace config {
194 : : //
195 : : // Public functions
196 : : //
197 : :
198 : 295 : ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
199 : : const bool check)
200 : 0 : throw(ModuleSpecError)
201 : :
202 : : {
203 [ + - ]: 295 : module_specification = module_spec_element;
204 [ + - ]: 295 : if (check) {
205 [ + + ]: 295 : check_module_specification(module_specification);
206 : : }
207 : 230 : }
208 : :
209 : : ConstElementPtr
210 : 2 : ModuleSpec::getCommandsSpec() const {
211 [ + - ][ + + ]: 2 : if (module_specification->contains("commands")) {
212 [ + - ]: 1 : return (module_specification->get("commands"));
213 : : } else {
214 : : return (ElementPtr());
215 : : }
216 : : }
217 : :
218 : : ConstElementPtr
219 : 64 : ModuleSpec::getConfigSpec() const {
220 [ + - ][ + + ]: 64 : if (module_specification->contains("config_data")) {
221 [ + - ]: 63 : return (module_specification->get("config_data"));
222 : : } else {
223 : : return (ElementPtr());
224 : : }
225 : : }
226 : :
227 : : ConstElementPtr
228 : 2 : ModuleSpec::getStatisticsSpec() const {
229 [ + - ][ + + ]: 2 : if (module_specification->contains("statistics")) {
230 [ + - ]: 1 : return (module_specification->get("statistics"));
231 : : } else {
232 : : return (ElementPtr());
233 : : }
234 : : }
235 : :
236 : : const std::string
237 : 33 : ModuleSpec::getModuleName() const {
238 [ + - ][ + - ]: 66 : return (module_specification->get("module_name")->stringValue());
239 : : }
240 : :
241 : : const std::string
242 : 2 : ModuleSpec::getModuleDescription() const {
243 [ + - ][ + + ]: 2 : if (module_specification->contains("module_description")) {
244 [ + - ][ + - ]: 2 : return (module_specification->get("module_description")->stringValue());
245 : : } else {
246 : 2 : return (std::string(""));
247 : : }
248 : : }
249 : :
250 : : bool
251 : 154 : ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
252 [ + - ]: 154 : ConstElementPtr spec = module_specification->find("config_data");
253 [ + - ]: 308 : return (validateSpecList(spec, data, full, ElementPtr()));
254 : : }
255 : :
256 : : bool
257 : 2 : ModuleSpec::validateStatistics(ConstElementPtr data, const bool full) const {
258 [ + - ]: 2 : ConstElementPtr spec = module_specification->find("statistics");
259 [ + - ]: 4 : return (validateSpecList(spec, data, full, ElementPtr()));
260 : : }
261 : :
262 : : bool
263 : 12 : ModuleSpec::validateCommand(const std::string& command,
264 : : ConstElementPtr args,
265 : : ElementPtr errors) const
266 : : {
267 [ + + ]: 12 : if (args->getType() != Element::map) {
268 : 1 : errors->add(Element::create("args for command " +
269 [ + - ][ + - ]: 3 : command + " is not a map"));
[ + - ]
270 : 1 : return (false);
271 : : }
272 : :
273 [ + - ]: 11 : ConstElementPtr commands_spec = module_specification->find("commands");
274 [ - + ]: 11 : if (!commands_spec) {
275 : : // there are no commands according to the spec.
276 [ # # ][ # # ]: 0 : errors->add(Element::create("The given module has no commands"));
277 : : return (false);
278 : : }
279 : :
280 [ + - ][ + + ]: 53 : BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
[ + - ][ + - ]
[ + + ][ + + ]
281 [ + - ][ + - ]: 36 : if (cur_command->get("command_name")->stringValue() == command) {
[ + - ][ + + ]
282 : 20 : return (validateSpecList(cur_command->get("command_args"),
283 [ + - ][ + - ]: 10 : args, true, errors));
[ + - ]
284 : : }
285 : : }
286 : :
287 : : // this command is unknown
288 [ + - ][ + - ]: 3 : errors->add(Element::create("Unknown command " + command));
[ + - ]
289 : : return (false);
290 : : }
291 : :
292 : : bool
293 : 10 : ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
294 : : ElementPtr errors) const
295 : : {
296 [ + - ]: 10 : ConstElementPtr spec = module_specification->find("config_data");
297 [ + - ]: 20 : return (validateSpecList(spec, data, full, errors));
298 : : }
299 : :
300 : : bool
301 : 1 : ModuleSpec::validateStatistics(ConstElementPtr data, const bool full,
302 : : ElementPtr errors) const
303 : : {
304 [ + - ]: 1 : ConstElementPtr spec = module_specification->find("statistics");
305 [ + - ]: 2 : return (validateSpecList(spec, data, full, errors));
306 : : }
307 : :
308 : : ModuleSpec
309 : 83 : moduleSpecFromFile(const std::string& file_name, const bool check)
310 : 0 : throw(JSONError, ModuleSpecError)
311 : : {
312 [ + - ][ + - ]: 166 : std::ifstream file;
[ + - ]
313 : :
314 [ + - ]: 83 : file.open(file_name.c_str());
315 [ + + ]: 83 : if (!file) {
316 [ + - ]: 4 : std::stringstream errs;
317 [ + - ][ + - ]: 2 : errs << "Error opening " << file_name << ": " << strerror(errno);
[ + - ][ + - ]
318 [ + - ][ + - ]: 6 : isc_throw(ModuleSpecError, errs.str());
[ + - ]
319 : : }
320 : :
321 [ + - ]: 81 : ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
322 [ + - ][ + - ]: 81 : if (module_spec_element->contains("module_spec")) {
[ + + ]
323 [ + - ][ + - ]: 189 : return (ModuleSpec(module_spec_element->get("module_spec"), check));
[ + + ]
324 : : } else {
325 [ + - ][ + - ]: 4 : isc_throw(ModuleSpecError, "No module_spec in specification");
[ + - ]
326 : : }
327 : : }
328 : :
329 : : ModuleSpec
330 : 40 : moduleSpecFromFile(std::ifstream& in, const bool check)
331 : 0 : throw(JSONError, ModuleSpecError)
332 : : {
333 [ + - ]: 40 : ConstElementPtr module_spec_element = Element::fromJSON(in);
334 [ + - ][ + - ]: 40 : if (module_spec_element->contains("module_spec")) {
[ + + ]
335 [ + - ][ + - ]: 114 : return (ModuleSpec(module_spec_element->get("module_spec"), check));
[ + - ]
336 : : } else {
337 [ + - ][ + - ]: 4 : isc_throw(ModuleSpecError, "No module_spec in specification");
[ + - ]
338 : : }
339 : : }
340 : :
341 : :
342 : : namespace {
343 : : //
344 : : // private functions
345 : : //
346 : :
347 : : //
348 : : // helper functions for validation
349 : : //
350 : : bool
351 : 299 : check_type(ConstElementPtr spec, ConstElementPtr element) {
352 : 299 : std::string cur_item_type;
353 [ + - ][ + - ]: 897 : cur_item_type = spec->get("item_type")->stringValue();
[ + - ]
354 [ + - ][ + + ]: 299 : if (cur_item_type == "any") {
355 : : return (true);
356 : : }
357 [ + + + + : 295 : switch (element->getType()) {
+ + - ]
358 : : case Element::integer:
359 [ + - ]: 67 : return (cur_item_type == "integer");
360 : : break;
361 : : case Element::real:
362 [ + - ]: 11 : return (cur_item_type == "real");
363 : : break;
364 : : case Element::boolean:
365 [ + - ]: 26 : return (cur_item_type == "boolean");
366 : : break;
367 : : case Element::string:
368 [ + - ]: 86 : return (cur_item_type == "string");
369 : : break;
370 : : case Element::list:
371 [ + - ]: 31 : return (cur_item_type == "list");
372 : : break;
373 : : case Element::map:
374 [ + - ]: 74 : return (cur_item_type == "map" ||
375 [ + + ][ + - ]: 74 : cur_item_type == "named_set");
[ + - ]
376 : : break;
377 : : }
378 : : return (false);
379 : : }
380 : : }
381 : :
382 : : bool
383 : 237 : ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
384 : : const bool full, ElementPtr errors) const
385 : : {
386 [ + - + + ]: 474 : if (!check_type(spec, data)) {
387 : : // we should do some proper error feedback here
388 : : // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
389 : : // std::cout << spec << std::endl;
390 [ + + ]: 10 : if (errors) {
391 [ + - ]: 6 : errors->add(Element::create("Type mismatch"));
392 : : }
393 : : return (false);
394 : : }
395 [ + + ]: 227 : if (data->getType() == Element::list) {
396 [ + - ]: 30 : ConstElementPtr list_spec = spec->get("list_item_spec");
397 [ + - ][ + + ]: 251 : BOOST_FOREACH(ConstElementPtr list_el, data->listValue()) {
[ + - ][ + - ]
[ + + ][ + + ]
398 [ + - ][ + + ]: 124 : if (!check_type(list_spec, list_el)) {
399 [ - + ]: 1 : if (errors) {
400 [ # # ][ # # ]: 0 : errors->add(Element::create("Type mismatch"));
401 : : }
402 : : return (false);
403 : : }
404 [ + - ][ + - ]: 122 : if (list_spec->get("item_type")->stringValue() == "map") {
[ + - ][ + - ]
[ + + ]
405 [ + - ][ + + ]: 106 : if (!validateItem(list_spec, list_el, full, errors)) {
406 : : return (false);
407 : : }
408 : : }
409 : : }
410 : : }
411 [ + + ]: 218 : if (data->getType() == Element::map) {
412 : : // either a normal 'map' or a 'named set' (determined by which
413 : : // subspecification it has)
414 [ + - ][ + + ]: 52 : if (spec->contains("map_item_spec")) {
415 [ + - ][ + - ]: 150 : if (!validateSpecList(spec->get("map_item_spec"), data, full, errors)) {
[ + - ][ + + ]
416 : : return (false);
417 : : }
418 : : } else {
419 : : typedef std::pair<std::string, ConstElementPtr> maptype;
420 : :
421 [ + + ][ + - ]: 9 : BOOST_FOREACH(maptype m, data->mapValue()) {
[ + - ][ + + ]
[ + + ]
422 [ + - ][ + - ]: 12 : if (!validateItem(spec->get("named_set_item_spec"), m.second, full, errors)) {
[ + - ][ + + ]
423 : : return (false);
424 : : }
425 : : }
426 : : }
427 : : }
428 [ + - ][ + + ]: 208 : if (spec->contains("item_format")) {
429 [ + - ][ + - ]: 27 : if (!check_format(data, spec->get("item_format"))) {
[ + + ]
430 [ + + ]: 6 : if (errors) {
431 [ + - ]: 6 : errors->add(Element::create("Format mismatch"));
432 : : }
433 : : return (false);
434 : : }
435 : : }
436 : : return (true);
437 : : }
438 : :
439 : : // spec is a map with item_name etc, data is a map
440 : : bool
441 : 404 : ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
442 : : const bool full, ElementPtr errors) const
443 : : {
444 [ + - ][ + - ]: 1212 : std::string item_name = spec->get("item_name")->stringValue();
445 [ + - ][ + - ]: 808 : bool optional = spec->get("item_optional")->boolValue();
[ + - ]
446 : : ConstElementPtr data_el;
447 [ + - ][ + - ]: 404 : data_el = data->get(item_name);
448 : :
449 [ + + ]: 404 : if (data_el) {
450 [ + - ][ + + ]: 422 : if (!validateItem(spec, data_el, full, errors)) {
451 : : return (false);
452 : : }
453 : : } else {
454 [ + + ][ + + ]: 193 : if (!optional && full) {
455 [ + + ]: 148 : if (errors) {
456 [ + - ][ + - ]: 2 : errors->add(Element::create("Non-optional value missing"));
457 : : }
458 : : return (false);
459 : : }
460 : : }
461 : : return (true);
462 : : }
463 : :
464 : : // spec is a list of maps, data is a map
465 : : bool
466 : 227 : ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
467 : : const bool full, ElementPtr errors) const
468 : : {
469 : 227 : bool validated = true;
470 [ + + + - ]: 1843 : BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
[ + - ][ + + ]
[ + + ]
471 [ + - ][ + + ]: 808 : if (!validateSpec(cur_spec_el, data, full, errors)) {
472 : 404 : validated = false;
473 : : }
474 : : }
475 : :
476 : : typedef std::pair<std::string, ConstElementPtr> maptype;
477 : :
478 [ + + ][ + - ]: 1274 : BOOST_FOREACH(maptype m, data->mapValue()) {
[ + - ][ + + ]
[ + + ]
479 : 349 : bool found = false;
480 : : // Ignore 'version' as a config element
481 [ + - ][ + + ]: 349 : if (m.first.compare("version") != 0) {
482 [ + - ][ + + ]: 4722 : BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
[ + - ][ + - ]
[ + + ][ + + ]
483 [ + - ][ + - ]: 2254 : if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
[ + - ][ + + ]
484 : 1127 : found = true;
485 : : }
486 : : }
487 [ + + ]: 214 : if (!found) {
488 : 3 : validated = false;
489 [ + + ]: 3 : if (errors) {
490 [ + - ][ + - ]: 567 : errors->add(Element::create("Unknown item " + m.first));
[ + - ]
491 : : }
492 : : }
493 : : }
494 : : }
495 : :
496 : 227 : return (validated);
497 : : }
498 : :
499 : : }
500 : 232 : }
|