blob: e55b2f3e3a7dda35674e27c6188206f8c8381b65 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <libgen.h>
#include <fstream>
#include <map>
#include <regex>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/schema.h"
#include "rapidjson/stringbuffer.h"
using namespace rapidjson;
// Reads the content of a file into a JSON document.
bool ReadDocument(std::string file, Document* document) {
std::ifstream file_stream(file);
if (!file_stream.is_open()) {
fprintf(stderr, "Error: unable to open file %s.\n", file.c_str());
return false;
}
std::string content((std::istreambuf_iterator<char>(file_stream)),
std::istreambuf_iterator<char>());
file_stream.close();
if (document->Parse(content).HasParseError()) {
fprintf(stderr, "Error: unable to parse JSON in file %s.\n", file.c_str());
return false;
}
return true;
}
// A schema provider that can find schemas specified as URIs/paths relative to
// the main schema.
class LocalSchemaProvider : public IRemoteSchemaDocumentProvider {
public:
explicit LocalSchemaProvider(const std::string directory) :
directory_(directory),
has_errors_(false) {}
~LocalSchemaProvider() override {}
const SchemaDocument* GetRemoteDocument(
const char* uri, SizeType length) override {
std::string input(uri, length);
std::smatch matches;
std::regex pattern("^(file:)?([^/#:]+)$");
if (!std::regex_search(input, matches, pattern)) {
fprintf(stderr, "Error: could not find schema %s.\n", input.c_str());
has_errors_ = true;
return nullptr;
}
std::string file_name = matches[matches.size() == 2 ? 1 : 2].str();
if (documents_[file_name]) {
return documents_[file_name].get();
}
Document schema_document;
std::string file_path = directory_ + "/" + file_name;
if (!ReadDocument(file_path, &schema_document)) {
// ReadDocument already prints a useful error message.
has_errors_ = true;
return nullptr;
}
documents_[file_name] = std::make_unique<SchemaDocument>(schema_document);
return documents_[file_name].get();
}
// Returns true if some schemas could not be resolved.
// By default, missing schemas are just ignored.
bool HasErrors() {
return has_errors_;
}
private:
// Map of resolved documents.
std::map<std::string, std::unique_ptr<SchemaDocument>> documents_;
// Base directory for schema paths.
const std::string directory_;
// Whether some schema references could not be resolved.
bool has_errors_;
};
// Returns the base directory of a given file.
std::string BaseDir(const std::string file) {
char* file_copy = strdup(file.c_str());
char* base = dirname(file_copy);
std::string result(base);
free(file_copy);
return result;
}
int main(int argc, const char** argv) {
if (argc < 3 || argc > 4) {
printf("Usage: %s <schema> <file> [stamp]\n", argv[0]);
return 1;
}
const std::string schema_path = argv[1];
const std::string file_path = argv[2];
Document schema_document;
if (!ReadDocument(schema_path, &schema_document)) {
return 1;
}
Document file_document;
if (!ReadDocument(file_path, &file_document)) {
return 1;
}
std::string schema_base = BaseDir(schema_path);
LocalSchemaProvider provider(schema_base);
SchemaDocument schema(schema_document, nullptr /* uri */, 0 /* uriLength */,
&provider);
SchemaValidator validator(schema);
if (!file_document.Accept(validator)) {
fprintf(stderr, "Error: the file %s is invalid according to schema %s.\n",
file_path.c_str(), schema_path.c_str());
StringBuffer buffer;
validator.GetInvalidSchemaPointer().StringifyUriFragment(buffer);
fprintf(stderr, " - location in schema %s\n", buffer.GetString());
fprintf(stderr, " - affected keyword %s\n",
validator.GetInvalidSchemaKeyword());
buffer.Clear();
validator.GetInvalidDocumentPointer().StringifyUriFragment(buffer);
fprintf(stderr, " - document reference %s\n", buffer.GetString());
return 1;
}
if (provider.HasErrors()) {
return 1;
}
if (argc == 4) {
// Write the stamp file if one was given.
std::string stamp_path = argv[3];
std::ofstream stamp(stamp_path);
stamp.close();
}
return 0;
}