json_schemer 1.0.2 → 2.0.0

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -6
  3. data/CHANGELOG.md +25 -0
  4. data/Gemfile.lock +2 -2
  5. data/README.md +137 -14
  6. data/json_schemer.gemspec +1 -1
  7. data/lib/json_schemer/draft201909/meta.rb +335 -0
  8. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  9. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  10. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  11. data/lib/json_schemer/draft202012/meta.rb +361 -0
  12. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  13. data/lib/json_schemer/draft202012/vocab/content.rb +44 -0
  14. data/lib/json_schemer/draft202012/vocab/core.rb +154 -0
  15. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +31 -0
  16. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +29 -0
  17. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  18. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  19. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  20. data/lib/json_schemer/draft202012/vocab.rb +103 -0
  21. data/lib/json_schemer/draft4/meta.rb +155 -0
  22. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  23. data/lib/json_schemer/draft4/vocab.rb +18 -0
  24. data/lib/json_schemer/draft6/meta.rb +161 -0
  25. data/lib/json_schemer/draft6/vocab.rb +16 -0
  26. data/lib/json_schemer/draft7/meta.rb +178 -0
  27. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  28. data/lib/json_schemer/draft7/vocab.rb +30 -0
  29. data/lib/json_schemer/errors.rb +1 -0
  30. data/lib/json_schemer/format/duration.rb +23 -0
  31. data/lib/json_schemer/format/email.rb +56 -0
  32. data/lib/json_schemer/format/json_pointer.rb +18 -0
  33. data/lib/json_schemer/format.rb +53 -34
  34. data/lib/json_schemer/keyword.rb +41 -0
  35. data/lib/json_schemer/location.rb +25 -0
  36. data/lib/json_schemer/openapi.rb +40 -0
  37. data/lib/json_schemer/openapi30/document.rb +1673 -0
  38. data/lib/json_schemer/openapi30/meta.rb +26 -0
  39. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  40. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  41. data/lib/json_schemer/openapi31/document.rb +1559 -0
  42. data/lib/json_schemer/openapi31/meta.rb +128 -0
  43. data/lib/json_schemer/openapi31/vocab/base.rb +89 -0
  44. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  45. data/lib/json_schemer/output.rb +55 -0
  46. data/lib/json_schemer/result.rb +168 -0
  47. data/lib/json_schemer/schema.rb +390 -0
  48. data/lib/json_schemer/version.rb +1 -1
  49. data/lib/json_schemer.rb +198 -24
  50. metadata +43 -10
  51. data/lib/json_schemer/schema/base.rb +0 -677
  52. data/lib/json_schemer/schema/draft4.json +0 -149
  53. data/lib/json_schemer/schema/draft4.rb +0 -44
  54. data/lib/json_schemer/schema/draft6.json +0 -155
  55. data/lib/json_schemer/schema/draft6.rb +0 -25
  56. data/lib/json_schemer/schema/draft7.json +0 -172
  57. data/lib/json_schemer/schema/draft7.rb +0 -32
@@ -1,19 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
  module JSONSchemer
3
3
  module Format
4
+ include Duration
5
+ include Email
4
6
  include Hostname
7
+ include JSONPointer
5
8
  include URITemplate
6
9
 
7
- # this is no good
8
- EMAIL_REGEX = /\A[^@\s]+@([\p{L}\d-]+\.)+[\p{L}\d\-]{2,}\z/i.freeze
9
- JSON_POINTER_REGEX_STRING = '(\/([^~\/]|~[01])*)*'
10
- JSON_POINTER_REGEX = /\A#{JSON_POINTER_REGEX_STRING}\z/.freeze
11
- RELATIVE_JSON_POINTER_REGEX = /\A(0|[1-9]\d*)(#|#{JSON_POINTER_REGEX_STRING})?\z/.freeze
12
10
  DATE_TIME_OFFSET_REGEX = /(Z|[\+\-]([01][0-9]|2[0-3]):[0-5][0-9])\z/i.freeze
13
11
  HOUR_24_REGEX = /T24/.freeze
14
12
  LEAP_SECOND_REGEX = /T\d{2}:\d{2}:6/.freeze
15
13
  IP_REGEX = /\A[\h:.]+\z/.freeze
16
14
  INVALID_QUERY_REGEX = /\s/.freeze
15
+ IRI_ESCAPE_REGEX = /[^[:ascii:]]/
16
+ UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-[89AB]\h{3}-\h{12}\z/i
17
+ NIL_UUID = '00000000-0000-0000-0000-000000000000'
18
+ ASCII_8BIT_TO_PERCENT_ENCODED = 256.times.each_with_object({}) do |byte, out|
19
+ out[-byte.chr] = -sprintf('%%%02X', byte)
20
+ end.freeze
21
+
22
+ class << self
23
+ def percent_encode(data, regexp)
24
+ data = data.dup
25
+ data.force_encoding(Encoding::ASCII_8BIT)
26
+ data.gsub!(regexp, ASCII_8BIT_TO_PERCENT_ENCODED)
27
+ data.force_encoding(Encoding::US_ASCII)
28
+ end
29
+
30
+ def decode_content_encoding(data, content_encoding)
31
+ case content_encoding
32
+ when 'base64'
33
+ begin
34
+ [true, Base64.strict_decode64(data)]
35
+ rescue
36
+ [false, nil]
37
+ end
38
+ else
39
+ raise UnknownContentEncoding, content_encoding
40
+ end
41
+ end
42
+
43
+ def parse_content_media_type(data, content_media_type)
44
+ case content_media_type
45
+ when 'application/json'
46
+ begin
47
+ [true, JSON.parse(data)]
48
+ rescue
49
+ [false, nil]
50
+ end
51
+ else
52
+ raise UnknownContentMediaType, content_media_type
53
+ end
54
+ end
55
+ end
17
56
 
18
57
  def valid_spec_format?(data, format)
19
58
  case format
@@ -51,18 +90,15 @@ module JSONSchemer
51
90
  valid_relative_json_pointer?(data)
52
91
  when 'regex'
53
92
  valid_regex?(data)
93
+ when 'duration'
94
+ valid_duration?(data)
95
+ when 'uuid'
96
+ valid_uuid?(data)
54
97
  else
55
98
  raise UnknownFormat, format
56
99
  end
57
100
  end
58
101
 
59
- def valid_json?(data)
60
- JSON.parse(data)
61
- true
62
- rescue JSON::ParserError
63
- false
64
- end
65
-
66
102
  def valid_date_time?(data)
67
103
  return false if HOUR_24_REGEX.match?(data)
68
104
  datetime = DateTime.rfc3339(data)
@@ -72,12 +108,6 @@ module JSONSchemer
72
108
  false
73
109
  end
74
110
 
75
- def valid_email?(data)
76
- return false unless EMAIL_REGEX.match?(data)
77
- local, _domain = data.partition('@')
78
- !local.start_with?('.') && !local.end_with?('.') && !local.include?('..')
79
- end
80
-
81
111
  def valid_ip?(data, family)
82
112
  IPAddr.new(data, family)
83
113
  IP_REGEX.match?(data)
@@ -106,22 +136,7 @@ module JSONSchemer
106
136
  end
107
137
 
108
138
  def iri_escape(data)
109
- data.gsub(/[^[:ascii:]]/) do |match|
110
- us = match
111
- tmp = +''
112
- us.each_byte do |uc|
113
- tmp << sprintf('%%%02X', uc)
114
- end
115
- tmp
116
- end.force_encoding(Encoding::US_ASCII)
117
- end
118
-
119
- def valid_json_pointer?(data)
120
- JSON_POINTER_REGEX.match?(data)
121
- end
122
-
123
- def valid_relative_json_pointer?(data)
124
- RELATIVE_JSON_POINTER_REGEX.match?(data)
139
+ Format.percent_encode(data, IRI_ESCAPE_REGEX)
125
140
  end
126
141
 
127
142
  def valid_regex?(data)
@@ -129,5 +144,9 @@ module JSONSchemer
129
144
  rescue InvalidEcmaRegexp
130
145
  false
131
146
  end
147
+
148
+ def valid_uuid?(data)
149
+ UUID_REGEX.match?(data) || NIL_UUID == data
150
+ end
132
151
  end
133
152
  end
@@ -0,0 +1,41 @@
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
+ private
30
+
31
+ def parse
32
+ value
33
+ end
34
+
35
+ def subschema(value, keyword = nil, **options)
36
+ options[:base_uri] ||= schema.base_uri
37
+ options[:meta_schema] ||= schema.meta_schema
38
+ Schema.new(value, self, root, keyword, **options)
39
+ end
40
+ end
41
+ 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