#pragma once #include #include #include #include #include #include #include #include namespace jvalidate { class ValidationResult { public: template friend class ValidationVisitor; using DocPointer = detail::Pointer; using SchemaPointer = detail::Pointer; using Annotation = std::variant>; struct LocalResult { bool valid; std::map errors; std::map annotations; }; struct indent { indent(int i) : i(i) {} friend std::ostream & operator<<(std::ostream & os, indent id) { while (id.i-- > 0) os << " "; return os; } int i; }; private: bool valid_; std::map> results_; public: friend std::ostream & operator<<(std::ostream & os, ValidationResult const & result) { char const * div = "\n"; os << "{\n" << indent(1) << R"("valid": )" << (result.valid_ ? "true" : "false") << ',' << '\n'; os << indent(1) << R"("details": [)"; for (auto const & [doc_path, by_schema] : result.results_) { for (auto const & [schema_path, local] : by_schema) { os << std::exchange(div, ",\n") << indent(2) << '{' << '\n'; os << indent(3) << R"("valid": )" << (local.valid ? "true" : "false") << ',' << '\n'; os << indent(3) << R"("evaluationPath": ")" << schema_path << '"' << ',' << '\n'; os << indent(3) << R"("instanceLocation": ")" << doc_path << '"'; print(os, local.annotations, "annotations", 3); print(os, local.errors, "errors", 3); os << '\n' << indent(2) << '}'; } } return os << '\n' << indent(1) << ']' << '\n' << '}'; } static void print(std::ostream & os, std::map const & named, std::string_view name, int const i) { if (named.empty()) { return; } os << ',' << '\n'; os << indent(i) << '"' << name << '"' << ':' << ' ' << '{' << '\n'; for (auto const & [key, anno] : named) { os << indent(i + 1) << '"' << key << '"' << ':' << ' '; if (auto const * str = std::get_if<0>(&anno)) { os << '"' << *str << '"'; } else if (auto const * vec = std::get_if<1>(&anno)) { os << '['; char const * div = "\n"; for (size_t n = 0; n < vec->size(); ++n) { os << std::exchange(div, ",\n") << indent(i + 2) << '"' << vec->at(n) << '"'; } os << '\n' << indent(i + 1) << ']'; } os << '\n'; } os << indent(i) << '}'; } bool has(detail::Pointer const & where, detail::Pointer const & schema_path) const { return has(where) && results_.at(where).contains(schema_path); } bool has(detail::Pointer const & where) const { return results_.contains(where); } Annotation const * annotation(detail::Pointer const & where, detail::Pointer const & schema_path, std::string const & name) const { if (not results_.contains(where)) { return nullptr; } auto const & by_schema = results_.at(where); if (not by_schema.contains(schema_path)) { return nullptr; } auto const & local = by_schema.at(schema_path); if (not local.annotations.contains(name)) { return nullptr; } return &local.annotations.at(name); } Annotation const * error(detail::Pointer const & where, detail::Pointer const & schema_path, std::string const & name) const { if (not results_.contains(where)) { return nullptr; } auto const & by_schema = results_.at(where); if (not by_schema.contains(schema_path)) { return nullptr; } auto const & local = by_schema.at(schema_path); if (not local.errors.contains(name)) { return nullptr; } return &local.errors.at(name); } private: void merge(ValidationResult && result) { for (auto && [where, by_schema] : result.results_) { for (auto && [schema_path, local] : by_schema) { results_[where][schema_path].annotations.merge(local.annotations); results_[where][schema_path].errors.merge(local.errors); } } } void valid(detail::Pointer const & where, detail::Pointer const & schema_path, bool valid) { if (has(where, schema_path)) { results_[where][schema_path].valid = valid; } if (where.empty() && schema_path.empty()) { valid_ = valid; } } void error(detail::Pointer const & where, detail::Pointer const & schema_path, std::string const & name, Annotation message) { if (std::visit([](auto const & v) { return v.empty(); }, message)) { return; } results_[where][schema_path].errors.emplace(name, std::move(message)); } void annotate(detail::Pointer const & where, detail::Pointer const & schema_path, std::string const & name, Annotation message) { if (std::visit([](auto const & v) { return v.empty(); }, message)) { return; } results_[where][schema_path].annotations.emplace(name, std::move(message)); } }; }