Browse Source

feat: implement uri_template format

Sam Jaffe 7 months ago
parent
commit
29bdafb5a8
1 changed files with 70 additions and 2 deletions
  1. 70 2
      include/jvalidate/format.h

+ 70 - 2
include/jvalidate/format.h

@@ -35,6 +35,7 @@ bool duration(std::string_view dur);
 
 template <typename CharT = char> bool uri(std::basic_string_view<CharT> uri);
 template <typename CharT = char> bool uri_reference(std::basic_string_view<CharT> uri);
+bool uri_template(std::u32string_view uri);
 bool uuid(std::string_view id);
 template <typename CharT = char> bool hostname(std::basic_string_view<CharT> name);
 
@@ -109,11 +110,62 @@ inline bool is_pchar(std::basic_string_view<CharT> part, size_t & pos,
     return true;
   }
   if (part[pos] == '%') {
-    return std::strchr(g_hex_digits, part[++pos]) && std::strchr(g_hex_digits, part[++pos]);
+    return pos + 2 < part.size() && std::strchr(g_hex_digits, part[++pos]) &&
+           std::strchr(g_hex_digits, part[++pos]);
   }
   return extra_valid_chars.find(part[pos]) != part.npos;
 };
 
+inline bool is_uri_template_literal(std::u32string_view part, size_t & pos) {
+  constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
+  if (part[pos] == '%') {
+    return pos + 2 < part.size() && std::strchr(g_hex_digits, part[++pos]) &&
+           std::strchr(g_hex_digits, part[++pos]);
+  }
+  return !std::strchr(R"( "'%<>\^`{|}`)", part[pos]) && part[pos] > 0x1F && part[pos] != 0x7F;
+}
+
+inline bool is_uri_template_varchar(std::u32string_view part, size_t & pos) {
+  constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
+  if (part[pos] == '%') {
+    return pos + 2 < part.size() && std::strchr(g_hex_digits, part[++pos]) &&
+           std::strchr(g_hex_digits, part[++pos]);
+  }
+  return std::isalnum(part[pos]) || part[pos] == '_';
+}
+
+inline bool is_uri_template_expression(std::u32string_view part) {
+  if (part.empty()) {
+    return false;
+  }
+
+  if (std::strchr("+#./;?&=,!@|", part[0])) {
+    part.remove_prefix(1);
+  }
+
+  for (size_t pos = part.find(','); !part.empty();
+       part.remove_prefix(std::min(part.size(), pos)), pos = part.find(',')) {
+    std::u32string_view varspec = part.substr(0, pos);
+    std::u32string_view expand;
+    if (size_t const mod = varspec.find_first_of(U":*"); mod != varspec.npos) {
+      expand = varspec.substr(mod + 1);
+      varspec.remove_suffix(expand.size() + 1);
+    }
+
+    if (expand.empty() || expand == U"*") {
+      // No Modifier, or Explode
+    } else if (expand.size() > 4 || expand[0] == '0' ||
+               not std::ranges::all_of(expand, [](char c) { return std::isdigit(c); })) {
+      return false;
+    }
+    for (size_t i = 0; i < varspec.size(); ++i) {
+      RETURN_UNLESS(is_uri_template_varchar(varspec, i) || (i > 0 && varspec[i] == '.'), false);
+    }
+  }
+
+  return true;
+}
+
 template <typename CharT> inline bool is_uri_authority(std::basic_string_view<CharT> uri) {
   if (size_t pos = uri.find('@'); pos != uri.npos && pos < uri.find('/')) {
     for (size_t i = 0; i < pos; ++i) {
@@ -258,6 +310,22 @@ template <typename CharT> inline bool uri_reference(std::basic_string_view<CharT
   return true;
 }
 
+inline bool uri_template(std::u32string_view uri) {
+  for (size_t i = 0; i < uri.size(); ++i) {
+    if (uri[i] != '{') {
+      RETURN_UNLESS(detail::is_uri_template_literal(uri, i), false);
+      continue;
+    }
+
+    std::u32string_view expr = uri.substr(i + 1);
+    size_t const pos = expr.find('}');
+    RETURN_UNLESS(pos != uri.npos, false);
+    RETURN_UNLESS(detail::is_uri_template_expression(expr.substr(0, pos)), false);
+    i += pos + 1;
+  }
+  return true;
+}
+
 inline bool uuid(std::string_view id) {
   constexpr char const * g_hex_digits = "0123456789ABCDEFabcdef";
   constexpr size_t g_uuid_len = 36;
@@ -525,7 +593,7 @@ private:
       {"time", &format::time},
       {"uri", &format::uri},
       {"uri-reference", &format::uri_reference},
-      {"uri-template", nullptr},
+      {"uri-template", &format::utf32<format::uri_template>},
       {"uuid", &format::uuid},
   };