json_schemer 0.2.18 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -7
  3. data/CHANGELOG.md +69 -0
  4. data/Gemfile.lock +28 -10
  5. data/README.md +379 -4
  6. data/bin/hostname_character_classes +42 -0
  7. data/bin/rake +29 -0
  8. data/exe/json_schemer +62 -0
  9. data/json_schemer.gemspec +6 -12
  10. data/lib/json_schemer/cached_resolver.rb +16 -0
  11. data/lib/json_schemer/content.rb +18 -0
  12. data/lib/json_schemer/draft201909/meta.rb +320 -0
  13. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  14. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  15. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  16. data/lib/json_schemer/draft202012/meta.rb +364 -0
  17. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  18. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  19. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  20. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  21. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  22. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  23. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  24. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  25. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  26. data/lib/json_schemer/draft4/meta.rb +161 -0
  27. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  28. data/lib/json_schemer/draft4/vocab.rb +18 -0
  29. data/lib/json_schemer/draft6/meta.rb +172 -0
  30. data/lib/json_schemer/draft6/vocab.rb +16 -0
  31. data/lib/json_schemer/draft7/meta.rb +183 -0
  32. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  33. data/lib/json_schemer/draft7/vocab.rb +30 -0
  34. data/lib/json_schemer/ecma_regexp.rb +51 -0
  35. data/lib/json_schemer/errors.rb +1 -0
  36. data/lib/json_schemer/format/duration.rb +23 -0
  37. data/lib/json_schemer/format/email.rb +56 -0
  38. data/lib/json_schemer/format/hostname.rb +58 -0
  39. data/lib/json_schemer/format/json_pointer.rb +18 -0
  40. data/lib/json_schemer/format/uri_template.rb +34 -0
  41. data/lib/json_schemer/format.rb +129 -109
  42. data/lib/json_schemer/keyword.rb +53 -0
  43. data/lib/json_schemer/location.rb +25 -0
  44. data/lib/json_schemer/openapi.rb +40 -0
  45. data/lib/json_schemer/openapi30/document.rb +1672 -0
  46. data/lib/json_schemer/openapi30/meta.rb +32 -0
  47. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  48. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  49. data/lib/json_schemer/openapi31/document.rb +1557 -0
  50. data/lib/json_schemer/openapi31/meta.rb +136 -0
  51. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  52. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  53. data/lib/json_schemer/output.rb +56 -0
  54. data/lib/json_schemer/result.rb +229 -0
  55. data/lib/json_schemer/schema.rb +423 -0
  56. data/lib/json_schemer/version.rb +1 -1
  57. data/lib/json_schemer.rb +218 -28
  58. metadata +98 -25
  59. data/lib/json_schemer/cached_ref_resolver.rb +0 -14
  60. data/lib/json_schemer/schema/base.rb +0 -658
  61. data/lib/json_schemer/schema/draft4.rb +0 -44
  62. data/lib/json_schemer/schema/draft6.rb +0 -25
  63. data/lib/json_schemer/schema/draft7.rb +0 -32
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Format
4
+ module Hostname
5
+ # https://datatracker.ietf.org/doc/html/rfc5892#section-2.1
6
+ MARKS = '\p{Mn}\p{Mc}'
7
+ LETTER_DIGITS = "\\p{Ll}\\p{Lu}\\p{Lo}\\p{Nd}\\p{Lm}#{MARKS}"
8
+ # https://datatracker.ietf.org/doc/html/rfc5892#section-2.6
9
+ EXCEPTIONS_PVALID = '\u{06FD}\u{06FE}\u{0F0B}\u{3007}' # \u{00DF}\u{03C2} covered by \p{Ll}
10
+ EXCEPTIONS_DISALLOWED = '\u{0640}\u{07FA}\u{302E}\u{302F}\u{3031}\u{3032}\u{3033}\u{3034}\u{3035}\u{303B}'
11
+ LABEL_CHARACTER_CLASS = "[#{LETTER_DIGITS}#{EXCEPTIONS_PVALID}&&[^#{EXCEPTIONS_DISALLOWED}]]"
12
+ # https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.2
13
+ LEADING_CHARACTER_CLASS = "[#{LABEL_CHARACTER_CLASS}&&[^#{MARKS}]]"
14
+ LABEL_REGEX_STRING = "#{LEADING_CHARACTER_CLASS}([#{LABEL_CHARACTER_CLASS}\-]*#{LABEL_CHARACTER_CLASS})?"
15
+ HOSTNAME_REGEX = /\A(#{LABEL_REGEX_STRING}\.)*#{LABEL_REGEX_STRING}\z/i.freeze
16
+ # bin/hostname_character_classes
17
+ VIRAMA_CHARACTER_CLASS = '[\u{094D}\u{09CD}\u{0A4D}\u{0ACD}\u{0B4D}\u{0BCD}\u{0C4D}\u{0CCD}\u{0D3B}\u{0D3C}\u{0D4D}\u{0DCA}\u{0E3A}\u{0EBA}\u{0F84}\u{1039}\u{103A}\u{1714}\u{1715}\u{1734}\u{17D2}\u{1A60}\u{1B44}\u{1BAA}\u{1BAB}\u{1BF2}\u{1BF3}\u{2D7F}\u{A806}\u{A82C}\u{A8C4}\u{A953}\u{A9C0}\u{AAF6}\u{ABED}\u{10A3F}\u{11046}\u{11070}\u{1107F}\u{110B9}\u{11133}\u{11134}\u{111C0}\u{11235}\u{112EA}\u{1134D}\u{11442}\u{114C2}\u{115BF}\u{1163F}\u{116B6}\u{1172B}\u{11839}\u{1193D}\u{1193E}\u{119E0}\u{11A34}\u{11A47}\u{11A99}\u{11C3F}\u{11D44}\u{11D45}\u{11D97}\u{11F41}\u{11F42}]'
18
+ JOINING_TYPE_L_CHARACTER_CLASS = '[\u{A872}\u{10ACD}\u{10AD7}\u{10D00}\u{10FCB}]'
19
+ JOINING_TYPE_D_CHARACTER_CLASS = '[\u{0620}\u{0626}\u{0628}\u{062A}-\u{062E}\u{0633}-\u{063F}\u{0641}-\u{0647}\u{0649}-\u{064A}\u{066E}-\u{066F}\u{0678}-\u{0687}\u{069A}-\u{06BF}\u{06C1}-\u{06C2}\u{06CC}\u{06CE}\u{06D0}-\u{06D1}\u{06FA}-\u{06FC}\u{06FF}\u{0712}-\u{0714}\u{071A}-\u{071D}\u{071F}-\u{0727}\u{0729}\u{072B}\u{072D}-\u{072E}\u{074E}-\u{0758}\u{075C}-\u{076A}\u{076D}-\u{0770}\u{0772}\u{0775}-\u{0777}\u{077A}-\u{077F}\u{07CA}-\u{07EA}\u{0841}-\u{0845}\u{0848}\u{084A}-\u{0853}\u{0855}\u{0860}\u{0862}-\u{0865}\u{0868}\u{0886}\u{0889}-\u{088D}\u{08A0}-\u{08A9}\u{08AF}-\u{08B0}\u{08B3}-\u{08B8}\u{08BA}-\u{08C8}\u{1807}\u{1820}-\u{1842}\u{1843}\u{1844}-\u{1878}\u{1887}-\u{18A8}\u{18AA}\u{A840}-\u{A871}\u{10AC0}-\u{10AC4}\u{10AD3}-\u{10AD6}\u{10AD8}-\u{10ADC}\u{10ADE}-\u{10AE0}\u{10AEB}-\u{10AEE}\u{10B80}\u{10B82}\u{10B86}-\u{10B88}\u{10B8A}-\u{10B8B}\u{10B8D}\u{10B90}\u{10BAD}-\u{10BAE}\u{10D01}-\u{10D21}\u{10D23}\u{10F30}-\u{10F32}\u{10F34}-\u{10F44}\u{10F51}-\u{10F53}\u{10F70}-\u{10F73}\u{10F76}-\u{10F81}\u{10FB0}\u{10FB2}-\u{10FB3}\u{10FB8}\u{10FBB}-\u{10FBC}\u{10FBE}-\u{10FBF}\u{10FC1}\u{10FC4}\u{10FCA}\u{1E900}-\u{1E943}]'
20
+ JOINING_TYPE_T_CHARACTER_CLASS = '[\u{00AD}\u{0300}-\u{036F}\u{0483}-\u{0487}\u{0488}-\u{0489}\u{0591}-\u{05BD}\u{05BF}\u{05C1}-\u{05C2}\u{05C4}-\u{05C5}\u{05C7}\u{0610}-\u{061A}\u{061C}\u{064B}-\u{065F}\u{0670}\u{06D6}-\u{06DC}\u{06DF}-\u{06E4}\u{06E7}-\u{06E8}\u{06EA}-\u{06ED}\u{070F}\u{0711}\u{0730}-\u{074A}\u{07A6}-\u{07B0}\u{07EB}-\u{07F3}\u{07FD}\u{0816}-\u{0819}\u{081B}-\u{0823}\u{0825}-\u{0827}\u{0829}-\u{082D}\u{0859}-\u{085B}\u{0898}-\u{089F}\u{08CA}-\u{08E1}\u{08E3}-\u{0902}\u{093A}\u{093C}\u{0941}-\u{0948}\u{094D}\u{0951}-\u{0957}\u{0962}-\u{0963}\u{0981}\u{09BC}\u{09C1}-\u{09C4}\u{09CD}\u{09E2}-\u{09E3}\u{09FE}\u{0A01}-\u{0A02}\u{0A3C}\u{0A41}-\u{0A42}\u{0A47}-\u{0A48}\u{0A4B}-\u{0A4D}\u{0A51}\u{0A70}-\u{0A71}\u{0A75}\u{0A81}-\u{0A82}\u{0ABC}\u{0AC1}-\u{0AC5}\u{0AC7}-\u{0AC8}\u{0ACD}\u{0AE2}-\u{0AE3}\u{0AFA}-\u{0AFF}\u{0B01}\u{0B3C}\u{0B3F}\u{0B41}-\u{0B44}\u{0B4D}\u{0B55}-\u{0B56}\u{0B62}-\u{0B63}\u{0B82}\u{0BC0}\u{0BCD}\u{0C00}\u{0C04}\u{0C3C}\u{0C3E}-\u{0C40}\u{0C46}-\u{0C48}\u{0C4A}-\u{0C4D}\u{0C55}-\u{0C56}\u{0C62}-\u{0C63}\u{0C81}\u{0CBC}\u{0CBF}\u{0CC6}\u{0CCC}-\u{0CCD}\u{0CE2}-\u{0CE3}\u{0D00}-\u{0D01}\u{0D3B}-\u{0D3C}\u{0D41}-\u{0D44}\u{0D4D}\u{0D62}-\u{0D63}\u{0D81}\u{0DCA}\u{0DD2}-\u{0DD4}\u{0DD6}\u{0E31}\u{0E34}-\u{0E3A}\u{0E47}-\u{0E4E}\u{0EB1}\u{0EB4}-\u{0EBC}\u{0EC8}-\u{0ECE}\u{0F18}-\u{0F19}\u{0F35}\u{0F37}\u{0F39}\u{0F71}-\u{0F7E}\u{0F80}-\u{0F84}\u{0F86}-\u{0F87}\u{0F8D}-\u{0F97}\u{0F99}-\u{0FBC}\u{0FC6}\u{102D}-\u{1030}\u{1032}-\u{1037}\u{1039}-\u{103A}\u{103D}-\u{103E}\u{1058}-\u{1059}\u{105E}-\u{1060}\u{1071}-\u{1074}\u{1082}\u{1085}-\u{1086}\u{108D}\u{109D}\u{135D}-\u{135F}\u{1712}-\u{1714}\u{1732}-\u{1733}\u{1752}-\u{1753}\u{1772}-\u{1773}\u{17B4}-\u{17B5}\u{17B7}-\u{17BD}\u{17C6}\u{17C9}-\u{17D3}\u{17DD}\u{180B}-\u{180D}\u{180F}\u{1885}-\u{1886}\u{18A9}\u{1920}-\u{1922}\u{1927}-\u{1928}\u{1932}\u{1939}-\u{193B}\u{1A17}-\u{1A18}\u{1A1B}\u{1A56}\u{1A58}-\u{1A5E}\u{1A60}\u{1A62}\u{1A65}-\u{1A6C}\u{1A73}-\u{1A7C}\u{1A7F}\u{1AB0}-\u{1ABD}\u{1ABE}\u{1ABF}-\u{1ACE}\u{1B00}-\u{1B03}\u{1B34}\u{1B36}-\u{1B3A}\u{1B3C}\u{1B42}\u{1B6B}-\u{1B73}\u{1B80}-\u{1B81}\u{1BA2}-\u{1BA5}\u{1BA8}-\u{1BA9}\u{1BAB}-\u{1BAD}\u{1BE6}\u{1BE8}-\u{1BE9}\u{1BED}\u{1BEF}-\u{1BF1}\u{1C2C}-\u{1C33}\u{1C36}-\u{1C37}\u{1CD0}-\u{1CD2}\u{1CD4}-\u{1CE0}\u{1CE2}-\u{1CE8}\u{1CED}\u{1CF4}\u{1CF8}-\u{1CF9}\u{1DC0}-\u{1DFF}\u{200B}\u{200E}-\u{200F}\u{202A}-\u{202E}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{20D0}-\u{20DC}\u{20DD}-\u{20E0}\u{20E1}\u{20E2}-\u{20E4}\u{20E5}-\u{20F0}\u{2CEF}-\u{2CF1}\u{2D7F}\u{2DE0}-\u{2DFF}\u{302A}-\u{302D}\u{3099}-\u{309A}\u{A66F}\u{A670}-\u{A672}\u{A674}-\u{A67D}\u{A69E}-\u{A69F}\u{A6F0}-\u{A6F1}\u{A802}\u{A806}\u{A80B}\u{A825}-\u{A826}\u{A82C}\u{A8C4}-\u{A8C5}\u{A8E0}-\u{A8F1}\u{A8FF}\u{A926}-\u{A92D}\u{A947}-\u{A951}\u{A980}-\u{A982}\u{A9B3}\u{A9B6}-\u{A9B9}\u{A9BC}-\u{A9BD}\u{A9E5}\u{AA29}-\u{AA2E}\u{AA31}-\u{AA32}\u{AA35}-\u{AA36}\u{AA43}\u{AA4C}\u{AA7C}\u{AAB0}\u{AAB2}-\u{AAB4}\u{AAB7}-\u{AAB8}\u{AABE}-\u{AABF}\u{AAC1}\u{AAEC}-\u{AAED}\u{AAF6}\u{ABE5}\u{ABE8}\u{ABED}\u{FB1E}\u{FE00}-\u{FE0F}\u{FE20}-\u{FE2F}\u{FEFF}\u{FFF9}-\u{FFFB}\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}-\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}-\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}-\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}-\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}-\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}-\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}-\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}-\u{11301}\u{1133B}-\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}-\u{114C0}\u{114C2}-\u{114C3}\u{115B2}-\u{115B5}\u{115BC}-\u{115BD}\u{115BF}-\u{115C0}\u{115DC}-\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}-\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}-\u{1183A}\u{1193B}-\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}-\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}-\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}-\u{11CB3}\u{11CB5}-\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}-\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}-\u{11D91}\u{11D95}\u{11D97}\u{11EF3}-\u{11EF4}\u{11F00}-\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13430}-\u{1343F}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}-\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D17A}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}-\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]'
21
+ JOINING_TYPE_R_CHARACTER_CLASS = '[\u{0622}-\u{0625}\u{0627}\u{0629}\u{062F}-\u{0632}\u{0648}\u{0671}-\u{0673}\u{0675}-\u{0677}\u{0688}-\u{0699}\u{06C0}\u{06C3}-\u{06CB}\u{06CD}\u{06CF}\u{06D2}-\u{06D3}\u{06D5}\u{06EE}-\u{06EF}\u{0710}\u{0715}-\u{0719}\u{071E}\u{0728}\u{072A}\u{072C}\u{072F}\u{074D}\u{0759}-\u{075B}\u{076B}-\u{076C}\u{0771}\u{0773}-\u{0774}\u{0778}-\u{0779}\u{0840}\u{0846}-\u{0847}\u{0849}\u{0854}\u{0856}-\u{0858}\u{0867}\u{0869}-\u{086A}\u{0870}-\u{0882}\u{088E}\u{08AA}-\u{08AC}\u{08AE}\u{08B1}-\u{08B2}\u{08B9}\u{10AC5}\u{10AC7}\u{10AC9}-\u{10ACA}\u{10ACE}-\u{10AD2}\u{10ADD}\u{10AE1}\u{10AE4}\u{10AEF}\u{10B81}\u{10B83}-\u{10B85}\u{10B89}\u{10B8C}\u{10B8E}-\u{10B8F}\u{10B91}\u{10BA9}-\u{10BAC}\u{10D22}\u{10F33}\u{10F54}\u{10F74}-\u{10F75}\u{10FB4}-\u{10FB6}\u{10FB9}-\u{10FBA}\u{10FBD}\u{10FC2}-\u{10FC3}\u{10FC9}]'
22
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.1
23
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.2
24
+ ZERO_WIDTH_VIRAMA = "#{VIRAMA_CHARACTER_CLASS}[\\u{200C}\\u{200D}]"
25
+ ZERO_WIDTH_NON_JOINER_JOINING_TYPE = "[#{JOINING_TYPE_L_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]#{JOINING_TYPE_T_CHARACTER_CLASS}*\\u{200C}#{JOINING_TYPE_T_CHARACTER_CLASS}*[#{JOINING_TYPE_R_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]"
26
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.3
27
+ MIDDLE_DOT = '\u{006C}\u{00B7}\u{006C}'
28
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.4
29
+ GREEK_LOWER_NUMERAL_SIGN = '\u{0375}\p{Greek}'
30
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.5
31
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.6
32
+ HEBREW_PUNCTUATION = '\p{Hebrew}[\u{05F3}\u{05F4}]'
33
+ CONTEXT_REGEX = /(#{ZERO_WIDTH_VIRAMA}|#{ZERO_WIDTH_NON_JOINER_JOINING_TYPE}|#{MIDDLE_DOT}|#{GREEK_LOWER_NUMERAL_SIGN}|#{HEBREW_PUNCTUATION})/.freeze
34
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.7
35
+ KATAKANA_MIDDLE_DOT_REGEX = /\u{30FB}/.freeze
36
+ KATAKANA_MIDDLE_DOT_CONTEXT_REGEX = /[\p{Hiragana}\p{Katakana}\p{Han}]/.freeze
37
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.8
38
+ # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.9
39
+ ARABIC_INDIC_DIGITS_REGEX = /[\u{0660}-\u{0669}]/.freeze
40
+ ARABIC_EXTENDED_DIGITS_REGEX = /[\u{06F0}-\u{06F9}]/.freeze
41
+
42
+ def valid_hostname?(data)
43
+ data.split('.').map do |a_label|
44
+ return false if a_label.size > 63
45
+ u_label = SimpleIDN.to_unicode(a_label)
46
+ # https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.1
47
+ return false if u_label.slice(2, 2) == '--'
48
+ return false if ARABIC_INDIC_DIGITS_REGEX.match?(u_label) && ARABIC_EXTENDED_DIGITS_REGEX.match?(u_label)
49
+ u_label.gsub!(CONTEXT_REGEX, 'ok')
50
+ u_label.gsub!(KATAKANA_MIDDLE_DOT_REGEX, 'ok') if KATAKANA_MIDDLE_DOT_CONTEXT_REGEX.match?(u_label)
51
+ u_label
52
+ end.join('.').match?(HOSTNAME_REGEX)
53
+ rescue SimpleIDN::ConversionError
54
+ false
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Format
4
+ module JSONPointer
5
+ JSON_POINTER_REGEX_STRING = '(\/([^~\/]|~[01])*)*'
6
+ JSON_POINTER_REGEX = /\A#{JSON_POINTER_REGEX_STRING}\z/.freeze
7
+ RELATIVE_JSON_POINTER_REGEX = /\A(0|[1-9]\d*)(#|#{JSON_POINTER_REGEX_STRING})?\z/.freeze
8
+
9
+ def valid_json_pointer?(data)
10
+ JSON_POINTER_REGEX.match?(data)
11
+ end
12
+
13
+ def valid_relative_json_pointer?(data)
14
+ RELATIVE_JSON_POINTER_REGEX.match?(data)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Format
4
+ module URITemplate
5
+ # https://datatracker.ietf.org/doc/html/rfc6570
6
+ PCT_ENCODED = '%\h{2}' # pct-encoded = "%" HEXDIG HEXDIG
7
+ EXPLODE = '\*' # explode = "*"
8
+ MAX_LENGTH = '[1-9]\d{0,3}' # max-length = %x31-39 0*3DIGIT ; positive integer < 10000
9
+ PREFIX = ":#{MAX_LENGTH}" # prefix = ":" max-length
10
+ MODIFIER_LEVEL4 = "#{PREFIX}|#{EXPLODE}" # modifier-level4 = prefix / explode
11
+ VARCHAR = "(\\w|#{PCT_ENCODED})" # varchar = ALPHA / DIGIT / "_" / pct-encoded
12
+ VARNAME = "#{VARCHAR}(\\.?#{VARCHAR})*" # varname = varchar *( ["."] varchar )
13
+ VARSPEC = "#{VARNAME}(#{MODIFIER_LEVEL4})?" # varspec = varname [ modifier-level4 ]
14
+ VARIABLE_LIST = "#{VARSPEC}(,#{VARSPEC})*" # variable-list = varspec *( "," varspec )
15
+ OPERATOR = '[+#./;?&=,!@|]' # operator = op-level2 / op-level3 / op-reserve
16
+ # op-level2 = "+" / "#"
17
+ # op-level3 = "." / "/" / ";" / "?" / "&"
18
+ # op-reserve = "=" / "," / "!" / "@" / "|"
19
+ EXPRESSION = "{#{OPERATOR}?#{VARIABLE_LIST}}" # expression = "{" [ operator ] variable-list "}"
20
+ LITERALS = "[^\\x00-\\x20\\x7F\"%'<>\\\\^`{|}]|#{PCT_ENCODED}" # literals = %x21 / %x23-24 / %x26 / %x28-3B / %x3D / %x3F-5B
21
+ # / %x5D / %x5F / %x61-7A / %x7E / ucschar / iprivate
22
+ # / pct-encoded
23
+ # ; any Unicode character except: CTL, SP,
24
+ # ; DQUOTE, "'", "%" (aside from pct-encoded),
25
+ # ; "<", ">", "\", "^", "`", "{", "|", "}"
26
+ URI_TEMPLATE = "(#{LITERALS}|#{EXPRESSION})*" # URI-Template = *( literals / expression )
27
+ URI_TEMPLATE_REGEX = /\A#{URI_TEMPLATE}\z/
28
+
29
+ def valid_uri_template?(data)
30
+ URI_TEMPLATE_REGEX.match?(data)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,128 +1,148 @@
1
1
  # frozen_string_literal: true
2
2
  module JSONSchemer
3
3
  module Format
4
- # this is no good
5
- EMAIL_REGEX = /\A[^@\s]+@([\p{L}\d-]+\.)+[\p{L}\d\-]{2,}\z/i.freeze
6
- LABEL_REGEX_STRING = '[\p{L}\p{N}]([\p{L}\p{N}\-]*[\p{L}\p{N}])?'
7
- HOSTNAME_REGEX = /\A(#{LABEL_REGEX_STRING}\.)*#{LABEL_REGEX_STRING}\z/i.freeze
8
- JSON_POINTER_REGEX_STRING = '(\/([^~\/]|~[01])*)*'
9
- JSON_POINTER_REGEX = /\A#{JSON_POINTER_REGEX_STRING}\z/.freeze
10
- RELATIVE_JSON_POINTER_REGEX = /\A(0|[1-9]\d*)(#|#{JSON_POINTER_REGEX_STRING})?\z/.freeze
11
- DATE_TIME_OFFSET_REGEX = /(Z|[\+\-]([01][0-9]|2[0-3]):[0-5][0-9])\z/i.freeze
12
- INVALID_QUERY_REGEX = /[[:space:]]/.freeze
13
-
14
- def valid_spec_format?(data, format)
15
- case format
16
- when 'date-time'
17
- valid_date_time?(data)
18
- when 'date'
19
- valid_date_time?("#{data}T04:05:06.123456789+07:00")
20
- when 'time'
21
- valid_date_time?("2001-02-03T#{data}")
22
- when 'email'
23
- data.ascii_only? && valid_email?(data)
24
- when 'idn-email'
25
- valid_email?(data)
26
- when 'hostname'
27
- data.ascii_only? && valid_hostname?(data)
28
- when 'idn-hostname'
29
- valid_hostname?(data)
30
- when 'ipv4'
31
- valid_ip?(data, :v4)
32
- when 'ipv6'
33
- valid_ip?(data, :v6)
34
- when 'uri'
35
- valid_uri?(data)
36
- when 'uri-reference'
37
- valid_uri_reference?(data)
38
- when 'iri'
39
- valid_uri?(iri_escape(data))
40
- when 'iri-reference'
41
- valid_uri_reference?(iri_escape(data))
42
- when 'uri-template'
43
- valid_uri_template?(data)
44
- when 'json-pointer'
45
- valid_json_pointer?(data)
46
- when 'relative-json-pointer'
47
- valid_relative_json_pointer?(data)
48
- when 'regex'
49
- EcmaReValidator.valid?(data)
50
- end
4
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3
5
+ DATE_TIME = proc do |instance, _format|
6
+ !instance.is_a?(String) || valid_date_time?(instance)
51
7
  end
52
-
53
- def valid_json?(data)
54
- JSON.parse(data)
55
- true
56
- rescue JSON::ParserError
57
- false
8
+ DATE = proc do |instance, _format|
9
+ !instance.is_a?(String) || valid_date_time?("#{instance}T04:05:06.123456789+07:00")
58
10
  end
59
-
60
- def valid_date_time?(data)
61
- DateTime.rfc3339(data)
62
- DATE_TIME_OFFSET_REGEX.match?(data)
63
- rescue ArgumentError => e
64
- false
11
+ TIME = proc do |instance, _format|
12
+ !instance.is_a?(String) || valid_date_time?("2001-02-03T#{instance}")
65
13
  end
66
-
67
- def valid_email?(data)
68
- EMAIL_REGEX.match?(data)
14
+ DURATION = proc do |instance, _format|
15
+ !instance.is_a?(String) || valid_duration?(instance)
69
16
  end
70
-
71
- def valid_hostname?(data)
72
- HOSTNAME_REGEX.match?(data) && data.split('.').all? { |label| label.size <= 63 }
17
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.2
18
+ EMAIL = proc do |instance, _format|
19
+ !instance.is_a?(String) || instance.ascii_only? && valid_email?(instance)
73
20
  end
74
-
75
- def valid_ip?(data, type)
76
- ip_address = IPAddr.new(data)
77
- type == :v4 ? ip_address.ipv4? : ip_address.ipv6?
78
- rescue IPAddr::InvalidAddressError
79
- false
21
+ IDN_EMAIL = proc do |instance, _format|
22
+ !instance.is_a?(String) || valid_email?(instance)
80
23
  end
81
-
82
- def parse_uri_scheme(data)
83
- scheme, _userinfo, _host, _port, _registry, _path, opaque, query, _fragment = URI::RFC3986_PARSER.split(data)
84
- # URI::RFC3986_PARSER.parse allows spaces in these and I don't think it should
85
- raise URI::InvalidURIError if INVALID_QUERY_REGEX.match?(query) || INVALID_QUERY_REGEX.match?(opaque)
86
- scheme
24
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.3
25
+ HOSTNAME = proc do |instance, _format|
26
+ !instance.is_a?(String) || instance.ascii_only? && valid_hostname?(instance)
87
27
  end
88
-
89
- def valid_uri?(data)
90
- !!parse_uri_scheme(data)
91
- rescue URI::InvalidURIError
92
- false
28
+ IDN_HOSTNAME = proc do |instance, _format|
29
+ !instance.is_a?(String) || valid_hostname?(instance)
93
30
  end
94
-
95
- def valid_uri_reference?(data)
96
- parse_uri_scheme(data)
97
- true
98
- rescue URI::InvalidURIError
99
- false
31
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.4
32
+ IPV4 = proc do |instance, _format|
33
+ !instance.is_a?(String) || valid_ip?(instance, Socket::AF_INET)
100
34
  end
101
-
102
- def iri_escape(data)
103
- data.gsub(/[^[:ascii:]]/) do |match|
104
- us = match
105
- tmp = +''
106
- us.each_byte do |uc|
107
- tmp << sprintf('%%%02X', uc)
108
- end
109
- tmp
110
- end.force_encoding(Encoding::US_ASCII)
35
+ IPV6 = proc do |instance, _format|
36
+ !instance.is_a?(String) || valid_ip?(instance, Socket::AF_INET6)
111
37
  end
112
-
113
- def valid_uri_template?(data)
114
- URITemplate.new(data)
115
- true
116
- rescue URITemplate::Invalid
117
- false
38
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.5
39
+ URI = proc do |instance, _format|
40
+ !instance.is_a?(String) || valid_uri?(instance)
118
41
  end
119
-
120
- def valid_json_pointer?(data)
121
- JSON_POINTER_REGEX.match?(data)
42
+ URI_REFERENCE = proc do |instance, _format|
43
+ !instance.is_a?(String) || valid_uri_reference?(instance)
44
+ end
45
+ IRI = proc do |instance, _format|
46
+ !instance.is_a?(String) || valid_uri?(iri_escape(instance))
47
+ end
48
+ IRI_REFERENCE = proc do |instance, _format|
49
+ !instance.is_a?(String) || valid_uri_reference?(iri_escape(instance))
50
+ end
51
+ UUID = proc do |instance, _format|
52
+ !instance.is_a?(String) || valid_uuid?(instance)
53
+ end
54
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.6
55
+ URI_TEMPLATE = proc do |instance, _format|
56
+ !instance.is_a?(String) || valid_uri_template?(instance)
57
+ end
58
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.7
59
+ JSON_POINTER = proc do |instance, _format|
60
+ !instance.is_a?(String) || valid_json_pointer?(instance)
61
+ end
62
+ RELATIVE_JSON_POINTER = proc do |instance, _format|
63
+ !instance.is_a?(String) || valid_relative_json_pointer?(instance)
64
+ end
65
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.8
66
+ REGEX = proc do |instance, _format|
67
+ !instance.is_a?(String) || valid_regex?(instance)
122
68
  end
123
69
 
124
- def valid_relative_json_pointer?(data)
125
- RELATIVE_JSON_POINTER_REGEX.match?(data)
70
+ DATE_TIME_OFFSET_REGEX = /(Z|[\+\-]([01][0-9]|2[0-3]):[0-5][0-9])\z/i.freeze
71
+ DATE_TIME_SEPARATOR_CHARACTER_CLASS = '[Tt\s]'
72
+ HOUR_24_REGEX = /#{DATE_TIME_SEPARATOR_CHARACTER_CLASS}24:/.freeze
73
+ LEAP_SECOND_REGEX = /#{DATE_TIME_SEPARATOR_CHARACTER_CLASS}\d{2}:\d{2}:6/.freeze
74
+ IP_REGEX = /\A[\h:.]+\z/.freeze
75
+ INVALID_QUERY_REGEX = /\s/.freeze
76
+ IRI_ESCAPE_REGEX = /[^[:ascii:]]/
77
+ UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-[89AB]\h{3}-\h{12}\z/i
78
+ NIL_UUID = '00000000-0000-0000-0000-000000000000'
79
+ ASCII_8BIT_TO_PERCENT_ENCODED = 256.times.each_with_object({}) do |byte, out|
80
+ out[-byte.chr] = -sprintf('%%%02X', byte)
81
+ end.freeze
82
+
83
+ class << self
84
+ include Duration
85
+ include Email
86
+ include Hostname
87
+ include JSONPointer
88
+ include URITemplate
89
+
90
+ def percent_encode(data, regexp)
91
+ data = data.dup
92
+ data.force_encoding(Encoding::ASCII_8BIT)
93
+ data.gsub!(regexp, ASCII_8BIT_TO_PERCENT_ENCODED)
94
+ data.force_encoding(Encoding::US_ASCII)
95
+ end
96
+
97
+ def valid_date_time?(data)
98
+ return false if HOUR_24_REGEX.match?(data)
99
+ datetime = DateTime.rfc3339(data)
100
+ return false if LEAP_SECOND_REGEX.match?(data) && datetime.new_offset.strftime('%H:%M') != '23:59'
101
+ DATE_TIME_OFFSET_REGEX.match?(data)
102
+ rescue ArgumentError
103
+ false
104
+ end
105
+
106
+ def valid_ip?(data, family)
107
+ IPAddr.new(data, family)
108
+ IP_REGEX.match?(data)
109
+ rescue IPAddr::Error
110
+ false
111
+ end
112
+
113
+ def parse_uri_scheme(data)
114
+ scheme, _userinfo, _host, _port, _registry, _path, opaque, query, _fragment = ::URI::RFC3986_PARSER.split(data)
115
+ # ::URI::RFC3986_PARSER.parse allows spaces in these and I don't think it should
116
+ raise ::URI::InvalidURIError if INVALID_QUERY_REGEX.match?(query) || INVALID_QUERY_REGEX.match?(opaque)
117
+ scheme
118
+ end
119
+
120
+ def valid_uri?(data)
121
+ !!parse_uri_scheme(data)
122
+ rescue ::URI::InvalidURIError
123
+ false
124
+ end
125
+
126
+ def valid_uri_reference?(data)
127
+ parse_uri_scheme(data)
128
+ true
129
+ rescue ::URI::InvalidURIError
130
+ false
131
+ end
132
+
133
+ def iri_escape(data)
134
+ Format.percent_encode(data, IRI_ESCAPE_REGEX)
135
+ end
136
+
137
+ def valid_regex?(data)
138
+ !!EcmaRegexp.ruby_equivalent(data)
139
+ rescue InvalidEcmaRegexp
140
+ false
141
+ end
142
+
143
+ def valid_uuid?(data)
144
+ UUID_REGEX.match?(data) || NIL_UUID == data
145
+ end
126
146
  end
127
147
  end
128
148
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ class Keyword
4
+ include Output
5
+
6
+ attr_reader :value, :parent, :root, :parsed
7
+
8
+ def initialize(value, parent, keyword, schema = parent)
9
+ @value = value
10
+ @parent = parent
11
+ @root = parent.root
12
+ @keyword = keyword
13
+ @schema = schema
14
+ @parsed = parse
15
+ end
16
+
17
+ def validate(_instance, _instance_location, _keyword_location, _context)
18
+ nil
19
+ end
20
+
21
+ def absolute_keyword_location
22
+ @absolute_keyword_location ||= "#{parent.absolute_keyword_location}/#{fragment_encode(escaped_keyword)}"
23
+ end
24
+
25
+ def schema_pointer
26
+ @schema_pointer ||= "#{parent.schema_pointer}/#{escaped_keyword}"
27
+ end
28
+
29
+ def error_key
30
+ keyword
31
+ end
32
+
33
+ def fetch(key)
34
+ parsed.fetch(parsed.is_a?(Array) ? key.to_i : key)
35
+ end
36
+
37
+ def parsed_schema
38
+ parsed.is_a?(Schema) ? parsed : nil
39
+ end
40
+
41
+ private
42
+
43
+ def parse
44
+ value
45
+ end
46
+
47
+ def subschema(value, keyword = nil, **options)
48
+ options[:base_uri] ||= schema.base_uri
49
+ options[:meta_schema] ||= schema.meta_schema
50
+ Schema.new(value, self, root, keyword, **options)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Location
4
+ JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' }
5
+ JSON_POINTER_TOKEN_ESCAPE_REGEX = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys)
6
+
7
+ class << self
8
+ def root
9
+ {}
10
+ end
11
+
12
+ def join(location, name)
13
+ location[name] ||= { :name => name, :parent => location }
14
+ end
15
+
16
+ def resolve(location)
17
+ location[:resolve] ||= location[:parent] ? "#{resolve(location[:parent])}/#{escape_json_pointer_token(location[:name])}" : ''
18
+ end
19
+
20
+ def escape_json_pointer_token(token)
21
+ token.gsub(JSON_POINTER_TOKEN_ESCAPE_REGEX, JSON_POINTER_TOKEN_ESCAPE_CHARS)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ class OpenAPI
4
+ def initialize(document, **options)
5
+ @document = document
6
+
7
+ version = document['openapi']
8
+ case version
9
+ when /\A3\.1\.\d+\z/
10
+ @document_schema = JSONSchemer.openapi31_document
11
+ json_schema_dialect = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s }
12
+ when /\A3\.0\.\d+\z/
13
+ @document_schema = JSONSchemer.openapi30_document
14
+ json_schema_dialect = OpenAPI30::BASE_URI.to_s
15
+ else
16
+ raise UnsupportedOpenAPIVersion, version
17
+ end
18
+
19
+ meta_schema = META_SCHEMAS_BY_BASE_URI_STR[json_schema_dialect] || raise(UnsupportedMetaSchema, json_schema_dialect)
20
+
21
+ @schema = JSONSchemer.schema(@document, :meta_schema => meta_schema, **options)
22
+ end
23
+
24
+ def valid?
25
+ @document_schema.valid?(@document)
26
+ end
27
+
28
+ def validate(**options)
29
+ @document_schema.validate(@document, **options)
30
+ end
31
+
32
+ def ref(value)
33
+ @schema.ref(value)
34
+ end
35
+
36
+ def schema(name)
37
+ ref("#/components/schemas/#{name}")
38
+ end
39
+ end
40
+ end