From dee2fa64fffb769d94dd1b60bdfa6b9df2b1df37 Mon Sep 17 00:00:00 2001 From: Johannes Rave Date: Sun, 17 Jul 2022 07:29:40 +0300 Subject: [PATCH] Support for time related format fields --- .gitignore | 1 + CMakeLists.txt | 4 + include/valijson/schema_parser.hpp | 13 +++- include/valijson/validation_visitor.hpp | 99 +++++++++++++++++++++++-- tests/test_validator.cpp | 18 +++++ 5 files changed, 124 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index c4a612d..67a1212 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ doc/html .idea cmake-build-* CMakeFiles/ +.vs diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ebf31..9b63b68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,10 @@ if(valijson_BUILD_TESTS) set_target_properties(test_suite PROPERTIES COMPILE_FLAGS " -pedantic -Werror -Wshadow -Wunused") endif() + if (MSVC) + target_compile_options(test_suite PRIVATE "/bigobj") + endif() + # Definition for using picojson set_target_properties(test_suite PROPERTIES COMPILE_DEFINITIONS "PICOJSON_USE_INT64") diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index 0d29304..0c0d5da 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -1436,11 +1436,16 @@ private: constraints::FormatConstraint makeFormatConstraint( const AdapterType &node) { - constraints::FormatConstraint constraint; + if (node.isString()) { + const std::string value = node.asString(); + if (!value.empty()) { + constraints::FormatConstraint constraint; + constraint.setFormat(value); + return constraint; + } + } - // TODO - - return constraint; + throwRuntimeError("Expected a string value for 'format' constraint."); } /** diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index f13403d..3adb0d1 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -348,15 +348,59 @@ public: } /** - * @brief Validate current node against a FormatConstraint - * - * @param constraint Constraint that the target must validate against - * - * @return \c true if validation succeeds; \c false otherwise - */ + * @brief Validate current node against a FormatConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation succeeds; \c false otherwise + */ bool visit(const FormatConstraint &constraint) override { - // TODO + const std::string s = m_target.asString(); + const std::string format = constraint.getFormat(); + if (format == "date") { + // Matches dates like: 2022-07-18 + std::regex date_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$"); + std::smatch matches; + if (std::regex_match(s, matches, date_regex)) { + const auto month = std::stoi(matches[2].str()); + const auto day = std::stoi(matches[3].str()); + return validate_date_range(month, day); + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date"); + } + return false; + } + } else if (format == "time") { + // Matches times like: 16:52:45Z, 16:52:45+02:00 + std::regex time_regex("^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$"); + if (std::regex_match(s, time_regex)) { + return true; + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid time"); + } + return false; + } + } else if (format == "date-time") { + // Matches data times like: 2022-07-18T16:52:45Z, 2022-07-18T16:52:45+02:00 + std::regex datetime_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$"); + std::smatch matches; + if (std::regex_match(s, matches, datetime_regex)) { + const auto month = std::stoi(matches[2].str()); + const auto day = std::stoi(matches[3].str()); + return validate_date_range(month, day); + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + } return true; } @@ -1783,6 +1827,47 @@ private: return constraint.accept(visitor); } + /** + * @brief Helper function to validate if day is valid for given month + * + * @param month Month, 1-12 + * @param day Day, 1-31 + * + * @return true if day is valid for given month, false otherwise. + */ + bool validate_date_range(int month, int day) + { + if (month == 2) { + if (day < 0 || day > 29) { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + } else { + int limit = 31; + if (month <= 7) { + if (month % 2 == 0) { + limit = 30; + } + } else { + if (month % 2 != 0) { + limit = 30; + } + } + if (day < 0 || day > limit) { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + + } + return true; + } + /// The JSON value being validated AdapterType m_target; diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp index 6e8d971..e2bf78e 100644 --- a/tests/test_validator.cpp +++ b/tests/test_validator.cpp @@ -15,10 +15,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -176,6 +178,7 @@ protected: processTestFile(testFile, version); processTestFile(testFile, version); processTestFile(testFile, version); + processTestFile(testFile, version); #ifdef VALIJSON_BUILD_POCO_ADAPTER processTestFile(testFile, version); @@ -605,3 +608,18 @@ TEST_F(TestValidator, Draft7_UniqueItems) { processDraft7TestFile(TEST_SUITE_DIR "draft7/uniqueItems.json"); } + +TEST_F(TestValidator, Draft7_OptionalFormatDate) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/date.json"); +} + +TEST_F(TestValidator, Draft7_OptionalFormatTime) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/time.json"); +} + +TEST_F(TestValidator, Draft7_OptionalFormatDateTime) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/date-time.json"); +} \ No newline at end of file