You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

472 lines
19 KiB

  1. #include <nlohmann/json-schema.hpp>
  2. #include <algorithm>
  3. #include <exception>
  4. #include <iostream>
  5. #include <regex>
  6. #include <sstream>
  7. #include <string>
  8. #include <utility>
  9. #include <vector>
  10. /**
  11. * Many of the RegExes are from @see http://jmrware.com/articles/2009/uri_regexp/URI_regex.html
  12. */
  13. namespace
  14. {
  15. template <typename T>
  16. void range_check(const T value, const T min, const T max)
  17. {
  18. if (!((value >= min) && (value <= max))) {
  19. std::stringstream out;
  20. out << "Value " << value << " should be in interval [" << min << "," << max << "] but is not!";
  21. throw std::invalid_argument(out.str());
  22. }
  23. }
  24. /** @see date_time_check */
  25. void rfc3339_date_check(const std::string &value)
  26. {
  27. const static std::regex dateRegex{R"(^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$)"};
  28. std::smatch matches;
  29. if (!std::regex_match(value, matches, dateRegex)) {
  30. throw std::invalid_argument(value + " is not a date string according to RFC 3339.");
  31. }
  32. const auto year = std::stoi(matches[1].str());
  33. const auto month = std::stoi(matches[2].str());
  34. const auto mday = std::stoi(matches[3].str());
  35. const auto isLeapYear = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
  36. range_check(month, 1, 12);
  37. if (month == 2) {
  38. range_check(mday, 1, isLeapYear ? 29 : 28);
  39. } else if (month <= 7) {
  40. range_check(mday, 1, month % 2 == 0 ? 30 : 31);
  41. } else {
  42. range_check(mday, 1, month % 2 == 0 ? 31 : 30);
  43. }
  44. }
  45. /** @see date_time_check */
  46. void rfc3339_time_check(const std::string &value)
  47. {
  48. const static std::regex timeRegex{R"(^([0-9]{2})\:([0-9]{2})\:([0-9]{2})(\.[0-9]+)?(?:[Zz]|((?:\+|\-)[0-9]{2})\:([0-9]{2}))$)"};
  49. std::smatch matches;
  50. if (!std::regex_match(value, matches, timeRegex)) {
  51. throw std::invalid_argument(value + " is not a time string according to RFC 3339.");
  52. }
  53. auto hour = std::stoi(matches[1].str());
  54. auto minute = std::stoi(matches[2].str());
  55. auto second = std::stoi(matches[3].str());
  56. // const auto secfrac = std::stof( matches[4].str() );
  57. range_check(hour, 0, 23);
  58. range_check(minute, 0, 59);
  59. int offsetHour = 0,
  60. offsetMinute = 0;
  61. /* don't check the numerical offset if time zone is specified as 'Z' */
  62. if (!matches[5].str().empty()) {
  63. offsetHour = std::stoi(matches[5].str());
  64. offsetMinute = std::stoi(matches[6].str());
  65. range_check(offsetHour, -23, 23);
  66. range_check(offsetMinute, 0, 59);
  67. if (offsetHour < 0)
  68. offsetMinute *= -1;
  69. }
  70. /**
  71. * @todo Could be made more exact by querying a leap second database and choosing the
  72. * correct maximum in {58,59,60}. This current solution might match some invalid dates
  73. * but it won't lead to false negatives. This only works if we know the full date, however
  74. */
  75. auto day_minutes = hour * 60 + minute - (offsetHour * 60 + offsetMinute);
  76. if (day_minutes < 0)
  77. day_minutes += 60 * 24;
  78. hour = day_minutes % 24;
  79. minute = day_minutes / 24;
  80. if (hour == 23 && minute == 59)
  81. range_check(second, 0, 60); // possible leap-second
  82. else
  83. range_check(second, 0, 59);
  84. }
  85. /**
  86. * @see https://tools.ietf.org/html/rfc3339#section-5.6
  87. *
  88. * @verbatim
  89. * date-fullyear = 4DIGIT
  90. * date-month = 2DIGIT ; 01-12
  91. * date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
  92. * ; month/year
  93. * time-hour = 2DIGIT ; 00-23
  94. * time-minute = 2DIGIT ; 00-59
  95. * time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
  96. * ; rules
  97. * time-secfrac = "." 1*DIGIT
  98. * time-numoffset = ("+" / "-") time-hour ":" time-minute
  99. * time-offset = "Z" / time-numoffset
  100. *
  101. * partial-time = time-hour ":" time-minute ":" time-second
  102. * [time-secfrac]
  103. * full-date = date-fullyear "-" date-month "-" date-mday
  104. * full-time = partial-time time-offset
  105. *
  106. * date-time = full-date "T" full-time
  107. * @endverbatim
  108. * NOTE: Per [ABNF] and ISO8601, the "T" and "Z" characters in this
  109. * syntax may alternatively be lower case "t" or "z" respectively.
  110. */
  111. void rfc3339_date_time_check(const std::string &value)
  112. {
  113. const static std::regex dateTimeRegex{R"(^([0-9]{4}\-[0-9]{2}\-[0-9]{2})[Tt]([0-9]{2}\:[0-9]{2}\:[0-9]{2}(?:\.[0-9]+)?(?:[Zz]|(?:\+|\-)[0-9]{2}\:[0-9]{2}))$)"};
  114. std::smatch matches;
  115. if (!std::regex_match(value, matches, dateTimeRegex)) {
  116. throw std::invalid_argument(value + " is not a date-time string according to RFC 3339.");
  117. }
  118. rfc3339_date_check(matches[1].str());
  119. rfc3339_time_check(matches[2].str());
  120. }
  121. const std::string decOctet{R"((?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]))"}; // matches numbers 0-255
  122. const std::string ipv4Address{"(?:" + decOctet + R"(\.){3})" + decOctet};
  123. const std::string h16{R"([0-9A-Fa-f]{1,4})"};
  124. const std::string h16Left{"(?:" + h16 + ":)"};
  125. const std::string ipv6Address{
  126. "(?:"
  127. "(?:" +
  128. h16Left + "{6}"
  129. "|::" +
  130. h16Left + "{5}"
  131. "|(?:" +
  132. h16 + ")?::" + h16Left + "{4}"
  133. "|(?:" +
  134. h16Left + "{0,1}" + h16 + ")?::" + h16Left + "{3}"
  135. "|(?:" +
  136. h16Left + "{0,2}" + h16 + ")?::" + h16Left + "{2}"
  137. "|(?:" +
  138. h16Left + "{0,3}" + h16 + ")?::" + h16Left +
  139. "|(?:" + h16Left + "{0,4}" + h16 + ")?::"
  140. ")(?:" +
  141. h16Left + h16 + "|" + ipv4Address + ")"
  142. "|(?:" +
  143. h16Left + "{0,5}" + h16 + ")?::" + h16 +
  144. "|(?:" + h16Left + "{0,6}" + h16 + ")?::"
  145. ")"};
  146. const std::string ipvFuture{R"([Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+)"};
  147. const std::string regName{R"((?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*)"};
  148. const std::string host{
  149. "(?:"
  150. R"(\[(?:)" +
  151. ipv6Address + "|" + ipvFuture + R"()\])" +
  152. "|" + ipv4Address +
  153. "|" + regName +
  154. ")"};
  155. const std::string uuid{R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"};
  156. // from http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
  157. const std::string hostname{R"(^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$)"};
  158. /**
  159. * @see https://tools.ietf.org/html/rfc5322#section-4.1
  160. *
  161. * @verbatim
  162. * atom = [CFWS] 1*atext [CFWS]
  163. * word = atom / quoted-string
  164. * phrase = 1*word / obs-phrase
  165. * obs-FWS = 1*WSP *(CRLF 1*WSP)
  166. * FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
  167. * ; Folding white space
  168. * ctext = %d33-39 / ; Printable US-ASCII
  169. * %d42-91 / ; characters not including
  170. * %d93-126 / ; "(", ")", or "\"
  171. * obs-ctext
  172. * ccontent = ctext / quoted-pair / comment
  173. * comment = "(" *([FWS] ccontent) [FWS] ")"
  174. * CFWS = (1*([FWS] comment) [FWS]) / FWS
  175. * obs-local-part = word *("." word)
  176. * obs-domain = atom *("." atom)
  177. * obs-dtext = obs-NO-WS-CTL / quoted-pair
  178. * quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
  179. * obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
  180. * %d11 / ; characters that do not
  181. * %d12 / ; include the carriage
  182. * %d14-31 / ; return, line feed, and
  183. * %d127 ; white space characters
  184. * obs-ctext = obs-NO-WS-CTL
  185. * obs-qtext = obs-NO-WS-CTL
  186. * obs-utext = %d0 / obs-NO-WS-CTL / VCHAR
  187. * obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
  188. * obs-body = *((*LF *CR *((%d0 / text) *LF *CR)) / CRLF)
  189. * obs-unstruct = *((*LF *CR *(obs-utext *LF *CR)) / FWS)
  190. * obs-phrase = word *(word / "." / CFWS)
  191. * obs-phrase-list = [phrase / CFWS] *("," [phrase / CFWS])
  192. * qtext = %d33 / ; Printable US-ASCII
  193. * %d35-91 / ; characters not including
  194. * %d93-126 / ; "\" or the quote character
  195. * obs-qtext
  196. * qcontent = qtext / quoted-pair
  197. * quoted-string = [CFWS]
  198. * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
  199. * [CFWS]
  200. * atext = ALPHA / DIGIT / ; Printable US-ASCII
  201. * "!" / "#" / ; characters not including
  202. * "$" / "%" / ; specials. Used for atoms.
  203. * "&" / "'" /
  204. * "*" / "+" /
  205. * "-" / "/" /
  206. * "=" / "?" /
  207. * "^" / "_" /
  208. * "`" / "{" /
  209. * "|" / "}" /
  210. * "~"
  211. * dot-atom-text = 1*atext *("." 1*atext)
  212. * dot-atom = [CFWS] dot-atom-text [CFWS]
  213. * addr-spec = local-part "@" domain
  214. * local-part = dot-atom / quoted-string / obs-local-part
  215. * domain = dot-atom / domain-literal / obs-domain
  216. * domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
  217. * dtext = %d33-90 / ; Printable US-ASCII
  218. * %d94-126 / ; characters not including
  219. * obs-dtext ; "[", "]", or "\"
  220. * @endverbatim
  221. * @todo Currently don't have a working tool for this larger ABNF to generate a regex.
  222. * Other options:
  223. * - https://github.com/ldthomas/apg-6.3
  224. * - https://github.com/akr/abnf
  225. *
  226. * The problematic thing are the allowed whitespaces (even newlines) in the email.
  227. * Ignoring those and starting with
  228. * @see https://stackoverflow.com/questions/13992403/regex-validation-of-email-addresses-according-to-rfc5321-rfc5322
  229. * and trying to divide up the complicated regex into understandable ABNF definitions from rfc5322 yields:
  230. */
  231. const std::string obsnowsctl{R"([\x01-\x08\x0b\x0c\x0e-\x1f\x7f])"};
  232. const std::string obsqp{R"(\\[\x01-\x09\x0b\x0c\x0e-\x7f])"};
  233. const std::string qtext{R"((?:[\x21\x23-\x5b\x5d-\x7e]|)" + obsnowsctl + ")"};
  234. const std::string dtext{R"([\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f])"};
  235. const std::string quotedString{R"("(?:)" + qtext + "|" + obsqp + R"()*")"};
  236. const std::string atext{R"([A-Za-z0-9!#$%&'*+/=?^_`{|}~-])"};
  237. const std::string domainLiteral{R"(\[(?:(?:)" + decOctet + R"()\.){3}(?:)" + decOctet + R"(|[A-Za-z0-9-]*[A-Za-z0-9]:(?:)" + dtext + "|" + obsqp + R"()+)\])"};
  238. const std::string dotAtom{"(?:" + atext + R"(+(?:\.)" + atext + "+)*)"};
  239. const std::string stackoverflowMagicPart{R"((?:[[:alnum:]](?:[[:alnum:]-]*[[:alnum:]])?\.)+)"
  240. R"([[:alnum:]](?:[[:alnum:]-]*[[:alnum:]])?)"};
  241. const std::string email{"(?:" + dotAtom + "|" + quotedString + ")@(?:" + stackoverflowMagicPart + "|" + domainLiteral + ")"};
  242. /**
  243. * @see
  244. *
  245. * @verbatim
  246. * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
  247. *
  248. * hier-part = "//" authority path-abempty
  249. * / path-absolute
  250. * / path-rootless
  251. * / path-empty
  252. *
  253. * URI-reference = URI / relative-ref
  254. *
  255. * absolute-URI = scheme ":" hier-part [ "?" query ]
  256. *
  257. * relative-ref = relative-part [ "?" query ] [ "#" fragment ]
  258. *
  259. * relative-part = "//" authority path-abempty
  260. * / path-absolute
  261. * / path-noscheme
  262. * / path-empty
  263. *
  264. * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
  265. *
  266. * authority = [ userinfo "@" ] host [ ":" port ]
  267. * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
  268. * host = IP-literal / IPv4address / reg-name
  269. * port = *DIGIT
  270. *
  271. * IP-literal = "[" ( IPv6address / IPvFuture ) "]"
  272. *
  273. * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
  274. *
  275. * IPv6address = 6( h16 ":" ) ls32
  276. * / "::" 5( h16 ":" ) ls32
  277. * / [ h16 ] "::" 4( h16 ":" ) ls32
  278. * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
  279. * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
  280. * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
  281. * / [ *4( h16 ":" ) h16 ] "::" ls32
  282. * / [ *5( h16 ":" ) h16 ] "::" h16
  283. * / [ *6( h16 ":" ) h16 ] "::"
  284. *
  285. * h16 = 1*4HEXDIG
  286. * ls32 = ( h16 ":" h16 ) / IPv4address
  287. * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
  288. * dec-octet = DIGIT ; 0-9
  289. * / %x31-39 DIGIT ; 10-99
  290. * / "1" 2DIGIT ; 100-199
  291. * / "2" %x30-34 DIGIT ; 200-249
  292. * / "25" %x30-35 ; 250-255
  293. *
  294. * reg-name = *( unreserved / pct-encoded / sub-delims )
  295. *
  296. * path = path-abempty ; begins with "/" or is empty
  297. * / path-absolute ; begins with "/" but not "//"
  298. * / path-noscheme ; begins with a non-colon segment
  299. * / path-rootless ; begins with a segment
  300. * / path-empty ; zero characters
  301. *
  302. * path-abempty = *( "/" segment )
  303. * path-absolute = "/" [ segment-nz *( "/" segment ) ]
  304. * path-noscheme = segment-nz-nc *( "/" segment )
  305. * path-rootless = segment-nz *( "/" segment )
  306. * path-empty = 0<pchar>
  307. *
  308. * segment = *pchar
  309. * segment-nz = 1*pchar
  310. * segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
  311. * ; non-zero-length segment without any colon ":"
  312. *
  313. * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  314. *
  315. * query = *( pchar / "/" / "?" )
  316. *
  317. * fragment = *( pchar / "/" / "?" )
  318. *
  319. * pct-encoded = "%" HEXDIG HEXDIG
  320. *
  321. * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  322. * reserved = gen-delims / sub-delims
  323. * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
  324. * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  325. * / "*" / "+" / "," / ";" / "="
  326. *
  327. * @endverbatim
  328. * @see adapted from: https://github.com/jhermsmeier/uri.regex/blob/master/uri.regex
  329. *
  330. */
  331. void rfc3986_uri_check(const std::string &value)
  332. {
  333. const static std::string scheme{R"(([A-Za-z][A-Za-z0-9+\-.]*):)"};
  334. const static std::string hierPart{
  335. R"((?:(\/\/)(?:((?:[A-Za-z0-9\-._~!$&'()*+,;=:]|)"
  336. R"(%[0-9A-Fa-f]{2})*)@)?((?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|)"
  337. R"(::(?:[0-9A-Fa-f]{1,4}:){5}|)"
  338. R"((?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|)"
  339. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|)"
  340. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|)"
  341. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|)"
  342. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|)"
  343. R"((?:(?:25[0-5]|2[0-4][0-9]|)"
  344. R"([01]?[0-9][0-9]?)\.){3}(?:25[0-5]|)"
  345. R"(2[0-4][0-9]|)"
  346. R"([01]?[0-9][0-9]?))|)"
  347. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|)"
  348. R"((?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|)"
  349. R"([Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+)\]|)"
  350. R"((?:(?:25[0-5]|)"
  351. R"(2[0-4][0-9]|)"
  352. R"([01]?[0-9][0-9]?)\.){3}(?:25[0-5]|)"
  353. R"(2[0-4][0-9]|)"
  354. R"([01]?[0-9][0-9]?)|)"
  355. R"((?:[A-Za-z0-9\-._~!$&'()*+,;=]|)"
  356. R"(%[0-9A-Fa-f]{2})*))(?::([0-9]*))?((?:\/(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|)"
  357. R"(%[0-9A-Fa-f]{2})*)*)|)"
  358. R"(\/((?:(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|)"
  359. R"(%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|)"
  360. R"(%[0-9A-Fa-f]{2})*)*)?)|)"
  361. R"(((?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|)"
  362. R"(%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|)"
  363. R"(%[0-9A-Fa-f]{2})*)*)|))"};
  364. const static std::string query{R"((?:\?((?:[A-Za-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9A-Fa-f]{2})*))?)"};
  365. const static std::string fragment{
  366. R"((?:\#((?:[A-Za-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9A-Fa-f]{2})*))?)"};
  367. const static std::string uriFormat{scheme + hierPart + query + fragment};
  368. const static std::regex uriRegex{uriFormat};
  369. if (!std::regex_match(value, uriRegex)) {
  370. throw std::invalid_argument(value + " is not a URI string according to RFC 3986.");
  371. }
  372. }
  373. } // namespace
  374. namespace nlohmann
  375. {
  376. namespace json_schema
  377. {
  378. /**
  379. * Checks validity for built-ins by converting the definitions given as ABNF in the linked RFC from
  380. * @see https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
  381. * into regular expressions using @see https://www.msweet.org/abnf/ and some manual editing.
  382. *
  383. * @see https://json-schema.org/latest/json-schema-validation.html
  384. */
  385. void default_string_format_check(const std::string &format, const std::string &value)
  386. {
  387. if (format == "date-time") {
  388. rfc3339_date_time_check(value);
  389. } else if (format == "date") {
  390. rfc3339_date_check(value);
  391. } else if (format == "time") {
  392. rfc3339_time_check(value);
  393. } else if (format == "uri") {
  394. rfc3986_uri_check(value);
  395. } else if (format == "email") {
  396. static const std::regex emailRegex{email};
  397. if (!std::regex_match(value, emailRegex)) {
  398. throw std::invalid_argument(value + " is not a valid email according to RFC 5322.");
  399. }
  400. } else if (format == "hostname") {
  401. static const std::regex hostRegex{hostname};
  402. if (!std::regex_match(value, hostRegex)) {
  403. throw std::invalid_argument(value + " is not a valid hostname according to RFC 3986 Appendix A.");
  404. }
  405. } else if (format == "ipv4") {
  406. const static std::regex ipv4Regex{"^" + ipv4Address + "$"};
  407. if (!std::regex_match(value, ipv4Regex)) {
  408. throw std::invalid_argument(value + " is not an IPv4 string according to RFC 2673.");
  409. }
  410. } else if (format == "ipv6") {
  411. static const std::regex ipv6Regex{ipv6Address};
  412. if (!std::regex_match(value, ipv6Regex)) {
  413. throw std::invalid_argument(value + " is not an IPv6 string according to RFC 5954.");
  414. }
  415. } else if (format == "uuid") {
  416. static const std::regex uuidRegex{uuid};
  417. if (!std::regex_match(value, uuidRegex)) {
  418. throw std::invalid_argument(value + " is not an uuid string according to RFC 4122.");
  419. }
  420. } else if (format == "regex") {
  421. try {
  422. std::regex re(value, std::regex::ECMAScript);
  423. } catch (std::exception &exception) {
  424. throw exception;
  425. }
  426. } else {
  427. /* yet unsupported JSON schema draft 7 built-ins */
  428. static const std::vector<std::string> jsonSchemaStringFormatBuiltIns{
  429. "date-time", "time", "date", "email", "idn-email", "hostname", "idn-hostname", "ipv4", "ipv6", "uri",
  430. "uri-reference", "iri", "iri-reference", "uri-template", "json-pointer", "relative-json-pointer", "regex"};
  431. if (std::find(jsonSchemaStringFormatBuiltIns.begin(), jsonSchemaStringFormatBuiltIns.end(), format) != jsonSchemaStringFormatBuiltIns.end()) {
  432. throw std::logic_error("JSON schema string format built-in " + format + " not yet supported. " +
  433. "Please open an issue or use a custom format checker.");
  434. }
  435. throw std::logic_error("Don't know how to validate " + format);
  436. }
  437. }
  438. } // namespace json_schema
  439. } // namespace nlohmann