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

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

34 35 36 37
static void
check_leaf_item(const ElementPtr& spec, const std::string& name, Element::types type, bool mandatory)
{
    if (spec->contains(name)) {
38
        if (spec->get(name)->getType() == type) {
39 40
            return;
        } else {
41
            throw ModuleSpecError(name + " not of type " + Element::typeToName(type));
42 43 44 45 46
        }
    } 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...
47
        throw ModuleSpecError(name + " missing in " + spec->str());
48 49 50 51 52 53 54 55 56 57 58
    }
}

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",
59
                    Element::nameToType(spec->get("item_type")->stringValue()),
60
                    !spec->get("item_optional")->boolValue()
61 62
                   );

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

static void
check_config_item_list(const ElementPtr& spec) {
77
    if (spec->getType() != Element::list) {
78
        throw ModuleSpecError("config_data is not a list of elements");
79
    }
80
    BOOST_FOREACH(ElementPtr item, spec->listValue()) {
81 82 83 84 85 86 87 88 89 90 91 92 93
        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) {
94
    if (spec->getType() != Element::list) {
95
        throw ModuleSpecError("commands is not a list of elements");
96
    }
97
    BOOST_FOREACH(ElementPtr item, spec->listValue()) {
98 99 100 101 102 103 104
        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
105
    check_leaf_item(spec, "module_description", Element::string, false);
106 107
    // config_data is not mandatory; module could just define
    // commands and have no config
108 109 110 111 112 113 114 115
    if (spec->contains("config_data")) {
        check_config_item_list(spec->get("config_data"));
    }
    if (spec->contains("commands")) {
        check_command_list(spec->get("commands"));
    }
}

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

128 129 130 131
//
// Public functions
//

132 133 134 135 136 137
ModuleSpec::ModuleSpec(ElementPtr module_spec_element,
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
138
    if (check) {
139 140 141 142 143
        check_module_specification(module_specification);
    }
}

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

const ElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
153
ModuleSpec::getConfigSpec() const {
154
    if (module_specification->contains("config_data")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
155
        return (module_specification->get("config_data"));
156
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
157
        return (ElementPtr());
158 159 160
    }
}

161
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
162 163
ModuleSpec::getModuleName() const {
    return (module_specification->get("module_name")->stringValue());
164 165
}

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

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

bool
JINMEI Tatuya's avatar
JINMEI Tatuya committed
182 183
ModuleSpec::validate_config(const ElementPtr data, const bool full,
                            ElementPtr errors)
184
{
185
    ElementPtr spec = module_specification->find("config_data");
JINMEI Tatuya's avatar
JINMEI Tatuya committed
186
    return (validate_spec_list(spec, data, full, errors));
187 188
}

189 190
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
191
                   throw(JSONError, ModuleSpecError)
192 193 194 195 196 197 198 199 200 201
{
    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());
    }

202
    ElementPtr module_spec_element = Element::fromJSON(file, file_name);
203
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
204
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
205 206 207
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
208 209 210 211
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
212 213
                   throw(JSONError, ModuleSpecError)
{
214
    ElementPtr module_spec_element = Element::fromJSON(in);
215
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
216
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
217 218 219
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
220 221
}

222 223 224 225 226

//
// private functions
//

227 228 229 230 231 232 233
//
// helper functions for validation
//
static bool
check_type(ElementPtr spec, ElementPtr element)
{
    std::string cur_item_type;
234
    cur_item_type = spec->get("item_type")->stringValue();
235
    if (cur_item_type == "any") {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
236
        return (true);
237
    }
238
    switch (element->getType()) {
239
        case Element::integer:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
240
            return (cur_item_type == "integer");
241 242
            break;
        case Element::real:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
243
            return (cur_item_type == "real");
244 245
            break;
        case Element::boolean:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
246
            return (cur_item_type == "boolean");
247 248
            break;
        case Element::string:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
249
            return (cur_item_type == "string");
250 251
            break;
        case Element::list:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
252
            return (cur_item_type == "list");
253 254
            break;
        case Element::map:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
255
            return (cur_item_type == "map");
256 257
            break;
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
258
    return (false);
259 260 261
}

bool
262
ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
263
    if (!check_type(spec, data)) {
264 265 266
        // 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;
267 268 269
        if (errors) {
            errors->add(Element::create("Type mismatch"));
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
270
        return (false);
271
    }
272
    if (data->getType() == Element::list) {
273
        ElementPtr list_spec = spec->get("list_item_spec");
274
        BOOST_FOREACH(ElementPtr list_el, data->listValue()) {
275
            if (!check_type(list_spec, list_el)) {
276 277 278
                if (errors) {
                    errors->add(Element::create("Type mismatch"));
                }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
279
                return (false);
280
            }
281
            if (list_spec->get("item_type")->stringValue() == "map") {
282
                if (!validate_item(list_spec, list_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
283
                    return (false);
284 285
                }
            }
286 287
        }
    }
288
    if (data->getType() == Element::map) {
289
        if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
290
            return (false);
291 292
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
293
    return (true);
294 295 296 297
}

// spec is a map with item_name etc, data is a map
bool
298
ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
299 300
    std::string item_name = spec->get("item_name")->stringValue();
    bool optional = spec->get("item_optional")->boolValue();
301 302
    ElementPtr data_el;
    data_el = data->get(item_name);
303
    
304
    if (data_el) {
305
        if (!validate_item(spec, data_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
306
            return (false);
307 308
        }
    } else {
309 310 311 312
        if (!optional && full) {
            if (errors) {
                errors->add(Element::create("Non-optional value missing"));
            }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
313
            return (false);
314 315
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
316
    return (true);
317 318 319 320
}

// spec is a list of maps, data is a map
bool
321
ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
322 323
    ElementPtr cur_data_el;
    std::string cur_item_name;
324
    BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) {
325
        if (!validate_spec(cur_spec_el, data, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
326
            return (false);
327 328
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
329
    return (true);
330 331
}

332 333
}
}