module_spec.cc 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2010  Internet Systems Consortium.
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
// DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
// INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
// FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
// WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15

16
#include <config/module_spec.h>
17 18

#include <sstream>
Jelte Jansen's avatar
Jelte Jansen committed
19
#include <iostream>
Jelte Jansen's avatar
Jelte Jansen committed
20 21
#include <fstream>
#include <cerrno>
22 23 24

#include <boost/foreach.hpp>

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 36 37
void
check_leaf_item(ConstElementPtr spec, const std::string& name,
                Element::types type, bool mandatory)
38 39
{
    if (spec->contains(name)) {
40
        if (spec->get(name)->getType() == type) {
41 42
            return;
        } else {
43
            throw ModuleSpecError(name + " not of type " + Element::typeToName(type));
44 45 46 47 48
        }
    } else if (mandatory) {
        // todo: want parent item name, and perhaps some info about location
        // in list? or just catch and throw new...
        // or make this part non-throwing and check return value...
49
        throw ModuleSpecError(name + " missing in " + spec->str());
50 51 52
    }
}

53
void check_config_item_list(ConstElementPtr spec);
54

55 56
void
check_config_item(ConstElementPtr spec) {
57 58 59 60
    check_leaf_item(spec, "item_name", Element::string, true);
    check_leaf_item(spec, "item_type", Element::string, true);
    check_leaf_item(spec, "item_optional", Element::boolean, true);
    check_leaf_item(spec, "item_default",
61
                    Element::nameToType(spec->get("item_type")->stringValue()),
62
                    !spec->get("item_optional")->boolValue()
63 64
                   );

65
    // if list, check the list specification
66
    if (Element::nameToType(spec->get("item_type")->stringValue()) == Element::list) {
67 68 69 70
        check_leaf_item(spec, "list_item_spec", Element::map, true);
        check_config_item(spec->get("list_item_spec"));
    }
    // todo: add stuff for type map
71
    if (Element::nameToType(spec->get("item_type")->stringValue()) == Element::map) {
72
        check_leaf_item(spec, "map_item_spec", Element::list, true);
Jelte Jansen's avatar
Jelte Jansen committed
73
        check_config_item_list(spec->get("map_item_spec"));
74 75 76
    }
}

77 78
void
check_config_item_list(ConstElementPtr spec) {
79
    if (spec->getType() != Element::list) {
80
        throw ModuleSpecError("config_data is not a list of elements");
81
    }
82
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
83 84 85 86
        check_config_item(item);
    }
}

87 88
void
check_command(ConstElementPtr spec) {
89 90 91 92 93
    check_leaf_item(spec, "command_name", Element::string, true);
    check_leaf_item(spec, "command_args", Element::list, true);
    check_config_item_list(spec->get("command_args"));
}

94 95
void
check_command_list(ConstElementPtr spec) {
96
    if (spec->getType() != Element::list) {
97
        throw ModuleSpecError("commands is not a list of elements");
98
    }
99
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
100 101 102 103
        check_command(item);
    }
}

104 105
void
check_data_specification(ConstElementPtr spec) {
106
    check_leaf_item(spec, "module_name", Element::string, true);
Jelte Jansen's avatar
Jelte Jansen committed
107
    check_leaf_item(spec, "module_description", Element::string, false);
108 109
    // config_data is not mandatory; module could just define
    // commands and have no config
110 111 112 113 114 115 116 117
    if (spec->contains("config_data")) {
        check_config_item_list(spec->get("config_data"));
    }
    if (spec->contains("commands")) {
        check_command_list(spec->get("commands"));
    }
}

118
// checks whether the given element is a valid module specification
119
// throws a ModuleSpecError if the specification is bad
120 121
void
check_module_specification(ConstElementPtr def) {
122 123 124 125 126
    try {
        check_data_specification(def);
    } catch (TypeError te) {
        throw ModuleSpecError(te.what());
    }
127
}
128
}
129

130 131
namespace isc {
namespace config {
132 133 134 135
//
// Public functions
//

136
ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
137 138 139 140 141
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
142
    if (check) {
143 144 145 146
        check_module_specification(module_specification);
    }
}

147
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
148
ModuleSpec::getCommandsSpec() const {
149
    if (module_specification->contains("commands")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
150
        return (module_specification->get("commands"));
151
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
152
        return (ElementPtr());
153 154 155
    }
}

156
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
157
ModuleSpec::getConfigSpec() const {
158
    if (module_specification->contains("config_data")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
159
        return (module_specification->get("config_data"));
160
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
161
        return (ElementPtr());
162 163 164
    }
}

165
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
166 167
ModuleSpec::getModuleName() const {
    return (module_specification->get("module_name")->stringValue());
168 169
}

Jelte Jansen's avatar
Jelte Jansen committed
170
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
171
ModuleSpec::getModuleDescription() const {
Jelte Jansen's avatar
Jelte Jansen committed
172
    if (module_specification->contains("module_description")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
173
        return (module_specification->get("module_description")->stringValue());
Jelte Jansen's avatar
Jelte Jansen committed
174
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
175
        return (std::string(""));
Jelte Jansen's avatar
Jelte Jansen committed
176 177 178
    }
}

179
bool
180 181
ModuleSpec::validate_config(ConstElementPtr data, const bool full) const {
    ConstElementPtr spec = module_specification->find("config_data");
JINMEI Tatuya's avatar
JINMEI Tatuya committed
182
    return (validate_spec_list(spec, data, full, ElementPtr()));
183 184
}

185 186 187
bool
ModuleSpec::validate_command(const std::string& command,
                             ConstElementPtr args,
188
                             ElementPtr errors) const
189
{
190 191 192
    ConstElementPtr commands_spec = module_specification->find("commands");

    if (args->getType() != Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
193 194
        errors->add(Element::create("args for command " +
                                    command + " is not a map"));
195 196 197 198 199 200 201 202 203 204 205
        return (false);
    }

    if (!commands_spec) {
        // there are no commands according to the spec.
        errors->add(Element::create("The given module has no commands"));
        return (false);
    }

    BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
        if (cur_command->get("command_name")->stringValue() == command) {
Jelte Jansen's avatar
Jelte Jansen committed
206 207
            return (validate_spec_list(cur_command->get("command_args"),
                                       args, true, errors));
208 209 210 211 212 213 214 215
        }
    }

    // this command is unknown
    errors->add(Element::create("Unknown command " + command));
    return (false);
}

216
bool
217 218
ModuleSpec::validate_config(ConstElementPtr data, const bool full,
                            ElementPtr errors) const
219
{
220
    ConstElementPtr spec = module_specification->find("config_data");
JINMEI Tatuya's avatar
JINMEI Tatuya committed
221
    return (validate_spec_list(spec, data, full, errors));
222 223
}

224 225
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
226
                   throw(JSONError, ModuleSpecError)
227 228 229 230 231 232 233 234 235 236
{
    std::ifstream file;

    file.open(file_name.c_str());
    if (!file) {
        std::stringstream errs;
        errs << "Error opening " << file_name << ": " << strerror(errno);
        throw ModuleSpecError(errs.str());
    }

237
    ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
238
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
239
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
240 241 242
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
243 244 245 246
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
247 248
                   throw(JSONError, ModuleSpecError)
{
249
    ConstElementPtr module_spec_element = Element::fromJSON(in);
250
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
251
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
252 253 254
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
255 256
}

257

258
namespace {
259 260 261 262
//
// private functions
//

263 264 265
//
// helper functions for validation
//
266 267
bool
check_type(ConstElementPtr spec, ConstElementPtr element) {
268
    std::string cur_item_type;
269
    cur_item_type = spec->get("item_type")->stringValue();
270
    if (cur_item_type == "any") {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
271
        return (true);
272
    }
273
    switch (element->getType()) {
274
        case Element::integer:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
275
            return (cur_item_type == "integer");
276 277
            break;
        case Element::real:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
278
            return (cur_item_type == "real");
279 280
            break;
        case Element::boolean:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
281
            return (cur_item_type == "boolean");
282 283
            break;
        case Element::string:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
284
            return (cur_item_type == "string");
285 286
            break;
        case Element::list:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
287
            return (cur_item_type == "list");
288 289
            break;
        case Element::map:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
290
            return (cur_item_type == "map");
291 292
            break;
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
293
    return (false);
294
}
295
}
296 297

bool
298 299 300
ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
                          const bool full, ElementPtr errors) const
{
301
    if (!check_type(spec, data)) {
302 303 304
        // we should do some proper error feedback here
        // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
        // std::cout << spec << std::endl;
305 306 307
        if (errors) {
            errors->add(Element::create("Type mismatch"));
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
308
        return (false);
309
    }
310
    if (data->getType() == Element::list) {
311 312
        ConstElementPtr list_spec = spec->get("list_item_spec");
        BOOST_FOREACH(ConstElementPtr list_el, data->listValue()) {
313
            if (!check_type(list_spec, list_el)) {
314 315 316
                if (errors) {
                    errors->add(Element::create("Type mismatch"));
                }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
317
                return (false);
318
            }
319
            if (list_spec->get("item_type")->stringValue() == "map") {
320
                if (!validate_item(list_spec, list_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
321
                    return (false);
322 323
                }
            }
324 325
        }
    }
326
    if (data->getType() == Element::map) {
327
        if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
328
            return (false);
329 330
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
331
    return (true);
332 333 334 335
}

// spec is a map with item_name etc, data is a map
bool
336 337 338
ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
                          const bool full, ElementPtr errors) const
{
339 340
    std::string item_name = spec->get("item_name")->stringValue();
    bool optional = spec->get("item_optional")->boolValue();
341
    ConstElementPtr data_el;
342
    data_el = data->get(item_name);
343
    
344
    if (data_el) {
345
        if (!validate_item(spec, data_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
346
            return (false);
347 348
        }
    } else {
349 350 351 352
        if (!optional && full) {
            if (errors) {
                errors->add(Element::create("Non-optional value missing"));
            }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
353
            return (false);
354 355
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
356
    return (true);
357 358 359 360
}

// spec is a list of maps, data is a map
bool
361 362 363
ModuleSpec::validate_spec_list(ConstElementPtr spec, ConstElementPtr data,
                               const bool full, ElementPtr errors) const
{
364
    bool validated = true;
365
    std::string cur_item_name;
366
    BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
367
        if (!validate_spec(cur_spec_el, data, full, errors)) {
368
            validated = false;
369 370
        }
    }
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389

    typedef std::pair<std::string, ConstElementPtr> maptype;
    
    BOOST_FOREACH(maptype m, data->mapValue()) {
        bool found = false;
        BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
            if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
                found = true;
            }
        }
        if (!found) {
            validated = false;
            if (errors) {
                errors->add(Element::create("Unknown item " + m.first));
            }
        }
    }

    return (validated);
390 391
}

392 393
}
}