diff --git a/ChangeLog b/ChangeLog index 5e48f9f..9ec41cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2019-07-03 Kirit Saelensminde + Add an initial pass when a schema is first loaded to look for embedded `$id`s so they can be used from anywhere else in the schema. + 2019-01-22 Kirit Saelensminde Add debug logging for the HTTP schema loader. diff --git a/include/f5/json/schema.cache.hpp b/include/f5/json/schema.cache.hpp index 031f349..d2b8003 100644 --- a/include/f5/json/schema.cache.hpp +++ b/include/f5/json/schema.cache.hpp @@ -1,5 +1,6 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -24,7 +25,7 @@ namespace f5 { friend validation::annotations; std::shared_ptr base; - std::map cache; + std::map cache; public: /// Create an empty cache which uses the root cache @@ -34,17 +35,24 @@ namespace f5 { schema_cache(std::shared_ptr); /// Perform a lookup in this case and its bases - const schema &operator[](f5::u8view) const; + std::pair + operator[](fostlib::url) const; + [[deprecated("Only call with a fostlib::url")]] schema const & + operator[](f5::u8view) const; /// The root cache. The root cache is the only cache which /// should have an empty base. static std::shared_ptr root_cache(); /// Add a schema at a given position in the cache - const schema &insert(fostlib::string, schema); + const schema &insert(fostlib::url, schema); /// Add a schema at an unnamed position, i.e. only if it /// contains a `$id` describing its proper location const schema &insert(schema); + + private: + /// Looks for an exact match + schema const *recursive_lookup(fostlib::url const &) const; }; diff --git a/include/f5/json/schema.hpp b/include/f5/json/schema.hpp index 879edab..5d871ee 100644 --- a/include/f5/json/schema.hpp +++ b/include/f5/json/schema.hpp @@ -18,6 +18,9 @@ namespace f5 { namespace json { + class schema_cache; + + /** ## JSON Schema @@ -31,6 +34,9 @@ namespace f5 { public: schema(const fostlib::url &, value v); + /// We need to pre-load this with $id schemas that we find + std::shared_ptr schemas; + const fostlib::url &self() const { return id; } value assertions() const { return validation; } diff --git a/include/f5/json/validator.hpp b/include/f5/json/validator.hpp index 944951c..6e2fa8a 100644 --- a/include/f5/json/validator.hpp +++ b/include/f5/json/validator.hpp @@ -49,8 +49,6 @@ namespace f5 { value data; pointer dpos; - std::shared_ptr schemas; - private: friend class json::schema; /// Construct the initial location @@ -78,6 +76,9 @@ namespace f5 { /// on the local $id found in parent lexical scopes of the /// JSON fostlib::url spos_url() const; + + /// Return the schema cache for the current schema + schema_cache const &schemas() const; }; diff --git a/json-schema-validator/valid.cpp b/json-schema-validator/valid.cpp index df0dd20..4720f5d 100644 --- a/json-schema-validator/valid.cpp +++ b/json-schema-validator/valid.cpp @@ -1,5 +1,6 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -9,6 +10,7 @@ #include #include +#include #include @@ -41,8 +43,7 @@ namespace { std::cout << "Assertion: " << e.assertion << "\nSchema position: " << e.spos << "\nData position: " << e.dpos - << "\nSchema: " << s.assertions()[e.spos] - << "\nData: " << d[e.dpos] << std::endl; + << "\nData at that position: " << d[e.dpos] << std::endl; } } @@ -53,23 +54,42 @@ FSL_MAIN("json-schema-validator", "JSON Schema Validator") args.commandSwitch("v", c_verbose); args.commandSwitch("-schema", c_schema); - const f5::json::schema s{fostlib::url{}, load_json(c_schema.value())}; + fostlib::timer time; + + if (c_verbose.value()) { + std::cout << 0.0 << " Loading schema JSON " << c_schema.value() + << std::endl; + } + f5::json::value parsed = load_json(c_schema.value()); + if (c_verbose.value()) { + std::cout << time.seconds() << " Establishing as schema" << std::endl; + } + f5::json::schema const s{fostlib::url{}, parsed}; for (const auto &arg : args) { if (c_verbose.value()) { - std::cout << "Loading and validating " << arg << std::endl; + std::cout << time.seconds() << "Loading " << arg << std::endl; } const auto j = load_json(arg); + if (c_verbose.value()) { + std::cout << time.seconds() << " Validating " << std::endl; + } + auto v = s.validate(j); + if (c_verbose.value()) std::cout << time.seconds() << " Results in\n"; if (c_check_invalid.value()) { - if (const auto v = s.validate(j); v) { + if (v) { std::cout << arg << " validated when it should not have" << std::endl; return 2; } } else { - if (auto v = s.validate(j); not v) { + if (not v) { + if (c_verbose.value()) + std::cout << time.seconds() << " Results in\n"; std::cout << arg << " did not validate" << std::endl; - print(s, j, (f5::json::validation::result::error)std::move(v)); + print(s, j, + static_cast( + std::move(v))); return 1; } } diff --git a/src/annotations.cpp b/src/annotations.cpp index 56eee3b..2245869 100644 --- a/src/annotations.cpp +++ b/src/annotations.cpp @@ -1,5 +1,5 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. Distributed under the Boost Software License, Version 1.0. See @@ -22,9 +22,9 @@ namespace { std::shared_ptr schemas) { if (anp->sroot[anp->spos].has_key("$id")) { if (not schemas) { - schemas = - std::make_shared(anp->schemas); - anp->schemas = schemas; +// schemas = +// std::make_shared(anp->schemas); +// anp->schemas = schemas; } anp->base = &schemas->insert( f5::json::schema{anp->base->self(), anp->sroot[anp->spos]}); @@ -38,11 +38,10 @@ namespace { for (const auto &def : anp->sroot[anp->spos][sub]["definitions"].object()) { fostlib::url r{anp->base->self(), anp->spos / sub}; - const auto &subschema = anp->schemas->insert( - fostlib::string(r.as_string()), - f5::json::schema{base, def.second}); - definitions( - anp, subschema.self(), sub / "definitions" / def.first); +// const auto &subschema = anp->schemas->insert( +// r, f5::json::schema{base, def.second}); +// definitions( +// anp, subschema.self(), sub / "definitions" / def.first); } } } @@ -55,10 +54,7 @@ f5::json::validation::annotations::annotations( sroot(s.assertions()), spos(std::move(sp)), data(std::move(d)), - dpos(std::move(dp)), - schemas{std::make_shared()} { - id_handling(this, schemas); - definitions(this, base->self(), pointer{}); + dpos(std::move(dp)) { } @@ -68,10 +64,7 @@ f5::json::validation::annotations::annotations( sroot(s.assertions()), spos(std::move(sp)), data(std::move(d)), - dpos(std::move(dp)), - schemas{std::make_shared(an.schemas)} { - id_handling(this, schemas); - definitions(this, base->self(), pointer{}); + dpos(std::move(dp)) { } @@ -81,9 +74,7 @@ f5::json::validation::annotations::annotations( sroot(an.sroot), spos(std::move(sp)), data(an.data), - dpos(std::move(dp)), - schemas(an.schemas) { - id_handling(this, nullptr); + dpos(std::move(dp)) { } @@ -92,9 +83,7 @@ f5::json::validation::annotations::annotations(annotations &&b, result &&w) sroot{std::move(b.sroot)}, spos{std::move(b.spos)}, data{std::move(b.data)}, - dpos{std::move(b.dpos)}, - schemas{b.schemas} { - id_handling(this, nullptr); + dpos{std::move(b.dpos)} { merge(std::move(w)); } @@ -127,3 +116,8 @@ fostlib::url f5::json::validation::annotations::spos_url() const { } return u; } + + +auto f5::json::validation::annotations::schemas() const -> schema_cache const & { + return *base->schemas; +} diff --git a/src/assertions.numeric.cpp b/src/assertions.numeric.cpp index 9e200fa..965b4d0 100644 --- a/src/assertions.numeric.cpp +++ b/src/assertions.numeric.cpp @@ -29,13 +29,13 @@ namespace { return p(bound.value(), v) ? f5::json::validation::result{std::move(an)} : f5::json::validation::result{ - rule, an.spos, an.dpos}; + rule, an.spos, an.dpos}; }, [&](double v) mutable { return p(bound.value(), v) ? f5::json::validation::result{std::move(an)} : f5::json::validation::result{ - rule, an.spos, an.dpos}; + rule, an.spos, an.dpos}; }, [&](const auto &) mutable { return f5::json::validation::result{std::move(an)}; @@ -46,7 +46,7 @@ namespace { return p(bound.value(), v) ? f5::json::validation::result{std::move(an)} : f5::json::validation::result{ - rule, an.spos, an.dpos}; + rule, an.spos, an.dpos}; }, [&](double v) mutable { bool passed; @@ -58,7 +58,7 @@ namespace { return passed ? f5::json::validation::result{std::move(an)} : f5::json::validation::result{ - rule, an.spos, an.dpos}; + rule, an.spos, an.dpos}; }, [&](const auto &) mutable { return f5::json::validation::result{std::move(an)}; diff --git a/src/assertions.string.cpp b/src/assertions.string.cpp index 02f3965..bf0cd2e 100644 --- a/src/assertions.string.cpp +++ b/src/assertions.string.cpp @@ -48,8 +48,8 @@ const f5::json::assertion::checker f5::json::assertion::pattern_checker = auto string = fostlib::coerce>( an.data[an.dpos]); if (not string) return validation::result{std::move(an)}; - std::regex re{static_cast( - fostlib::coerce(part))}; + auto const rgx = fostlib::coerce(part); + std::regex re{rgx.data(), rgx.memory().size()}; if (std::regex_search( string->data(), string->data() + string->bytes(), re)) { return validation::result{std::move(an)}; diff --git a/src/schema.cache.cpp b/src/schema.cache.cpp index 14bcec7..a9675d9 100644 --- a/src/schema.cache.cpp +++ b/src/schema.cache.cpp @@ -1,5 +1,6 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -45,9 +46,33 @@ namespace { return m; } auto &g_loader_cache() { - static std::map> c; + static std::map> c; return c; } + auto &g_pre_load() { + static auto cache = []() { + auto cache = std::make_shared(nullptr); + if (const auto p = fostlib::coerce>( + f5::json::c_schema_path.value()); + p) { + const auto fn = + fostlib::coerce(fostlib::string(*p)); + fostlib::json s{ + f5::json::value::parse(fostlib::utf::load_file(fn))}; + cache->insert(f5::json::schema{fostlib::url{fostlib::url{}, fn}, + std::move(s)}); + } else if (f5::json::c_schema_path.value().isarray()) { + for (const auto filepath : f5::json::c_schema_path.value()) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, + "This type of schema load path not yet supported", + f5::json::c_schema_path.value()); + } + } + return cache; + }(); + return cache; + } } @@ -64,68 +89,93 @@ f5::json::schema_cache::schema_cache(std::shared_ptr b) auto f5::json::schema_cache::root_cache() -> std::shared_ptr { - static std::shared_ptr cache = []() { - auto cache = std::make_shared(nullptr); - if (const auto p = fostlib::coerce>( - c_schema_path.value()); - p) { - const auto fn = fostlib::coerce( - fostlib::string(*p)); - fostlib::json s{ - f5::json::value::parse(fostlib::utf::load_file(fn))}; - cache->insert( - schema{fostlib::url{fostlib::url{}, fn}, std::move(s)}); - } else if (c_schema_path.value().isarray()) { - for (const auto filepath : c_schema_path.value()) { - throw fostlib::exceptions::not_implemented( - "f5::json::schema_cache::root_cache", - "This type of schema load path not yet supported", - c_schema_path.value()); + /** + * This "special value" for the root cache is pretty bad, but we can't put + * a cache in a schema and load schemas during the root cache creation + * because we end up in an infinite loop. + * + * The returned pointer cannot be `nullptr` or we will go into an infinite + * loop chasing down the pre-load list before we bottom out and try to + * load in more schemas using the schema loading mechanism. + */ + static auto cache{ + std::make_shared(std::shared_ptr{})}; + return cache; +} + + +auto f5::json::schema_cache::recursive_lookup(fostlib::url const &u) const + -> schema const * { + const auto pos = cache.find(u); + if (pos == cache.end()) { + if (base == root_cache()) { + return (*g_pre_load()).recursive_lookup(u); + } else if (base) { + return (*base).recursive_lookup(u); + } else { + std::unique_lock lock{g_loader_cache_mutex()}; + if (auto pos = g_loader_cache().find(u); + pos != g_loader_cache().end()) { + return pos->second.get(); + } else { + auto l = load_schema(u.as_string()); + if (l) { + return (g_loader_cache()[u] = std::move(l)).get(); + } else { + return nullptr; + } } } - return cache; - }(); - return cache; + } else { + return &pos->second; + } } auto f5::json::schema_cache::operator[](f5::u8view u) const -> const schema & { + return (*this)[fostlib::url{u}].first; +} + + +auto f5::json::schema_cache::operator[](fostlib::url u) const + -> std::pair { try { - const auto pos = cache.find(u); - if (pos == cache.end()) { - if (base) { - return (*base)[u]; - } else { - std::unique_lock lock{g_loader_cache_mutex()}; - if (auto pos = g_loader_cache().find(u); - pos != g_loader_cache().end()) { - return *pos->second; + if (not u.fragment()) u.fragment(fostlib::string{}); + auto const *s = recursive_lookup(u); + if (s != nullptr) { + return {*s, fostlib::jcursor{}}; + } else { + if (f5::u8view{u.fragment().value()}.starts_with("/")) { + auto sptr = u.fragment().value(); + u.fragment(fostlib::string{}); + s = recursive_lookup(u); + if (s == nullptr) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Schema not found", u); } else { - auto l = load_schema(u); - if (l) { - return *( - g_loader_cache()[fostlib::string{u}] = - std::move(l)); - } else { - throw fostlib::exceptions::not_implemented( - __PRETTY_FUNCTION__, "Schema not found", u); - } + sptr = fostlib::ascii_printable_string{"#" + sptr}; + return {*s, + fostlib::jcursor::parse_json_pointer_fragment(sptr)}; } + } else { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Schema not found", u); } - } else { - return pos->second; } } catch (fostlib::exceptions::exception &e) { if (not e.data().has_key("schema-cache")) { std::unique_lock lock{g_loader_cache_mutex()}; for (const auto &p : g_loader_cache()) { - fostlib::push_back(e.data(), "schema-cache", "", p.first); + fostlib::push_back( + e.data(), "schema-cache", "", p.first.as_string()); } } const fostlib::string cp{std::to_string((int64_t)this)}; fostlib::insert(e.data(), "schema-cache", cp, value::array_t{}); for (const auto &c : cache) { - fostlib::push_back(e.data(), "schema-cache", cp, c.first); + fostlib::push_back( + e.data(), "schema-cache", cp, + fostlib::coerce(c.first)); } throw; } @@ -133,19 +183,14 @@ auto f5::json::schema_cache::operator[](f5::u8view u) const -> const schema & { auto f5::json::schema_cache::insert(schema s) -> const schema & { - if (s.assertions().has_key("$id")) { - auto parts = fostlib::partition( - fostlib::coerce(s.assertions()["$id"]), "#"); - cache.insert(std::make_pair(parts.first, s)); - } - auto pos = cache.insert( - std::make_pair(fostlib::coerce(s.self()), s)); + auto pos = cache.insert(std::make_pair(s.self(), s)); return pos.first->second; } -auto f5::json::schema_cache::insert(fostlib::string n, schema s) +auto f5::json::schema_cache::insert(fostlib::url n, schema s) -> const schema & { - cache.insert(std::make_pair(n, s)); + if (not n.fragment()) n.fragment(fostlib::string{}); + cache.insert(std::make_pair(std::move(n), s)); return insert(s); } diff --git a/src/schema.cpp b/src/schema.cpp index 0868af7..6c4b2a6 100644 --- a/src/schema.cpp +++ b/src/schema.cpp @@ -1,24 +1,64 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See */ #include +#include #include +#include + + +namespace { + /** + * We walk down through all of the keys (excepting some particular values) + * and we register all of the `$id`s that we find. + * + * The naive approach to this is going to be a bit slow, but good enough to + * start with because it will scan each schema we do find twice (once in + * that schema and once in its parent). Multiply nesting schemas increases + * this scan count. + */ + void preload_ids( + fostlib::url const &base, + f5::json::value schema, + f5::json::schema_cache &cache) { + if (schema.isobject()) { + for (auto const [key, value] : schema.object()) { + if (value.has_key("$id") && value["$id"].isatom()) { + fostlib::url const subschema{base, + fostlib::coerce(value["$id"])}; + std::cout << "Found sub-schema " << subschema << " in " + << base << '\n'; + auto &s = cache.insert(f5::json::schema{ + subschema, value}); + } + preload_ids(base, value, cache); + } + } + } +} + + f5::json::schema::schema(const fostlib::url &b, value v) : id{b, [v]() { if (v.has_key("$id")) { return fostlib::coerce(v["$id"]); } else { - return fostlib::guid(); + return fostlib::string{}; } }()}, - validation{v} {} + validation{v}, + schemas{std::make_shared()} { + if (not id.fragment()) id.fragment(fostlib::string{}); + preload_ids(id, validation, *schemas); +} auto f5::json::schema::validate(value j) const -> validation::result { diff --git a/src/schema.loaders.cpp b/src/schema.loaders.cpp index 4a12221..3864227 100644 --- a/src/schema.loaders.cpp +++ b/src/schema.loaders.cpp @@ -1,5 +1,6 @@ /** - Copyright 2018-2019, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -23,7 +24,8 @@ const fostlib::setting f5::json::c_schema_loaders( namespace { const fostlib::module c_fost_json_schema{fostlib::c_fost, "json-schema"}; - const fostlib::module c_fost_json_schema_loader{c_fost_json_schema, "loader"}; + const fostlib::module c_fost_json_schema_loader{c_fost_json_schema, + "loader"}; } @@ -49,6 +51,7 @@ f5::json::schema_loader::schema_loader(lstring n, schema_loader_fn f) std::unique_ptr f5::json::load_schema(u8view url) { + url = f5::u8view{url.begin(), std::find(url.begin(), url.end(), '#')}; for (const auto loader : c_schema_loaders.value()) { auto fn = g_loaders.find(fostlib::coerce(loader["loader"])); if (fn) { @@ -96,7 +99,8 @@ namespace { config["base"])}; fostlib::url fetch{ base, url.substr(prefix.code_points())}; - logger("base", base)("fetching", fetch)("found", true); + logger("base", + base)("fetching", fetch)("found", true); try { return http(base, fetch); } catch (fostlib::exceptions::exception &e) { @@ -116,7 +120,8 @@ namespace { } } else { logger("found", false); - logger("reason", "URL doesn't start with supplied prefix"); + logger("reason", + "URL doesn't start with supplied prefix"); } } else { logger("found", false); diff --git a/src/validator.cpp b/src/validator.cpp index 321a64a..c1fe20f 100644 --- a/src/validator.cpp +++ b/src/validator.cpp @@ -1,5 +1,6 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -11,6 +12,9 @@ #include +#include + + namespace { @@ -92,41 +96,79 @@ auto f5::json::validation::first_error(annotations an) -> result { return result{"false", std::move(an.spos), std::move(an.dpos)}; } else if (auto part = an.sroot[an.spos]; part.isobject()) { if (part.has_key("$ref")) { - const auto ref = fostlib::coerce(part["$ref"]); - if (ref.bytes() && *ref.begin() == '#') { - auto valid = first_error( - an, - fostlib::jcursor::parse_json_pointer_fragment( - fostlib::coerce(part["$ref"])), - an.dpos); + /// Easily switch between the two implementations here for now + bool const old_implementation{true}; + if constexpr (old_implementation) { + + auto const ref = fostlib::coerce(part["$ref"]); + if (ref.starts_with("#/") || ref == "#") { + auto valid = first_error( + an, + fostlib::jcursor::parse_json_pointer_fragment( + ref), + an.dpos); + if (not valid) + return valid; + else + return annotations(std::move(an), std::move(valid)); + } else { + fostlib::string url{ref}; + if (ref.starts_with("#")) { + url = fostlib::coerce( + fostlib::url{an.spos_url(), ref}); + } + const auto &cache = an.schemas(); + if (const auto frag = + std::find(url.begin(), url.end(), '#'); + frag == url.end()) { + auto const &[ref_schema, location] = + cache[fostlib::url{an.spos_url(), url}]; + auto valid = first_error( + annotations{an, ref_schema, pointer{}, + an.data, an.dpos}); + if (not valid) return valid; + return annotations{std::move(an), std::move(valid)}; + } else { + const f5::u8view us{url.begin(), frag}; + auto const &[ref_schema, location] = + cache[fostlib::url{an.spos_url(), us}]; + auto valid = first_error(annotations{ + an, ref_schema, + fostlib::jcursor::parse_json_pointer_fragment( + f5::u8view{frag, url.end()}), + an.data, an.dpos}); + if (not valid) return valid; + return annotations(std::move(an), std::move(valid)); + } + } + + } else { + + if (part["$ref"].isobject()) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Wait what?!"); + } + std::cout << "\nResolving $ref " << part["$ref"]; + std::cout << "Base URL " << an.spos_url() << std::endl; + std::cout << "As u8view " + << fostlib::coerce(part["$ref"]) + << std::endl; + fostlib::url const ref{ + an.spos_url(), + fostlib::coerce(part["$ref"])}; + std::cout << "Gives URL of " << ref << std::endl; + const auto &cache = an.schemas(); + auto const &[ref_schema, location] = cache[ref]; + std::cout << ref_schema.self() << " at " << location + << std::endl; + auto valid = first_error(annotations{ + an, ref_schema, location, an.data, an.dpos}); if (not valid) return valid; else return annotations(std::move(an), std::move(valid)); - } else { - const auto &cache = *an.schemas; - if (const auto frag = - std::find(ref.begin(), ref.end(), '#'); - frag == ref.end()) { - const fostlib::url u{an.spos_url(), ref}; - const auto &ref_schema = cache[u.as_string()]; - auto valid = first_error(annotations{ - an, ref_schema, pointer{}, an.data, an.dpos}); - if (not valid) return valid; - return annotations{std::move(an), std::move(valid)}; - } else { - const f5::u8view us{ref.begin(), frag}; - const fostlib::url u{an.spos_url(), us}; - const auto &ref_schema = cache[u.as_string()]; - auto valid = first_error(annotations{ - an, ref_schema, - fostlib::jcursor::parse_json_pointer_fragment( - f5::u8view{frag, ref.end()}), - an.data, an.dpos}); - if (not valid) return valid; - return annotations(std::move(an), std::move(valid)); - } } + } else { for (const auto &rule : part.object()) { const auto apos = g_assertions.find(rule.first); diff --git a/test/testsuite-v7/CMakeLists.txt b/test/testsuite-v7/CMakeLists.txt index de8794b..5f32cea 100644 --- a/test/testsuite-v7/CMakeLists.txt +++ b/test/testsuite-v7/CMakeLists.txt @@ -1,16 +1,23 @@ if(TARGET stress) set_property(TARGET stress PROPERTY EXCLUDE_FROM_ALL TRUE) - add_executable(json-schema-testsuite EXCLUDE_FROM_ALL testsuite.cpp) - target_link_libraries(json-schema-testsuite f5-json-schema fost-cli fost-inet) - add_dependencies(stress json-schema-testsuite) + add_executable(json-schema-testsuite-runner EXCLUDE_FROM_ALL testsuite.cpp) + target_link_libraries(json-schema-testsuite-runner f5-json-schema fost-cli fost-inet) + add_dependencies(stress json-schema-testsuite-runner) + set(json-schema-testsuite-parts) - function(draftv7 name) - add_custom_command(TARGET json-schema-testsuite - COMMAND json-schema-testsuite -b false + ## The use of a macro rather than a function gets around all sorts of + ## weird cmake stuff to do with the scoping rules of appending to the + ## `json-schema-testsuite-parts` list. + macro(draftv7 name) + add_custom_command(OUTPUT json-schema-testsuite-${name} + COMMAND json-schema-testsuite-runner -b false + "-o" json-schema-testsuite-${name} "-p" ${CMAKE_CURRENT_SOURCE_DIR}/../checks/json-schema.schema.json - ${name}.json) - endfunction() + ${name}.json + MAIN_DEPENDENCY json-schema-testsuite-runner) + list(APPEND json-schema-testsuite-parts json-schema-testsuite-${name}) + endmacro() draftv7(additionalItems) draftv7(additionalProperties) @@ -47,4 +54,10 @@ if(TARGET stress) draftv7(required) draftv7(type) draftv7(uniqueItems) + + + add_custom_target(json-schema-testsuite + DEPENDS ${json-schema-testsuite-parts}) + set_property(TARGET json-schema-testsuite PROPERTY EXCLUDE_FROM_ALL TRUE) + add_dependencies(stress json-schema-testsuite) endif() diff --git a/test/testsuite-v7/testsuite.cpp b/test/testsuite-v7/testsuite.cpp index 7624acb..732e26d 100644 --- a/test/testsuite-v7/testsuite.cpp +++ b/test/testsuite-v7/testsuite.cpp @@ -1,5 +1,6 @@ /** - Copyright 2018, Proteus Technologies Co Ltd. + Copyright 2018-2019, Proteus Technologies Co Ltd. + Distributed under the Boost Software License, Version 1.0. See @@ -15,16 +16,26 @@ namespace { + + + constexpr f5::u8view base_url = + "https://raw.githubusercontent.com/json-schema-org/" + "JSON-Schema-Test-Suite/e64ebf90a001f4e0e18984d2086ea15765cfead2/"; + const fostlib::setting c_verbose( __FILE__, "json-schema-testsuite", "Verbose", false, true); + const fostlib::setting> c_output( + __FILE__, + "json-schema-testsuite", + "Output file", + fostlib::null, + true); const fostlib::setting c_base(__FILE__, "json-schema-testsuite", "Base URL", - "https://raw.githubusercontent.com/" - "json-schema-org/JSON-Schema-Test-Suite/" - "master/tests/draft7/", + base_url + "tests/draft7/", true); const fostlib::setting c_loaders{ @@ -34,10 +45,7 @@ namespace { f5::json::value::object_t github; github["loader"] = "http"; github["prefix"] = "http://localhost:1234/"; - github["base"] = - "https://raw.githubusercontent.com/" - "json-schema-org/JSON-Schema-Test-Suite/master/" - "remotes/"; + github["base"] = base_url + "remotes/"; return github; }()); return loaders; @@ -50,6 +58,7 @@ namespace { FSL_MAIN("json-schema-testsuite", "JSON Schema Test Suite Runner") (fostlib::ostream &out, fostlib::arguments &args) { args.commandSwitch("v", c_verbose); + args.commandSwitch("o", c_output); args.commandSwitch("p", f5::json::c_schema_path); fostlib::stringstream buffer; @@ -88,7 +97,13 @@ FSL_MAIN("json-schema-testsuite", "JSON Schema Test Suite Runner") } } - if (failed && not c_verbose.value()) out << buffer.str(); + if (failed && not c_verbose.value()) { + out << buffer.str(); + } else if (not failed && c_output.value()) { + fostlib::utf::save_file( + fostlib::coerce(c_output.value().value()), + ""); + } return std::min(failed, 255); } catch (...) {