Skip to content

Commit

Permalink
Added API to handle schema references to other schemas.
Browse files Browse the repository at this point in the history
  • Loading branch information
alfmep committed Apr 24, 2021
1 parent 32c1f12 commit 467c3ab
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 51 deletions.
111 changes: 80 additions & 31 deletions src/ujson/Schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ujson/internal.hpp>
#include <ujson/Schema.hpp>
#include <ujson/utils.hpp>
#include <regex>
#include <math.h>

Expand Down Expand Up @@ -73,19 +75,50 @@ namespace ujson {

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
Schema::Schema (jvalue& schema)
: root_schema (schema)
Schema::Schema (jvalue& root_instance)
: root_schema (root_instance)
{
}


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
Schema::result_t Schema::validate (jvalue& instance)
{
return validate_impl (root_schema, instance);
}


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
bool Schema::add_ref_schema (const Schema& schema)
{
auto& id = find_jvalue (const_cast<jvalue&>(schema.root_schema), "/$id");
if (id.type() != j_string)
return false;

ref_schemas.emplace (id.str(), Schema(schema));
ref_cache.clear ();
return true;
}


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
void Schema::del_ref_schema (const std::string& id)
{
ref_schemas.erase (id);
ref_cache.clear ();
}


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
jvalue& Schema::root ()
{
return root_schema;
}


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
static bool is_schema_type (jvalue& schema)
Expand All @@ -96,24 +129,51 @@ namespace ujson {

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
jvalue* Schema::get_ref_schema (const std::string& ref)
Schema::result_t Schema::handle_ref (const std::string& ref, jvalue& instance)
{
jvalue* schema = nullptr;
std::stringstream ss (ref);
std::string part;
std::vector<std::string> parts;
std::getline(ss, part, '/');
if (part == "#") {
while (std::getline(ss, part, '/')) {
if (schema == nullptr)
schema = &(root_schema.get(part));
else
schema = &(schema->get(part));
if (schema->type() == j_invalid)
break;
auto entry = ref_cache.find (ref);
if (entry != ref_cache.end()) {
Schema& ref_root = entry->second.first;
jvalue& ref_schema = entry->second.second;
return ref_root.validate_impl (ref_schema, instance);
}

std::string id;
std::string pointer;

auto pos = ref.find ("#");
if (pos == std::string::npos) {
pointer = ref;
}else{
id = ref.substr (0, pos);
pointer = ref.substr (pos+1);
}
pointer.insert (0, 1, '/'); // Make sure the pointer starts with /

if (id.empty()) {
// Reference to pointer in this schema
auto& ref_schema = find_jvalue (root_schema, pointer);
if (ref_schema.valid()) {
ref_cache.emplace (ref,
std::make_pair(std::reference_wrapper<Schema>(*this),
std::reference_wrapper<jvalue>(ref_schema)));
}
return validate_impl (ref_schema, instance);
}else{
// Reference to pointer in other schema
auto entry = ref_schemas.find (id);
if (entry == ref_schemas.end()) {
return err_schema;
}
auto& ref_root = entry->second;
auto& ref_schema = find_jvalue (ref_root.root_schema, pointer);
if (ref_schema.valid()) {
ref_cache.emplace (ref,
std::make_pair(std::reference_wrapper<Schema>(ref_root),
std::reference_wrapper<jvalue>(ref_schema)));
}
return ref_root.validate_impl (ref_schema, instance);
}
return schema;
}


Expand Down Expand Up @@ -141,14 +201,8 @@ namespace ujson {
// Handle keyword "$ref" first
//
auto& ref = schema.get("$ref");
if (ref.type() != j_invalid) {
jvalue* ref_schema = get_ref_schema (ref.str());
if (!ref_schema)
return err_schema;
vdata.result = validate_impl (*ref_schema, instance);
if (vdata.result != valid)
return vdata.result;
}
if (ref.type() != j_invalid)
return handle_ref (ref.str(), instance);

// Handle the schema keywords
//
Expand Down Expand Up @@ -340,11 +394,6 @@ namespace ujson {
}
}

//
// Validation
//


if (vdata.result != valid)
return vdata.result;
}
Expand Down
33 changes: 28 additions & 5 deletions src/ujson/Schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,37 @@ namespace ujson {
};

/**
* Constructor.
* Default constructor.
*/
Schema (jvalue& schema);
Schema () = default;

/**
* Destructor.
* Constructor.
*/
~Schema () = default;
Schema (jvalue& root_instance);

/**
* @return Schema::valid on success.
*/
result_t validate (jvalue& instance);

/**
* Add a schema that this schema mey refer to.
*/
bool add_ref_schema (const Schema& schema);

/**
* Remove a schema that this schema mey refer to.
* @param id The id of the schema to remove.
*/
void del_ref_schema (const std::string& id);

/**
* Return the root schema instance.
*/
jvalue& root ();


private:
struct vdata_t {
jvalue& schema;
Expand All @@ -74,9 +91,15 @@ namespace ujson {
};

jvalue root_schema;
jvalue* get_ref_schema (const std::string& ref);
std::map<std::string, Schema> ref_schemas;
std::map<std::string,
std::pair<std::reference_wrapper<Schema>,
std::reference_wrapper<jvalue>>> ref_cache;

result_t validate_impl (jvalue& schema, jvalue& instance);

result_t handle_ref (const std::string& ref, jvalue& instance);

// Core - all types
void handle_allOf (vdata_t& vdata, jvalue& value);
void handle_anyOf (vdata_t& vdata, jvalue& value);
Expand Down
49 changes: 34 additions & 15 deletions utils/ujson-verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct appargs_t {
bool quiet;
std::vector<string> files;
std::string schema_file;
std::vector<string> ref_schema_files;

appargs_t() {
strict = true;
Expand All @@ -59,10 +60,14 @@ static void print_usage_and_exit (std::ostream& out, int exit_code)
<< "Usage: " << prog_name << " [OPTIONS] [FILE...]" << endl
<< endl
<< "Options:" <<endl
<< " -r, --relax Relaxed parsing, don't use strict mode when parsing." << endl
<< " -s, --schema=FILE Validate the JSON document with a schema file.." << endl
<< " -q, --quiet Silent mode, don't write anything to standard output." << endl
<< " -h, --help Print this help message and exit." << endl
<< " -r, --relax Relaxed parsing, don't use strict mode when parsing." << endl
<< " -s, --schema=FILE Validate the JSON document with a schema file.." << endl
<< " This option can be used multiple times." << endl
<< " The first schema will be used to validate the JSON document." << endl
<< " All schemas added after the first are schemas" << endl
<< " that can be referenced by the first schema." << endl
<< " -q, --quiet Silent mode, don't write anything to standard output." << endl
<< " -h, --help Print this help message and exit." << endl
<< endl;
exit (exit_code);
}
Expand Down Expand Up @@ -90,7 +95,10 @@ static void parse_args (int argc, char* argv[], appargs_t& args)
args.strict = false;
break;
case 's':
args.schema_file = optarg;
if (args.schema_file.empty())
args.schema_file = optarg;
else
args.ref_schema_files.emplace_back (optarg);
break;
case 'q':
args.quiet = true;
Expand All @@ -112,8 +120,7 @@ static void parse_args (int argc, char* argv[], appargs_t& args)
//------------------------------------------------------------------------------
static int verify_document (const std::string& filename,
ujson::Json& parser,
ujson::Schema& v,
ujson::jvalue& schema,
ujson::Schema& schema,
bool strict,
bool quiet)
{
Expand All @@ -128,8 +135,8 @@ static int verify_document (const std::string& filename,
}

//
if (schema.valid() &&
v.validate(instance) != ujson::Schema::valid)
if (schema.root().valid() &&
schema.validate(instance) != ujson::Schema::valid)
{
if (!quiet)
cout << log_filename << ": Not validated by schema" << endl;
Expand All @@ -148,24 +155,36 @@ int main (int argc, char* argv[])
parse_args (argc, argv, args);

ujson::Json parser;
ujson::jvalue schema;
ujson::Schema schema;

if (!args.schema_file.empty()) {
schema = parser.parse_file (args.schema_file, args.strict);
if (!schema.valid()) {
schema.root() = parser.parse_file (args.schema_file, args.strict);
if (!schema.root().valid()) {
if (!args.quiet)
std::cerr << "Schema error: " << parser.error() << std::endl;
std::cerr << "Schema error in file '" << args.schema_file << "': "<< parser.error() << std::endl;
exit (1);
}
for (auto& ref_file : args.ref_schema_files) {
ujson::jvalue s = parser.parse_file (ref_file, args.strict);
if (!s.valid()) {
if (!args.quiet)
std::cerr << "Schema error in file '" << ref_file << "': "<< parser.error() << std::endl;
exit (1);
}
if (!schema.add_ref_schema(ujson::Schema(s))) {
std::cerr << "Error adding schema file '" << ref_file << "': Missing \"$id\"" << parser.error() << std::endl;
exit (1);
}
}
}

if (args.files.empty())
args.files.emplace_back (""); // Parse standard input

int retval = 0;
ujson::Schema v (schema);
for (auto& filename : args.files) {
std::string log_filename = filename.empty() ? "JSON document" : filename;
if (verify_document(filename, parser, v, schema, args.strict, args.quiet))
if (verify_document(filename, parser, schema, args.strict, args.quiet))
retval = 1;
else if (!args.quiet)
cout << log_filename << ": ok" << endl;
Expand Down

0 comments on commit 467c3ab

Please sign in to comment.