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:
Patrick Boettcher 2018-05-22 18:02:52 +02:00
parent e3d42e65c2
commit 66e8f13f72
9 changed files with 125 additions and 70 deletions

View File

@ -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__ */

View File

@ -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

View File

@ -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
//

View File

@ -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

View File

@ -26,8 +26,8 @@
#include "json-schema.hpp"
#include <fstream>
#include <regex>
#include <iostream>
#include <regex>
using nlohmann::json;
using nlohmann::json_uri;

View File

@ -0,0 +1,3 @@
{
"type": "integer"
}

View File

@ -0,0 +1,3 @@
{
"type": "integer"
}

View File

@ -0,0 +1,11 @@
{
"definitions": {
"orNull": {
"anyOf": [
{"type": "null"},
{"$ref": "#"}
]
}
},
"type": "string"
}

View File

@ -0,0 +1,8 @@
{
"integer": {
"type": "integer"
},
"refToInteger": {
"$ref": "#/integer"
}
}