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
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft4
4
+ module Vocab
5
+ ALL = Draft6::Vocab::ALL.dup
6
+ ALL.transform_keys! { |key| key == '$id' ? 'id' : key }
7
+ ALL.delete('contains')
8
+ ALL.delete('propertyNames')
9
+ ALL.delete('const')
10
+ ALL.delete('examples')
11
+ ALL.merge!(
12
+ 'type' => Validation::Type,
13
+ 'exclusiveMaximum' => Validation::ExclusiveMaximum,
14
+ 'exclusiveMinimum' => Validation::ExclusiveMinimum
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft6
4
+ BASE_URI = URI('http://json-schema.org/draft-06/schema#')
5
+ SCHEMA = {
6
+ '$schema' => 'http://json-schema.org/draft-06/schema#',
7
+ '$id' => 'http://json-schema.org/draft-06/schema#',
8
+ 'title' => 'Core schema meta-schema',
9
+ 'definitions' => {
10
+ 'schemaArray' => {
11
+ 'type' => 'array',
12
+ 'minItems' => 1,
13
+ 'items' => { '$ref' => '#' }
14
+ },
15
+ 'nonNegativeInteger' => {
16
+ 'type' => 'integer',
17
+ 'minimum' => 0
18
+ },
19
+ 'nonNegativeIntegerDefault0' => {
20
+ 'allOf' => [
21
+ { '$ref' => '#/definitions/nonNegativeInteger' },
22
+ { 'default' => 0 }
23
+ ]
24
+ },
25
+ 'simpleTypes' => {
26
+ 'enum' => [
27
+ 'array',
28
+ 'boolean',
29
+ 'integer',
30
+ 'null',
31
+ 'number',
32
+ 'object',
33
+ 'string'
34
+ ]
35
+ },
36
+ 'stringArray' => {
37
+ 'type' => 'array',
38
+ 'items' => { 'type' => 'string' },
39
+ 'uniqueItems' => true,
40
+ 'default' => []
41
+ }
42
+ },
43
+ 'type' => ['object', 'boolean'],
44
+ 'properties' => {
45
+ '$id' => {
46
+ 'type' => 'string',
47
+ 'format' => 'uri-reference'
48
+ },
49
+ '$schema' => {
50
+ 'type' => 'string',
51
+ 'format' => 'uri'
52
+ },
53
+ '$ref' => {
54
+ 'type' => 'string',
55
+ 'format' => 'uri-reference'
56
+ },
57
+ 'title' => {
58
+ 'type' => 'string'
59
+ },
60
+ 'description' => {
61
+ 'type' => 'string'
62
+ },
63
+ 'default' => {},
64
+ 'examples' => {
65
+ 'type' => 'array',
66
+ 'items' => {}
67
+ },
68
+ 'multipleOf' => {
69
+ 'type' => 'number',
70
+ 'exclusiveMinimum' => 0
71
+ },
72
+ 'maximum' => {
73
+ 'type' => 'number'
74
+ },
75
+ 'exclusiveMaximum' => {
76
+ 'type' => 'number'
77
+ },
78
+ 'minimum' => {
79
+ 'type' => 'number'
80
+ },
81
+ 'exclusiveMinimum' => {
82
+ 'type' => 'number'
83
+ },
84
+ 'maxLength' => { '$ref' => '#/definitions/nonNegativeInteger' },
85
+ 'minLength' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
86
+ 'pattern' => {
87
+ 'type' => 'string',
88
+ 'format' => 'regex'
89
+ },
90
+ 'additionalItems' => { '$ref' => '#' },
91
+ 'items' => {
92
+ 'anyOf' => [
93
+ { '$ref' => '#' },
94
+ { '$ref' => '#/definitions/schemaArray' }
95
+ ],
96
+ 'default' => {}
97
+ },
98
+ 'maxItems' => { '$ref' => '#/definitions/nonNegativeInteger' },
99
+ 'minItems' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
100
+ 'uniqueItems' => {
101
+ 'type' => 'boolean',
102
+ 'default' => false
103
+ },
104
+ 'contains' => { '$ref' => '#' },
105
+ 'maxProperties' => { '$ref' => '#/definitions/nonNegativeInteger' },
106
+ 'minProperties' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
107
+ 'required' => { '$ref' => '#/definitions/stringArray' },
108
+ 'additionalProperties' => { '$ref' => '#' },
109
+ 'definitions' => {
110
+ 'type' => 'object',
111
+ 'additionalProperties' => { '$ref' => '#' },
112
+ 'default' => {}
113
+ },
114
+ 'properties' => {
115
+ 'type' => 'object',
116
+ 'additionalProperties' => { '$ref' => '#' },
117
+ 'default' => {}
118
+ },
119
+ 'patternProperties' => {
120
+ 'type' => 'object',
121
+ 'additionalProperties' => { '$ref' => '#' },
122
+ 'propertyNames' => { 'format' => 'regex' },
123
+ 'default' => {}
124
+ },
125
+ 'dependencies' => {
126
+ 'type' => 'object',
127
+ 'additionalProperties' => {
128
+ 'anyOf' => [
129
+ { '$ref' => '#' },
130
+ { '$ref' => '#/definitions/stringArray' }
131
+ ]
132
+ }
133
+ },
134
+ 'propertyNames' => { '$ref' => '#' },
135
+ 'const' => {},
136
+ 'enum' => {
137
+ 'type' => 'array',
138
+ 'minItems' => 1,
139
+ 'uniqueItems' => true
140
+ },
141
+ 'type' => {
142
+ 'anyOf' => [
143
+ { '$ref' => '#/definitions/simpleTypes' },
144
+ {
145
+ 'type' => 'array',
146
+ 'items' => { '$ref' => '#/definitions/simpleTypes' },
147
+ 'minItems' => 1,
148
+ 'uniqueItems' => true
149
+ }
150
+ ]
151
+ },
152
+ 'format' => { 'type' => 'string' },
153
+ 'allOf' => { '$ref' => '#/definitions/schemaArray' },
154
+ 'anyOf' => { '$ref' => '#/definitions/schemaArray' },
155
+ 'oneOf' => { '$ref' => '#/definitions/schemaArray' },
156
+ 'not' => { '$ref' => '#' }
157
+ },
158
+ 'default' => {}
159
+ }
160
+ end
161
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft6
4
+ module Vocab
5
+ ALL = Draft7::Vocab::ALL.dup
6
+ ALL.delete('$comment')
7
+ ALL.delete('if')
8
+ ALL.delete('then')
9
+ ALL.delete('else')
10
+ ALL.delete('readOnly')
11
+ ALL.delete('writeOnly')
12
+ ALL.delete('contentMediaType')
13
+ ALL.delete('contentEncoding')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft7
4
+ BASE_URI = URI('http://json-schema.org/draft-07/schema#')
5
+ SCHEMA = {
6
+ '$schema' => 'http://json-schema.org/draft-07/schema#',
7
+ '$id' => 'http://json-schema.org/draft-07/schema#',
8
+ 'title' => 'Core schema meta-schema',
9
+ 'definitions' => {
10
+ 'schemaArray' => {
11
+ 'type' => 'array',
12
+ 'minItems' => 1,
13
+ 'items' => { '$ref' => '#' }
14
+ },
15
+ 'nonNegativeInteger' => {
16
+ 'type' => 'integer',
17
+ 'minimum' => 0
18
+ },
19
+ 'nonNegativeIntegerDefault0' => {
20
+ 'allOf' => [
21
+ { '$ref' => '#/definitions/nonNegativeInteger' },
22
+ { 'default' => 0 }
23
+ ]
24
+ },
25
+ 'simpleTypes' => {
26
+ 'enum' => [
27
+ 'array',
28
+ 'boolean',
29
+ 'integer',
30
+ 'null',
31
+ 'number',
32
+ 'object',
33
+ 'string'
34
+ ]
35
+ },
36
+ 'stringArray' => {
37
+ 'type' => 'array',
38
+ 'items' => { 'type' => 'string' },
39
+ 'uniqueItems' => true,
40
+ 'default' => []
41
+ }
42
+ },
43
+ 'type' => ['object', 'boolean'],
44
+ 'properties' => {
45
+ '$id' => {
46
+ 'type' => 'string',
47
+ 'format' => 'uri-reference'
48
+ },
49
+ '$schema' => {
50
+ 'type' => 'string',
51
+ 'format' => 'uri'
52
+ },
53
+ '$ref' => {
54
+ 'type' => 'string',
55
+ 'format' => 'uri-reference'
56
+ },
57
+ '$comment' => {
58
+ 'type' => 'string'
59
+ },
60
+ 'title' => {
61
+ 'type' => 'string'
62
+ },
63
+ 'description' => {
64
+ 'type' => 'string'
65
+ },
66
+ 'default' => true,
67
+ 'readOnly' => {
68
+ 'type' => 'boolean',
69
+ 'default' => false
70
+ },
71
+ 'writeOnly' => {
72
+ 'type' => 'boolean',
73
+ 'default' => false
74
+ },
75
+ 'examples' => {
76
+ 'type' => 'array',
77
+ 'items' => true
78
+ },
79
+ 'multipleOf' => {
80
+ 'type' => 'number',
81
+ 'exclusiveMinimum' => 0
82
+ },
83
+ 'maximum' => {
84
+ 'type' => 'number'
85
+ },
86
+ 'exclusiveMaximum' => {
87
+ 'type' => 'number'
88
+ },
89
+ 'minimum' => {
90
+ 'type' => 'number'
91
+ },
92
+ 'exclusiveMinimum' => {
93
+ 'type' => 'number'
94
+ },
95
+ 'maxLength' => { '$ref' => '#/definitions/nonNegativeInteger' },
96
+ 'minLength' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
97
+ 'pattern' => {
98
+ 'type' => 'string',
99
+ 'format' => 'regex'
100
+ },
101
+ 'additionalItems' => { '$ref' => '#' },
102
+ 'items' => {
103
+ 'anyOf' => [
104
+ { '$ref' => '#' },
105
+ { '$ref' => '#/definitions/schemaArray' }
106
+ ],
107
+ 'default' => true
108
+ },
109
+ 'maxItems' => { '$ref' => '#/definitions/nonNegativeInteger' },
110
+ 'minItems' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
111
+ 'uniqueItems' => {
112
+ 'type' => 'boolean',
113
+ 'default' => false
114
+ },
115
+ 'contains' => { '$ref' => '#' },
116
+ 'maxProperties' => { '$ref' => '#/definitions/nonNegativeInteger' },
117
+ 'minProperties' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' },
118
+ 'required' => { '$ref' => '#/definitions/stringArray' },
119
+ 'additionalProperties' => { '$ref' => '#' },
120
+ 'definitions' => {
121
+ 'type' => 'object',
122
+ 'additionalProperties' => { '$ref' => '#' },
123
+ 'default' => {}
124
+ },
125
+ 'properties' => {
126
+ 'type' => 'object',
127
+ 'additionalProperties' => { '$ref' => '#' },
128
+ 'default' => {}
129
+ },
130
+ 'patternProperties' => {
131
+ 'type' => 'object',
132
+ 'additionalProperties' => { '$ref' => '#' },
133
+ 'propertyNames' => { 'format' => 'regex' },
134
+ 'default' => {}
135
+ },
136
+ 'dependencies' => {
137
+ 'type' => 'object',
138
+ 'additionalProperties' => {
139
+ 'anyOf' => [
140
+ { '$ref' => '#' },
141
+ { '$ref' => '#/definitions/stringArray' }
142
+ ]
143
+ }
144
+ },
145
+ 'propertyNames' => { '$ref' => '#' },
146
+ 'const' => true,
147
+ 'enum' => {
148
+ 'type' => 'array',
149
+ 'items' => true,
150
+ 'minItems' => 1,
151
+ 'uniqueItems' => true
152
+ },
153
+ 'type' => {
154
+ 'anyOf' => [
155
+ { '$ref' => '#/definitions/simpleTypes' },
156
+ {
157
+ 'type' => 'array',
158
+ 'items' => { '$ref' => '#/definitions/simpleTypes' },
159
+ 'minItems' => 1,
160
+ 'uniqueItems' => true
161
+ }
162
+ ]
163
+ },
164
+ 'format' => { 'type' => 'string' },
165
+ 'contentMediaType' => { 'type' => 'string' },
166
+ 'contentEncoding' => { 'type' => 'string' },
167
+ 'if' => { '$ref' => '#' },
168
+ 'then' => { '$ref' => '#' },
169
+ 'else' => { '$ref' => '#' },
170
+ 'allOf' => { '$ref' => '#/definitions/schemaArray' },
171
+ 'anyOf' => { '$ref' => '#/definitions/schemaArray' },
172
+ 'oneOf' => { '$ref' => '#/definitions/schemaArray' },
173
+ 'not' => { '$ref' => '#' }
174
+ },
175
+ 'default' => true
176
+ }
177
+ end
178
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft7
4
+ module Vocab
5
+ module Validation
6
+ class Ref < Draft202012::Vocab::Core::Ref
7
+ def self.exclusive?
8
+ true
9
+ end
10
+ end
11
+
12
+ class AdditionalItems < Keyword
13
+ def error(formatted_instance_location:, **)
14
+ "array items at #{formatted_instance_location} do not match `additionalItems` schema"
15
+ end
16
+
17
+ def parse
18
+ subschema(value)
19
+ end
20
+
21
+ def validate(instance, instance_location, keyword_location, context)
22
+ items = schema.parsed['items']&.parsed
23
+
24
+ if !instance.is_a?(Array) || !items.is_a?(Array) || items.size >= instance.size
25
+ return result(instance, instance_location, keyword_location, true)
26
+ end
27
+
28
+ offset = items.size
29
+
30
+ nested = instance.slice(offset..-1).map.with_index do |item, index|
31
+ parsed.validate_instance(item, join_location(instance_location, (offset + index).to_s), keyword_location, context)
32
+ end
33
+
34
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?)
35
+ end
36
+ end
37
+
38
+ class ContentEncoding < Keyword
39
+ def error(formatted_instance_location:, **)
40
+ "string at #{formatted_instance_location} could not be decoded using encoding: #{value}"
41
+ end
42
+
43
+ def validate(instance, instance_location, keyword_location, _context)
44
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
45
+
46
+ valid, annotation = Format.decode_content_encoding(instance, value)
47
+
48
+ result(instance, instance_location, keyword_location, valid, :annotation => annotation)
49
+ end
50
+ end
51
+
52
+ class ContentMediaType < Keyword
53
+ def error(formatted_instance_location:, **)
54
+ "string at #{formatted_instance_location} could not be parsed using media type: #{value}"
55
+ end
56
+
57
+ def validate(instance, instance_location, keyword_location, context)
58
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
59
+
60
+ decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance
61
+ valid, annotation = Format.parse_content_media_type(decoded_instance, value)
62
+
63
+ result(instance, instance_location, keyword_location, valid, :annotation => annotation)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft7
4
+ module Vocab
5
+ ALL = Draft201909::Vocab::CORE.dup
6
+ ALL.delete('$recursiveAnchor')
7
+ ALL.delete('$recursiveRef')
8
+ ALL.delete('$vocabulary')
9
+ ALL.delete('$anchor')
10
+ ALL.delete('$defs')
11
+ ALL.merge!(Draft201909::Vocab::APPLICATOR)
12
+ ALL.delete('dependentSchemas')
13
+ ALL.delete('unevaluatedItems')
14
+ ALL.delete('unevaluatedProperties')
15
+ ALL.merge!(Draft201909::Vocab::VALIDATION)
16
+ ALL.delete('dependentRequired')
17
+ ALL.delete('maxContains')
18
+ ALL.delete('minContains')
19
+ ALL.merge!(Draft202012::Vocab::FORMAT_ANNOTATION)
20
+ ALL.merge!(Draft201909::Vocab::META_DATA)
21
+ ALL.delete('deprecated')
22
+ ALL.merge!(
23
+ '$ref' => Validation::Ref,
24
+ 'additionalItems' => Validation::AdditionalItems,
25
+ 'contentEncoding' => Validation::ContentEncoding,
26
+ 'contentMediaType' => Validation::ContentMediaType
27
+ )
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Based on code from @robacarp found in issue 48:
2
3
  # https://github.com/davishmcclurg/json_schemer/issues/48
3
4
  #
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Format
4
+ module Duration
5
+ # https://datatracker.ietf.org/doc/html/rfc3339#appendix-A
6
+ DUR_SECOND = '\d+S' # dur-second = 1*DIGIT "S"
7
+ DUR_MINUTE = "\\d+M(#{DUR_SECOND})?" # dur-minute = 1*DIGIT "M" [dur-second]
8
+ DUR_HOUR = "\\d+H(#{DUR_MINUTE})?" # dur-hour = 1*DIGIT "H" [dur-minute]
9
+ DUR_TIME = "T(#{DUR_HOUR}|#{DUR_MINUTE}|#{DUR_SECOND})" # dur-time = "T" (dur-hour / dur-minute / dur-second)
10
+ DUR_DAY = '\d+D' # dur-day = 1*DIGIT "D"
11
+ DUR_WEEK = '\d+W' # dur-week = 1*DIGIT "W"
12
+ DUR_MONTH = "\\d+M(#{DUR_DAY})?" # dur-month = 1*DIGIT "M" [dur-day]
13
+ DUR_YEAR = "\\d+Y(#{DUR_MONTH})?" # dur-year = 1*DIGIT "Y" [dur-month]
14
+ DUR_DATE = "(#{DUR_DAY}|#{DUR_MONTH}|#{DUR_YEAR})(#{DUR_TIME})?" # dur-date = (dur-day / dur-month / dur-year) [dur-time]
15
+ DURATION = "P(#{DUR_DATE}|#{DUR_TIME}|#{DUR_WEEK})" # duration = "P" (dur-date / dur-time / dur-week)
16
+ DURATION_REGEX = /\A#{DURATION}\z/
17
+
18
+ def valid_duration?(data)
19
+ DURATION_REGEX.match?(data)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Format
4
+ module Email
5
+ # https://datatracker.ietf.org/doc/html/rfc6531#section-3.3
6
+ # I think this is the same as "UTF8-non-ascii"? (https://datatracker.ietf.org/doc/html/rfc6532#section-3.1)
7
+ UTF8_NON_ASCII = '[^[:ascii:]]'
8
+ # https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2
9
+ A_TEXT = "([\\w!#$%&'*+\\-/=?\\^`{|}~]|#{UTF8_NON_ASCII})" # atext = ALPHA / DIGIT / ; Printable US-ASCII
10
+ # "!" / "#" / ; characters not including
11
+ # "$" / "%" / ; specials. Used for atoms.
12
+ # "&" / "'" /
13
+ # "*" / "+" /
14
+ # "-" / "/" /
15
+ # "=" / "?" /
16
+ # "^" / "_" /
17
+ # "`" / "{" /
18
+ # "|" / "}" /
19
+ # "~"
20
+ Q_TEXT_SMTP = "([\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]|#{UTF8_NON_ASCII})" # qtextSMTP = %d32-33 / %d35-91 / %d93-126
21
+ # ; i.e., within a quoted string, any
22
+ # ; ASCII graphic or space is permitted
23
+ # ; without blackslash-quoting except
24
+ # ; double-quote and the backslash itself.
25
+ QUOTED_PAIR_SMTP = '\x5C[\x20-\x7E]' # quoted-pairSMTP = %d92 %d32-126
26
+ # ; i.e., backslash followed by any ASCII
27
+ # ; graphic (including itself) or SPace
28
+ Q_CONTENT_SMTP = "#{Q_TEXT_SMTP}|#{QUOTED_PAIR_SMTP}" # QcontentSMTP = qtextSMTP / quoted-pairSMTP
29
+ QUOTED_STRING = "\"(#{Q_CONTENT_SMTP})*\"" # Quoted-string = DQUOTE *QcontentSMTP DQUOTE
30
+ ATOM = "#{A_TEXT}+" # Atom = 1*atext
31
+ DOT_STRING = "#{ATOM}(\\.#{ATOM})*" # Dot-string = Atom *("." Atom)
32
+ LOCAL_PART = "#{DOT_STRING}|#{QUOTED_STRING}" # Local-part = Dot-string / Quoted-string
33
+ # ; MAY be case-sensitive
34
+ # IPv4-address-literal = Snum 3("." Snum)
35
+ # using `valid_id?` to check ip addresses because it's complicated. # IPv6-address-literal = "IPv6:" IPv6-addr
36
+ ADDRESS_LITERAL = '\[(IPv6:(?<ipv6>[\h:]+)|(?<ipv4>[\d.]+))\]' # address-literal = "[" ( IPv4-address-literal /
37
+ # IPv6-address-literal /
38
+ # General-address-literal ) "]"
39
+ # ; See Section 4.1.3
40
+ # using `valid_hostname?` to check domain because it's complicated
41
+ MAILBOX = "(#{LOCAL_PART})@(#{ADDRESS_LITERAL}|(?<domain>.+))" # Mailbox = Local-part "@" ( Domain / address-literal )
42
+ EMAIL_REGEX = /\A#{MAILBOX}\z/
43
+
44
+ def valid_email?(data)
45
+ return false unless match = EMAIL_REGEX.match(data)
46
+ if ipv4 = match.named_captures.fetch('ipv4')
47
+ valid_ip?(ipv4, Socket::AF_INET)
48
+ elsif ipv6 = match.named_captures.fetch('ipv6')
49
+ valid_ip?(ipv6, Socket::AF_INET6)
50
+ else
51
+ valid_hostname?(match.named_captures.fetch('domain'))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ 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