URIs/URLs/URNs: fix multiple URI for one (sub-)schema
This fixes other things as well: - handle transition from URN to URI correclty - URL constructions by handling '/' correctly Fixes #9
This commit is contained in:
parent
e3d42e65c2
commit
66e8f13f72
@ -106,7 +106,7 @@ class JSON_SCHEMA_VALIDATOR_API json_uri
|
||||
|
||||
protected:
|
||||
// decodes a JSON uri and replaces all or part of the currently stored values
|
||||
void from_string(const std::string &uri);
|
||||
void update(const std::string &uri);
|
||||
|
||||
std::tuple<std::string, std::string, std::string, std::string, std::string> tie() const
|
||||
{
|
||||
@ -116,7 +116,7 @@ protected:
|
||||
public:
|
||||
json_uri(const std::string &uri)
|
||||
{
|
||||
from_string(uri);
|
||||
update(uri);
|
||||
}
|
||||
|
||||
const std::string protocol() const { return proto_; }
|
||||
@ -124,7 +124,8 @@ public:
|
||||
const std::string path() const { return path_; }
|
||||
const local_json_pointer pointer() const { return pointer_; }
|
||||
|
||||
const std::string url() const;
|
||||
const std::string url() const { return location(); }
|
||||
const std::string location() const;
|
||||
|
||||
// decode and encode strings for ~ and % escape sequences
|
||||
static std::string unescape(const std::string &);
|
||||
@ -135,7 +136,7 @@ public:
|
||||
json_uri derive(const std::string &uri) const
|
||||
{
|
||||
json_uri u = *this;
|
||||
u.from_string(uri);
|
||||
u.update(uri);
|
||||
return u;
|
||||
}
|
||||
|
||||
@ -179,7 +180,7 @@ class JSON_SCHEMA_VALIDATOR_API json_validator
|
||||
void validate(const json &instance, const json &schema_, const std::string &name);
|
||||
void validate_array(const json &instance, const json &schema_, const std::string &name);
|
||||
void validate_object(const json &instance, const json &schema_, const std::string &name);
|
||||
void validate_string(const json &instance, const json &schema, const std::string &name);
|
||||
void validate_string(const json &instance, const json &schema, const std::string &name);
|
||||
|
||||
void insert_schema(const json &input, const json_uri &id);
|
||||
|
||||
@ -197,7 +198,7 @@ public:
|
||||
void validate(const json &instance);
|
||||
};
|
||||
|
||||
} // json_schema_draft4
|
||||
} // nlohmann
|
||||
} // namespace json_schema_draft4
|
||||
} // namespace nlohmann
|
||||
|
||||
#endif /* NLOHMANN_JSON_SCHEMA_HPP__ */
|
||||
|
||||
@ -50,54 +50,73 @@ void local_json_pointer::from_string(const std::string &r)
|
||||
} while (pos != std::string::npos);
|
||||
}
|
||||
|
||||
void json_uri::from_string(const std::string &uri)
|
||||
void json_uri::update(const std::string &uri)
|
||||
{
|
||||
// if it is an urn take it as it is - maybe there is more to be done
|
||||
if (uri.find("urn:") == 0) {
|
||||
urn_ = uri;
|
||||
return;
|
||||
}
|
||||
std::string pointer = "#"; // default pointer is document-root
|
||||
|
||||
std::string pointer = "#"; // default pointer is the root
|
||||
|
||||
// first split the URI into URL and JSON-pointer
|
||||
// first split the URI into location and pointer
|
||||
auto pointer_separator = uri.find('#');
|
||||
if (pointer_separator != std::string::npos) // and extract the JSON-pointer-string if found
|
||||
if (pointer_separator != std::string::npos) // and extract the pointer-string if found
|
||||
pointer = uri.substr(pointer_separator);
|
||||
|
||||
// the rest is an URL
|
||||
std::string url = uri.substr(0, pointer_separator);
|
||||
if (url.size()) { // if an URL is part of the URI
|
||||
auto location = uri.substr(0, pointer_separator);
|
||||
|
||||
std::size_t pos = 0;
|
||||
auto proto = url.find("://", pos);
|
||||
if (proto != std::string::npos) { // extract the protocol
|
||||
proto_ = url.substr(pos, proto - pos);
|
||||
pos = 3 + proto; // 3 == "://"
|
||||
if (location.size()) { // a location part has been found
|
||||
pointer_ = local_json_pointer(""); // if a location is given, the pointer is emptied
|
||||
|
||||
auto hostname = url.find("/", pos);
|
||||
if (hostname != std::string::npos) { // and the hostname (no proto without hostname)
|
||||
hostname_ = url.substr(pos, hostname - pos);
|
||||
pos = hostname;
|
||||
// if it is an URN take it as it is
|
||||
if (location.find("urn:") == 0) {
|
||||
urn_ = location;
|
||||
|
||||
// and clear URL members
|
||||
proto_ = "";
|
||||
hostname_ = "";
|
||||
path_ = "";
|
||||
|
||||
} else { // it is an URL
|
||||
|
||||
// split URL in protocol, hostname and path
|
||||
std::size_t pos = 0;
|
||||
auto proto = location.find("://", pos);
|
||||
if (proto != std::string::npos) { // extract the protocol
|
||||
|
||||
urn_ = ""; // clear URN-member if URL is parsed
|
||||
|
||||
proto_ = location.substr(pos, proto - pos);
|
||||
pos = 3 + proto; // 3 == "://"
|
||||
|
||||
auto hostname = location.find("/", pos);
|
||||
if (hostname != std::string::npos) { // and the hostname (no proto without hostname)
|
||||
hostname_ = location.substr(pos, hostname - pos);
|
||||
pos = hostname;
|
||||
}
|
||||
}
|
||||
|
||||
auto path = location.substr(pos);
|
||||
|
||||
// URNs cannot of have paths
|
||||
if (urn_.size() && path.size())
|
||||
throw std::invalid_argument("Cannot add a path (" + path + ") to an URN URI (" + urn_ + ")");
|
||||
|
||||
if (path[0] == '/') // if it starts with a / it is root-path
|
||||
path_ = path;
|
||||
else if (pos == 0 && path_.back() != '/') // the URL contained only a path and the current path has no / at the end, it is an absolute path
|
||||
path_ = '/' + path;
|
||||
else // otherwise it is a subfolder
|
||||
path_.append(path);
|
||||
}
|
||||
|
||||
// the rest is the path
|
||||
auto path = url.substr(pos);
|
||||
if (path[0] == '/') // if it starts with a / it is root-path
|
||||
path_ = path;
|
||||
else // otherwise it is a subfolder
|
||||
path_.append(path);
|
||||
|
||||
pointer_ = local_json_pointer("");
|
||||
}
|
||||
|
||||
// if there was a pointer part store it internally
|
||||
if (pointer.size() > 0)
|
||||
pointer_ = pointer;
|
||||
}
|
||||
|
||||
const std::string json_uri::url() const
|
||||
const std::string json_uri::location() const
|
||||
{
|
||||
if (urn_.size())
|
||||
return urn_;
|
||||
|
||||
std::stringstream s;
|
||||
|
||||
if (proto_.size() > 0)
|
||||
@ -113,8 +132,7 @@ std::string json_uri::to_string() const
|
||||
{
|
||||
std::stringstream s;
|
||||
|
||||
s << urn_
|
||||
<< url()
|
||||
s << location()
|
||||
<< pointer_.to_string();
|
||||
|
||||
return s.str();
|
||||
@ -184,4 +202,4 @@ std::string json_uri::escape(const std::string &src)
|
||||
return l;
|
||||
}
|
||||
|
||||
} // nlohmann
|
||||
} // namespace nlohmann
|
||||
|
||||
@ -45,23 +45,29 @@ namespace
|
||||
|
||||
class resolver
|
||||
{
|
||||
void resolve(json &schema, json_uri id)
|
||||
void resolve(json &schema, std::vector<nlohmann::json_uri> base_uris)
|
||||
{
|
||||
// look for the id-field in this schema
|
||||
auto fid = schema.find("id");
|
||||
|
||||
// found?
|
||||
if (fid != schema.end() &&
|
||||
fid.value().type() == json::value_t::string)
|
||||
id = id.derive(fid.value()); // resolve to a full id with URL + path based on the parent
|
||||
fid.value().type() == json::value_t::string) {
|
||||
// resolve to a full id with URL + path based on last base_uri-added for this node
|
||||
auto id = base_uris.back().derive(fid.value());
|
||||
if (std::find(base_uris.begin(), base_uris.end(), id) == base_uris.end())
|
||||
base_uris.push_back(id);
|
||||
}
|
||||
|
||||
// already existing - error
|
||||
if (schema_refs.find(id) != schema_refs.end())
|
||||
throw std::invalid_argument("schema " + id.to_string() + " already present in local resolver");
|
||||
|
||||
// store a raw pointer to this (sub-)schema referenced by its absolute json_uri
|
||||
// store a raw pointer to this (sub-)schema referenced by all of its absolute json_uris
|
||||
// this (sub-)schema is part of a schema stored inside schema_store_ so we can use the a raw-pointer-ref
|
||||
schema_refs[id] = &schema;
|
||||
for (auto &u : base_uris) {
|
||||
// already existing - error
|
||||
if (schema_refs.find(u) != schema_refs.end())
|
||||
throw std::invalid_argument("schema " + u.to_string() + " already present in local resolver");
|
||||
|
||||
schema_refs[u] = &schema;
|
||||
}
|
||||
|
||||
for (auto i = schema.begin(), end = schema.end(); i != end; ++i) {
|
||||
// FIXME: this inhibits the user adding properties with the key "default"
|
||||
@ -70,23 +76,38 @@ class resolver
|
||||
|
||||
switch (i.value().type()) {
|
||||
|
||||
case json::value_t::object: // child is object, it is a schema
|
||||
resolve(i.value(), id.append(json_uri::escape(i.key())));
|
||||
break;
|
||||
case json::value_t::object: { // child is object, it is a schema
|
||||
std::vector<nlohmann::json_uri> subschema_uris = base_uris;
|
||||
|
||||
// add key to all of the URIs
|
||||
for (auto &s : subschema_uris)
|
||||
s = s.append(nlohmann::json_uri::escape(i.key()));
|
||||
|
||||
resolve(i.value(), subschema_uris);
|
||||
} break;
|
||||
|
||||
case json::value_t::array: {
|
||||
std::vector<nlohmann::json_uri> subschema_uris = base_uris;
|
||||
for (auto &s : subschema_uris)
|
||||
s = s.append(nlohmann::json_uri::escape(i.key()));
|
||||
|
||||
std::size_t index = 0;
|
||||
auto child_id = id.append(json_uri::escape(i.key()));
|
||||
for (auto &v : i.value()) {
|
||||
if (v.type() == json::value_t::object) // array element is object
|
||||
resolve(v, child_id.append(std::to_string(index)));
|
||||
if (v.type() == json::value_t::object) { // array element is object
|
||||
|
||||
std::vector<nlohmann::json_uri> subschema_item_uris = subschema_uris;
|
||||
for (auto &s : subschema_item_uris)
|
||||
s = s.append(std::to_string(index));
|
||||
resolve(v, subschema_item_uris);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
} break;
|
||||
|
||||
case json::value_t::string:
|
||||
if (i.key() == "$ref") {
|
||||
json_uri ref = id.derive(i.value());
|
||||
// use last inserted URI to derive the $ref-element
|
||||
auto ref = base_uris.back().derive(i.value());
|
||||
i.value() = ref.to_string();
|
||||
refs.insert(ref);
|
||||
}
|
||||
@ -107,12 +128,8 @@ public:
|
||||
|
||||
resolver(json &schema, json_uri id)
|
||||
{
|
||||
// if schema has an id use it as name and to retrieve the namespace (URL)
|
||||
auto fid = schema.find("id");
|
||||
if (fid != schema.end())
|
||||
id = id.derive(fid.value());
|
||||
|
||||
resolve(schema, id);
|
||||
resolve(schema, {{id}});
|
||||
|
||||
// refs now contains all references
|
||||
//
|
||||
|
||||
@ -38,12 +38,6 @@ if(JSON_SCHEMA_TEST_SUITE_PATH)
|
||||
COMMAND ${PIPE_IN_TEST_SCRIPT} $<TARGET_FILE:json-schema-test> ${TEST_FILE})
|
||||
endforeach()
|
||||
|
||||
# XXX Unfortunately URLs are not very well handled yet, accept those tests which fail
|
||||
set_tests_properties(JSON-Suite::ref
|
||||
JSON-Suite::refRemote
|
||||
PROPERTIES
|
||||
WILL_FAIL ON)
|
||||
|
||||
# some optional tests will fail as well.
|
||||
set_tests_properties(JSON-Suite::Optional::bignum
|
||||
JSON-Suite::Optional::ecmascript-regex
|
||||
|
||||
@ -26,8 +26,8 @@
|
||||
#include "json-schema.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_uri;
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
3
test/JSON-Schema-Test-Suite/remotes/integer.json
Normal file
3
test/JSON-Schema-Test-Suite/remotes/integer.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
11
test/JSON-Schema-Test-Suite/remotes/name.json
Normal file
11
test/JSON-Schema-Test-Suite/remotes/name.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"definitions": {
|
||||
"orNull": {
|
||||
"anyOf": [
|
||||
{"type": "null"},
|
||||
{"$ref": "#"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "string"
|
||||
}
|
||||
8
test/JSON-Schema-Test-Suite/remotes/subSchemas.json
Normal file
8
test/JSON-Schema-Test-Suite/remotes/subSchemas.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"integer": {
|
||||
"type": "integer"
|
||||
},
|
||||
"refToInteger": {
|
||||
"$ref": "#/integer"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user