Optional<SchemaError> validate(ValidationContext&, const dynamic& value) const override { if (value.isObject()) { for (const auto& prop : properties_) { auto* p = value.get_ptr(prop); if (!value.get_ptr(prop)) { return makeError("to have property", prop, value); } } } return none; }
Job::Job(const Config& config, const string& name, const dynamic& d) : id_(Job::JobNameTable->insert(name)), name_(name), enabled_(d.getDefault("enabled", true).asBool()), owner_(d.getDefault("owner", "").asString().toStdString()), levelForTasks_(config.levelForTasks), priority_(d.getDefault("priority", 1.0).asDouble()), resources_(config.defaultJobResources), config_(dynamic::object), filters_(config.levels.size()), levelForHostPlacement_(StringTable::NotFound), backoffSettings_(config.defaultBackoffSettings), killOrphanTasksAfter_(config.killOrphanTasksAfter) { try { if (owner_.empty()) { throw BistroException("Job ", name, " missing owner."); } if (const auto* level_for_tasks_ptr = d.get_ptr("level_for_tasks")) { if (!level_for_tasks_ptr->isString()) { throw BistroException("'level_for_tasks' must be a string for ", name); } const auto& str_level_for_tasks = level_for_tasks_ptr->asString().toStdString(); int level_for_tasks = config.levels.lookup(str_level_for_tasks); if (level_for_tasks == StringTable::NotFound) { throw BistroException("Bad level_for_tasks: ", str_level_for_tasks); } levelForTasks_ = level_for_tasks; } auto it = d.find("resources"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'resources' must be an object for ", name); } for (const auto& pair : it->second.items()) { const auto& name = pair.first.asString().toStdString(); const int resource_id = config.resourceNames.lookup(name); if (resource_id == StringTable::NotFound) { throw BistroException("Invalid resource: ", name); } resources_[resource_id] = pair.second.asInt(); } } it = d.find("config"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'config' must be an object for ", name); } update(config_, it->second); } it = d.find("backoff"); if (it != d.items().end()) { if (!it->second.isArray()) { throw BistroException("'backoff' must be an array for ", name); } backoffSettings_ = JobBackoffSettings(it->second); } it = d.find("filters"); if (it != d.items().end()) { if (!it->second.isObject()) { throw BistroException("'filters' must be an object for ", name); } for (const auto& pair : it->second.items()) { const auto& level = pair.first.asString().toStdString(); const int level_id = config.levels.lookup(level); if (level_id == StringTable::NotFound) { throw BistroException("Invalid level in filters: ", level); } filters_[level_id] = JobFilters(pair.second); } } detail::parseKillOrphanTasksAfter(d, &killOrphanTasksAfter_); if (auto* ptr = d.get_ptr("version_id")) { if (!ptr->isInt()) { throw std::runtime_error("'version_id' must be an integer"); } versionID_ = ptr->getInt(); } if (const auto* host_level_ptr = d.get_ptr("level_for_host_placement")) { if (!host_level_ptr->isString()) { throw BistroException( "'level_for_host_placement' must be a string for ", name ); } const auto& str_host_level = host_level_ptr->asString().toStdString(); int host_level = config.levels.lookup(str_host_level); if (host_level == StringTable::NotFound) { throw BistroException( "Bad level_for_host_placement: ", str_host_level ); } levelForHostPlacement_ = host_level; } if (const auto* host_ptr = d.get_ptr("host_placement")) { if (!host_ptr->isString()) { throw BistroException("'host_placement' must be a string for ", name); } hostPlacement_ = host_ptr->asString().toStdString(); if (!hostPlacement_.empty() && levelForHostPlacement_ != StringTable::NotFound) { throw BistroException( "It makes no sense to specify both 'level_for_host_placement' and " "'host_placement'" ); } } it = d.find("create_time"); // Silently ignore non-integers because this isn't critical configuration if (it != d.items().end() && it->second.isInt()) { createTime_ = it->second.asInt(); } it = d.find("modify_time"); // Silently ignore non-integers because this isn't critical configuration if (it != d.items().end() && it->second.isInt()) { modifyTime_ = it->second.asInt(); } // We don't check that the job names in depends_on are valid jobs. // If an invalid job is specified here, or if there is circular dependency, // this job can never run. it = d.find("depends_on"); if (it != d.items().end()) { if (!it->second.isArray()) { throw BistroException("'depends_on' must be an array for ", name); } for (const auto& job_name : it->second) { dependsOn_.push_back(static_cast<ID>(JobNameTable->insert( job_name.asString().toStdString() ))); } } } catch (const exception& e) { LOG(ERROR) << "Error creating job: " << e.what(); error_ = e.what(); enabled_ = false; } }
void SchemaValidator::loadSchema(SchemaValidatorContext& context, const dynamic& schema) { if (!schema.isObject() || schema.empty()) { return; } // Check for $ref, if we have one we won't apply anything else. Refs are // pointers to other parts of the json, e.g. #/foo/bar points to the schema // located at root["foo"]["bar"]. if (const auto* p = schema.get_ptr("$ref")) { // We only support absolute refs, i.e. those starting with '#' if (p->isString() && p->stringPiece()[0] == '#') { auto it = context.refs.find(p->getString()); if (it != context.refs.end()) { validators_.emplace_back(make_unique<RefValidator>(it->second)); return; } // This is a ref, but we haven't loaded it yet. Find where it is based on // the root schema. std::vector<std::string> parts; split("/", p->stringPiece(), parts); const auto* s = &context.schema; // First part is '#' for (size_t i = 1; s && i < parts.size(); ++i) { // Per the standard, we must replace ~1 with / and then ~0 with ~ boost::replace_all(parts[i], "~1", "/"); boost::replace_all(parts[i], "~0", "~"); if (s->isObject()) { s = s->get_ptr(parts[i]); continue; } if (s->isArray()) { try { const size_t pos = to<size_t>(parts[i]); if (pos < s->size()) { s = s->get_ptr(pos); continue; } } catch (const std::range_error& e) { // ignore } } break; } // If you have a self-recursive reference, this avoids getting into an // infinite recursion, where we try to load a schema that just references // itself, and then we try to load it again, and so on. // Instead we load a pointer to the schema into the refs, so that any // future references to it will just see that pointer and won't try to // keep parsing further. if (s) { auto v = make_unique<SchemaValidator>(); context.refs[p->getString()] = v.get(); v->loadSchema(context, *s); validators_.emplace_back(std::move(v)); return; } } } // Numeric validators if (const auto* p = schema.get_ptr("multipleOf")) { validators_.emplace_back(make_unique<MultipleOfValidator>(*p)); } if (const auto* p = schema.get_ptr("maximum")) { validators_.emplace_back( make_unique<ComparisonValidator>(*p, schema.get_ptr("exclusiveMaximum"), ComparisonValidator::Type::MAX)); } if (const auto* p = schema.get_ptr("minimum")) { validators_.emplace_back( make_unique<ComparisonValidator>(*p, schema.get_ptr("exclusiveMinimum"), ComparisonValidator::Type::MIN)); } // String validators if (const auto* p = schema.get_ptr("maxLength")) { validators_.emplace_back( make_unique<SizeValidator<std::greater_equal<int64_t>>>( *p, dynamic::Type::STRING)); } if (const auto* p = schema.get_ptr("minLength")) { validators_.emplace_back( make_unique<SizeValidator<std::less_equal<int64_t>>>( *p, dynamic::Type::STRING)); } if (const auto* p = schema.get_ptr("pattern")) { validators_.emplace_back(make_unique<StringPatternValidator>(*p)); } // Array validators const auto* items = schema.get_ptr("items"); const auto* additionalItems = schema.get_ptr("additionalItems"); if (items || additionalItems) { validators_.emplace_back( make_unique<ArrayItemsValidator>(context, items, additionalItems)); } if (const auto* p = schema.get_ptr("maxItems")) { validators_.emplace_back( make_unique<SizeValidator<std::greater_equal<int64_t>>>( *p, dynamic::Type::ARRAY)); } if (const auto* p = schema.get_ptr("minItems")) { validators_.emplace_back( make_unique<SizeValidator<std::less_equal<int64_t>>>( *p, dynamic::Type::ARRAY)); } if (const auto* p = schema.get_ptr("uniqueItems")) { validators_.emplace_back(make_unique<ArrayUniqueValidator>(*p)); } // Object validators const auto* properties = schema.get_ptr("properties"); const auto* patternProperties = schema.get_ptr("patternProperties"); const auto* additionalProperties = schema.get_ptr("additionalProperties"); if (properties || patternProperties || additionalProperties) { validators_.emplace_back(make_unique<PropertiesValidator>( context, properties, patternProperties, additionalProperties)); } if (const auto* p = schema.get_ptr("maxProperties")) { validators_.emplace_back( make_unique<SizeValidator<std::greater_equal<int64_t>>>( *p, dynamic::Type::OBJECT)); } if (const auto* p = schema.get_ptr("minProperties")) { validators_.emplace_back( make_unique<SizeValidator<std::less_equal<int64_t>>>( *p, dynamic::Type::OBJECT)); } if (const auto* p = schema.get_ptr("required")) { validators_.emplace_back(make_unique<RequiredValidator>(*p)); } // Misc validators if (const auto* p = schema.get_ptr("dependencies")) { validators_.emplace_back(make_unique<DependencyValidator>(context, *p)); } if (const auto* p = schema.get_ptr("enum")) { validators_.emplace_back(make_unique<EnumValidator>(*p)); } if (const auto* p = schema.get_ptr("type")) { validators_.emplace_back(make_unique<TypeValidator>(*p)); } if (const auto* p = schema.get_ptr("allOf")) { validators_.emplace_back(make_unique<AllOfValidator>(context, *p)); } if (const auto* p = schema.get_ptr("anyOf")) { validators_.emplace_back(make_unique<AnyOfValidator>( context, *p, AnyOfValidator::Type::ONE_OR_MORE)); } if (const auto* p = schema.get_ptr("oneOf")) { validators_.emplace_back(make_unique<AnyOfValidator>( context, *p, AnyOfValidator::Type::EXACTLY_ONE)); } if (const auto* p = schema.get_ptr("not")) { validators_.emplace_back(make_unique<NotValidator>(context, *p)); } }