module_spec.cc 11.1 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 "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
namespace isc {
28
namespace config {
29

30 31 32 33
//
// static functions
//

34 35
// todo: is there a direct way to get a std::string from an enum label?
static std::string
36
getType_string(Element::types type)
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
{
    switch(type) {
    case Element::integer:
        return std::string("integer");
    case Element::real:
        return std::string("real");
    case Element::boolean:
        return std::string("boolean");
    case Element::string:
        return std::string("string");
    case Element::list:
        return std::string("list");
    case Element::map:
        return std::string("map");
    default:
        return std::string("unknown");
    }
}

static Element::types
57
getType_value(const std::string& type_name) {
58 59 60 61 62 63 64 65 66 67 68 69
    if (type_name == "integer") {
        return Element::integer;
    } else if (type_name == "real") {
        return Element::real;
    } else if (type_name == "boolean") {
        return Element::boolean;
    } else if (type_name == "string") {
        return Element::string;
    } else if (type_name == "list") {
        return Element::list;
    } else if (type_name == "map") {
        return Element::map;
70 71
    } else if (type_name == "any") {
        return Element::any;
72
    } else {
73
        throw ModuleSpecError(type_name + " is not a valid type name");
74 75 76 77 78 79 80
    }
}

static void
check_leaf_item(const ElementPtr& spec, const std::string& name, Element::types type, bool mandatory)
{
    if (spec->contains(name)) {
81
        if (spec->get(name)->getType() == type) {
82 83
            return;
        } else {
84
            throw ModuleSpecError(name + " not of type " + getType_string(type));
85 86 87 88 89
        }
    } 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...
90
        throw ModuleSpecError(name + " missing in " + spec->str());
91 92 93 94 95 96 97 98 99 100 101
    }
}

static void check_config_item_list(const ElementPtr& spec);

static void
check_config_item(const ElementPtr& spec) {
    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",
102 103
                    getType_value(spec->get("item_type")->stringValue()),
                    !spec->get("item_optional")->boolValue()
104 105
                   );

106
    // if list, check the list specification
107
    if (getType_value(spec->get("item_type")->stringValue()) == Element::list) {
108 109 110 111
        check_leaf_item(spec, "list_item_spec", Element::map, true);
        check_config_item(spec->get("list_item_spec"));
    }
    // todo: add stuff for type map
112
    if (getType_value(spec->get("item_type")->stringValue()) == Element::map) {
113
        check_leaf_item(spec, "map_item_spec", Element::list, true);
Jelte Jansen's avatar
Jelte Jansen committed
114
        check_config_item_list(spec->get("map_item_spec"));
115 116 117 118 119
    }
}

static void
check_config_item_list(const ElementPtr& spec) {
120
    if (spec->getType() != Element::list) {
121
        throw ModuleSpecError("config_data is not a list of elements");
122
    }
123
    BOOST_FOREACH(ElementPtr item, spec->listValue()) {
124 125 126 127 128 129 130 131 132 133 134 135 136
        check_config_item(item);
    }
}

static void
check_command(const ElementPtr& spec) {
    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"));
}

static void
check_command_list(const ElementPtr& spec) {
137
    if (spec->getType() != Element::list) {
138
        throw ModuleSpecError("commands is not a list of elements");
139
    }
140
    BOOST_FOREACH(ElementPtr item, spec->listValue()) {
141 142 143 144 145 146 147
        check_command(item);
    }
}

static void
check_data_specification(const ElementPtr& spec) {
    check_leaf_item(spec, "module_name", Element::string, true);
Jelte Jansen's avatar
Jelte Jansen committed
148
    check_leaf_item(spec, "module_description", Element::string, false);
149 150
    // config_data is not mandatory; module could just define
    // commands and have no config
151 152 153 154 155 156 157 158
    if (spec->contains("config_data")) {
        check_config_item_list(spec->get("config_data"));
    }
    if (spec->contains("commands")) {
        check_command_list(spec->get("commands"));
    }
}

159
// checks whether the given element is a valid module specification
160
// throws a ModuleSpecError if the specification is bad
161
static void
162
check_module_specification(const ElementPtr& def)
163
{
164
    check_data_specification(def);
165 166
}

167 168 169 170
//
// Public functions
//

171 172 173 174 175 176
ModuleSpec::ModuleSpec(ElementPtr module_spec_element,
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
177
    if (check) {
178 179 180 181 182
        check_module_specification(module_specification);
    }
}

const ElementPtr
183
ModuleSpec::getCommandsSpec() const
184 185 186 187 188 189 190 191 192
{
    if (module_specification->contains("commands")) {
        return module_specification->get("commands");
    } else {
        return ElementPtr();
    }
}

const ElementPtr
193
ModuleSpec::getConfigSpec() const
194 195 196 197 198
{
    if (module_specification->contains("config_data")) {
        return module_specification->get("config_data");
    } else {
        return ElementPtr();
199 200 201
    }
}

202
const std::string
203
ModuleSpec::getModuleName() const
204 205 206 207
{
    return module_specification->get("module_name")->stringValue();
}

Jelte Jansen's avatar
Jelte Jansen committed
208 209 210 211 212 213 214 215 216 217
const std::string
ModuleSpec::getModuleDescription() const
{
    if (module_specification->contains("module_description")) {
        return module_specification->get("module_description")->stringValue();
    } else {
        return std::string("");
    }
}

218
bool
219
ModuleSpec::validate_config(const ElementPtr data, const bool full)
220
{
221
    ElementPtr spec = module_specification->find("config_data");
222 223 224 225 226 227
    return validate_spec_list(spec, data, full, ElementPtr());
}

bool
ModuleSpec::validate_config(const ElementPtr data, const bool full, ElementPtr errors)
{
228
    ElementPtr spec = module_specification->find("config_data");
229
    return validate_spec_list(spec, data, full, errors);
230 231
}

232 233
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
234
                   throw(JSONError, ModuleSpecError)
235 236 237 238 239 240 241 242 243 244
{
    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());
    }

245
    ElementPtr module_spec_element = Element::fromJSON(file, file_name);
246 247 248 249 250
    if (module_spec_element->contains("module_spec")) {
        return ModuleSpec(module_spec_element->get("module_spec"), check);
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
251 252 253 254
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
255
                   throw(JSONError, ModuleSpecError) {
256
    ElementPtr module_spec_element = Element::fromJSON(in);
257 258 259 260 261
    if (module_spec_element->contains("module_spec")) {
        return ModuleSpec(module_spec_element->get("module_spec"), check);
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
262 263
}

264 265 266 267 268

//
// private functions
//

269 270 271 272 273 274 275
//
// helper functions for validation
//
static bool
check_type(ElementPtr spec, ElementPtr element)
{
    std::string cur_item_type;
276
    cur_item_type = spec->get("item_type")->stringValue();
277 278 279
    if (cur_item_type == "any") {
        return true;
    }
280
    switch (element->getType()) {
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
        case Element::integer:
            return cur_item_type == "integer";
            break;
        case Element::real:
            return cur_item_type == "real";
            break;
        case Element::boolean:
            return cur_item_type == "boolean";
            break;
        case Element::string:
            return cur_item_type == "string";
            break;
        case Element::list:
            return cur_item_type == "list";
            break;
        case Element::map:
            return cur_item_type == "map";
            break;
    }
    return false;
}

bool
304
ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
305
    if (!check_type(spec, data)) {
306 307 308
        // 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;
309 310 311
        if (errors) {
            errors->add(Element::create("Type mismatch"));
        }
312 313
        return false;
    }
314
    if (data->getType() == Element::list) {
315
        ElementPtr list_spec = spec->get("list_item_spec");
316
        BOOST_FOREACH(ElementPtr list_el, data->listValue()) {
317
            if (!check_type(list_spec, list_el)) {
318 319 320
                if (errors) {
                    errors->add(Element::create("Type mismatch"));
                }
321 322
                return false;
            }
323
            if (list_spec->get("item_type")->stringValue() == "map") {
324
                if (!validate_item(list_spec, list_el, full, errors)) {
325 326 327
                    return false;
                }
            }
328 329
        }
    }
330
    if (data->getType() == Element::map) {
331
        if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
332 333 334 335 336 337 338 339
            return false;
        }
    }
    return true;
}

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

// spec is a list of maps, data is a map
bool
363
ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
364 365
    ElementPtr cur_data_el;
    std::string cur_item_name;
366
    BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) {
367
        if (!validate_spec(cur_spec_el, data, full, errors)) {
368 369 370 371 372 373
            return false;
        }
    }
    return true;
}

374 375
}
}