|
|
@@ -8,7 +8,9 @@
|
|
|
#include <ctime>
|
|
|
#include <string>
|
|
|
#include <string_view>
|
|
|
+#include <system_error>
|
|
|
#include <unordered_map>
|
|
|
+#include <unordered_set>
|
|
|
#include <utility>
|
|
|
|
|
|
#ifdef JVALIDATE_HAS_IDNA
|
|
|
@@ -21,6 +23,7 @@
|
|
|
#include <jvalidate/detail/pointer.h>
|
|
|
#include <jvalidate/detail/relative_pointer.h>
|
|
|
#include <jvalidate/detail/string.h>
|
|
|
+#include <jvalidate/enum.h>
|
|
|
#include <jvalidate/forward.h>
|
|
|
|
|
|
#define CONSTRUCTS(TYPE) format::ctor_as_valid<detail::TYPE>
|
|
|
@@ -46,6 +49,11 @@ template <typename CharT = char> bool email(std::basic_string_view<CharT> em);
|
|
|
}
|
|
|
|
|
|
namespace jvalidate::format::detail {
|
|
|
+inline bool is_hex(std::string_view s) {
|
|
|
+ constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
|
|
|
+ return s.find_first_not_of(g_hex_digits) == std::string::npos;
|
|
|
+}
|
|
|
+
|
|
|
struct result {
|
|
|
ptrdiff_t consumed;
|
|
|
bool valid;
|
|
|
@@ -216,6 +224,41 @@ template <typename CharT> bool test_uri_part(std::basic_string_view<CharT> & uri
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+namespace jvalidate::format::draft03 {
|
|
|
+namespace detail = jvalidate::format::detail;
|
|
|
+
|
|
|
+inline bool time(std::string_view dt) {
|
|
|
+ std::tm tm;
|
|
|
+ char const * end = strptime(dt.data(), "%T", &tm);
|
|
|
+ if (end == nullptr || (end - dt.data()) < 8) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return end == dt.end();
|
|
|
+}
|
|
|
+
|
|
|
+inline bool utc_millisec(std::string_view utc) {
|
|
|
+ int64_t itime;
|
|
|
+ if (auto [end, ec] = std::from_chars(utc.begin(), utc.end(), itime);
|
|
|
+ ec == std::errc{} && end == utc.end()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ double dtime;
|
|
|
+ auto [end, ec] = std::from_chars(utc.begin(), utc.end(), dtime);
|
|
|
+ return ec == std::errc{} && end == utc.end();
|
|
|
+}
|
|
|
+
|
|
|
+inline bool css_2_1_color(std::string_view color) {
|
|
|
+ constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
|
|
|
+ if (color[0] == '#') {
|
|
|
+ return color.size() <= 7 && detail::is_hex(color.substr(1));
|
|
|
+ }
|
|
|
+ static std::unordered_set<std::string_view> g_color_codes{
|
|
|
+ "maroon", "red", "orange", "yellow", "olive", "purple", "fuchsia", "white", "lime",
|
|
|
+ "green", "navy", "blue", "aqua", "teal", "black", "silver", "gray"};
|
|
|
+ return g_color_codes.contains(color);
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
namespace jvalidate::format {
|
|
|
inline bool date(std::string_view dt) {
|
|
|
auto [consumed, valid] = detail::date(dt);
|
|
|
@@ -337,17 +380,14 @@ inline bool uri_template(std::u32string_view uri) {
|
|
|
}
|
|
|
|
|
|
inline bool uuid(std::string_view id) {
|
|
|
- constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
|
|
|
constexpr size_t g_uuid_len = 36;
|
|
|
constexpr size_t g_uuid_tokens = 5;
|
|
|
char tok0[9], tok1[5], tok2[5], tok3[5], tok4[13];
|
|
|
|
|
|
- auto is_hex = [](std::string_view s) {
|
|
|
- return s.find_first_not_of(g_hex_digits) == std::string::npos;
|
|
|
- };
|
|
|
return id.size() == g_uuid_len &&
|
|
|
sscanf(id.data(), "%8s-%4s-%4s-%4s-%12s", tok0, tok1, tok2, tok3, tok4) == g_uuid_tokens &&
|
|
|
- is_hex(tok0) && is_hex(tok1) && is_hex(tok2) && is_hex(tok3) && is_hex(tok4);
|
|
|
+ detail::is_hex(tok0) && detail::is_hex(tok1) && detail::is_hex(tok2) &&
|
|
|
+ detail::is_hex(tok3) && detail::is_hex(tok4);
|
|
|
}
|
|
|
|
|
|
inline bool duration(std::string_view dur) {
|
|
|
@@ -639,6 +679,8 @@ public:
|
|
|
enum class Status { Unknown, Unimplemented, Valid, Invalid };
|
|
|
|
|
|
private:
|
|
|
+ std::unordered_map<std::string, Predicate> user_formats_{{"regex", nullptr}};
|
|
|
+
|
|
|
std::unordered_map<std::string, Predicate> supported_formats_{
|
|
|
{"date", &format::date},
|
|
|
{"date-time", &format::date_time},
|
|
|
@@ -653,7 +695,6 @@ private:
|
|
|
{"iri-reference", UTF32(uri_reference)},
|
|
|
{"json-pointer", CONSTRUCTS(Pointer)},
|
|
|
{"relative-json-pointer", CONSTRUCTS(RelativePointer)},
|
|
|
- {"regex", nullptr},
|
|
|
{"time", &format::time},
|
|
|
{"uri", &format::uri},
|
|
|
{"uri-reference", &format::uri_reference},
|
|
|
@@ -661,12 +702,41 @@ private:
|
|
|
{"uuid", &format::uuid},
|
|
|
};
|
|
|
|
|
|
+ std::unordered_map<std::string, Predicate> draft03_supported_formats_{
|
|
|
+ {"date", &format::date},
|
|
|
+ // One of the weird things about draft03 - date-time allows for timezone
|
|
|
+ // and fraction-of-second in the argument, but time only allows hh:mm:ss.
|
|
|
+ {"date-time", &format::date_time},
|
|
|
+ {"time", &format::draft03::time},
|
|
|
+ {"utc-millisec", &format::draft03::utc_millisec},
|
|
|
+ {"color", &format::draft03::css_2_1_color},
|
|
|
+ {"style", nullptr},
|
|
|
+ {"phone", nullptr},
|
|
|
+ {"uri", &format::uri},
|
|
|
+ {"email", &format::email},
|
|
|
+ {"ip-address", &format::ipv4},
|
|
|
+ {"ipv6", &format::ipv6},
|
|
|
+ {"host-name", &format::hostname},
|
|
|
+ };
|
|
|
+
|
|
|
public:
|
|
|
FormatValidator() = default;
|
|
|
- FormatValidator(Predicate is_regex) { supported_formats_.insert_or_assign("regex", is_regex); }
|
|
|
+ FormatValidator(Predicate is_regex) { user_formats_.insert_or_assign("regex", is_regex); }
|
|
|
+
|
|
|
+ Status operator()(std::string const & format, schema::Version for_version,
|
|
|
+ std::string_view text) const {
|
|
|
+ auto const & supported =
|
|
|
+ for_version == schema::Version::Draft03 ? draft03_supported_formats_ : supported_formats_;
|
|
|
+ if (Status rval = (*this)(supported, format, text); rval != Status::Unknown) {
|
|
|
+ return rval;
|
|
|
+ }
|
|
|
+ return (*this)(user_formats_, format, text);
|
|
|
+ }
|
|
|
|
|
|
- Status operator()(std::string const & format, std::string_view text) const {
|
|
|
- if (auto it = supported_formats_.find(format); it != supported_formats_.end() && it->second) {
|
|
|
+private:
|
|
|
+ Status operator()(auto const & supported, std::string const & format,
|
|
|
+ std::string_view text) const {
|
|
|
+ if (auto it = supported.find(format); it != supported.end()) {
|
|
|
if (not it->second) {
|
|
|
return Status::Unimplemented;
|
|
|
}
|