Skip to content

Commit f0ee4e3

Browse files
Merge pull request #148 from davishmcclurg/content
Support custom content encodings and media types
2 parents daf4fce + 3ab85ad commit f0ee4e3

File tree

14 files changed

+168
-36
lines changed

14 files changed

+168
-36
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,37 @@ JSONSchemer.schema(
180180
# default: true
181181
format: true,
182182

183+
# custom formats
184+
formats: {
185+
'int32' => proc do |instance, _format|
186+
instance.is_a?(Integer) && instance.bit_length <= 32
187+
end,
188+
# disable specific format
189+
'email' => false
190+
},
191+
192+
# custom content encodings
193+
# only `base64` is available by default
194+
content_encodings: {
195+
# return [success, annotation] tuple
196+
'urlsafe_base64' => proc do |instance|
197+
[true, Base64.urlsafe_decode64(instance)]
198+
rescue
199+
[false, nil]
200+
end
201+
},
202+
203+
# custom content media types
204+
# only `application/json` is available by default
205+
content_media_types: {
206+
# return [success, annotation] tuple
207+
'text/csv' => proc do |instance|
208+
[true, CSV.parse(instance)]
209+
rescue
210+
[false, nil]
211+
end
212+
},
213+
183214
# insert default property values during validation
184215
# true/false
185216
# default: false

lib/json_schemer.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
require 'json_schemer/format/uri_template'
2121
require 'json_schemer/format/email'
2222
require 'json_schemer/format'
23+
require 'json_schemer/content'
2324
require 'json_schemer/errors'
2425
require 'json_schemer/cached_resolver'
2526
require 'json_schemer/ecma_regexp'
@@ -146,6 +147,8 @@ def draft202012
146147
Draft202012::SCHEMA,
147148
:base_uri => Draft202012::BASE_URI,
148149
:formats => Draft202012::FORMATS,
150+
:content_encodings => Draft202012::CONTENT_ENCODINGS,
151+
:content_media_types => Draft202012::CONTENT_MEDIA_TYPES,
149152
:ref_resolver => Draft202012::Meta::SCHEMAS.to_proc,
150153
:regexp_resolver => 'ecma'
151154
)
@@ -156,6 +159,8 @@ def draft201909
156159
Draft201909::SCHEMA,
157160
:base_uri => Draft201909::BASE_URI,
158161
:formats => Draft201909::FORMATS,
162+
:content_encodings => Draft201909::CONTENT_ENCODINGS,
163+
:content_media_types => Draft201909::CONTENT_MEDIA_TYPES,
159164
:ref_resolver => Draft201909::Meta::SCHEMAS.to_proc,
160165
:regexp_resolver => 'ecma'
161166
)
@@ -167,6 +172,8 @@ def draft7
167172
:vocabulary => { 'json-schemer://draft7' => true },
168173
:base_uri => Draft7::BASE_URI,
169174
:formats => Draft7::FORMATS,
175+
:content_encodings => Draft7::CONTENT_ENCODINGS,
176+
:content_media_types => Draft7::CONTENT_MEDIA_TYPES,
170177
:regexp_resolver => 'ecma'
171178
)
172179
end
@@ -177,6 +184,8 @@ def draft6
177184
:vocabulary => { 'json-schemer://draft6' => true },
178185
:base_uri => Draft6::BASE_URI,
179186
:formats => Draft6::FORMATS,
187+
:content_encodings => Draft6::CONTENT_ENCODINGS,
188+
:content_media_types => Draft6::CONTENT_MEDIA_TYPES,
180189
:regexp_resolver => 'ecma'
181190
)
182191
end
@@ -187,6 +196,8 @@ def draft4
187196
:vocabulary => { 'json-schemer://draft4' => true },
188197
:base_uri => Draft4::BASE_URI,
189198
:formats => Draft4::FORMATS,
199+
:content_encodings => Draft4::CONTENT_ENCODINGS,
200+
:content_media_types => Draft4::CONTENT_MEDIA_TYPES,
190201
:regexp_resolver => 'ecma'
191202
)
192203
end

lib/json_schemer/content.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
module JSONSchemer
3+
module ContentEncoding
4+
BASE64 = proc do |instance|
5+
[true, Base64.strict_decode64(instance)]
6+
rescue
7+
[false, nil]
8+
end
9+
end
10+
11+
module ContentMediaType
12+
JSON = proc do |instance|
13+
[true, ::JSON.parse(instance)]
14+
rescue
15+
[false, nil]
16+
end
17+
end
18+
end

lib/json_schemer/draft201909/meta.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module JSONSchemer
33
module Draft201909
44
BASE_URI = URI('https://json-schema.org/draft/2019-09/schema')
55
FORMATS = Draft202012::FORMATS
6+
CONTENT_ENCODINGS = Draft202012::CONTENT_ENCODINGS
7+
CONTENT_MEDIA_TYPES = Draft202012::CONTENT_MEDIA_TYPES
68
SCHEMA = {
79
'$schema' => 'https://json-schema.org/draft/2019-09/schema',
810
'$id' => 'https://json-schema.org/draft/2019-09/schema',

lib/json_schemer/draft202012/meta.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ module Draft202012
2323
'relative-json-pointer' => Format::RELATIVE_JSON_POINTER,
2424
'regex' => Format::REGEX
2525
}
26+
CONTENT_ENCODINGS = {
27+
'base64' => ContentEncoding::BASE64
28+
}
29+
CONTENT_MEDIA_TYPES = {
30+
'application/json' => ContentMediaType::JSON
31+
}
2632
SCHEMA = {
2733
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
2834
'$id' => 'https://json-schema.org/draft/2020-12/schema',

lib/json_schemer/draft202012/vocab/content.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,29 @@ module Draft202012
44
module Vocab
55
module Content
66
class ContentEncoding < Keyword
7+
def parse
8+
root.fetch_content_encoding(value) { raise UnknownContentEncoding, value }
9+
end
10+
711
def validate(instance, instance_location, keyword_location, _context)
812
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
913

10-
_valid, annotation = Format.decode_content_encoding(instance, value)
14+
_valid, annotation = parsed.call(instance)
1115

1216
result(instance, instance_location, keyword_location, true, :annotation => annotation)
1317
end
1418
end
1519

1620
class ContentMediaType < Keyword
21+
def parse
22+
root.fetch_content_media_type(value) { raise UnknownContentMediaType, value }
23+
end
24+
1725
def validate(instance, instance_location, keyword_location, context)
1826
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
1927

2028
decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance
21-
_valid, annotation = Format.parse_content_media_type(decoded_instance, value)
29+
_valid, annotation = parsed.call(decoded_instance)
2230

2331
result(instance, instance_location, keyword_location, true, :annotation => annotation)
2432
end

lib/json_schemer/draft4/meta.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module Draft4
66
FORMATS.delete('uri-reference')
77
FORMATS.delete('uri-template')
88
FORMATS.delete('json-pointer')
9+
CONTENT_ENCODINGS = Draft6::CONTENT_ENCODINGS
10+
CONTENT_MEDIA_TYPES = Draft6::CONTENT_MEDIA_TYPES
911
SCHEMA = {
1012
'id' => 'http://json-schema.org/draft-04/schema#',
1113
'$schema' => 'http://json-schema.org/draft-04/schema#',

lib/json_schemer/draft6/meta.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Draft6
1111
FORMATS.delete('iri-reference')
1212
FORMATS.delete('relative-json-pointer')
1313
FORMATS.delete('regex')
14+
CONTENT_ENCODINGS = Draft7::CONTENT_ENCODINGS
15+
CONTENT_MEDIA_TYPES = Draft7::CONTENT_MEDIA_TYPES
1416
SCHEMA = {
1517
'$schema' => 'http://json-schema.org/draft-06/schema#',
1618
'$id' => 'http://json-schema.org/draft-06/schema#',

lib/json_schemer/draft7/meta.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module Draft7
55
FORMATS = Draft201909::FORMATS.dup
66
FORMATS.delete('duration')
77
FORMATS.delete('uuid')
8+
CONTENT_ENCODINGS = Draft201909::CONTENT_ENCODINGS
9+
CONTENT_MEDIA_TYPES = Draft201909::CONTENT_MEDIA_TYPES
810
SCHEMA = {
911
'$schema' => 'http://json-schema.org/draft-07/schema#',
1012
'$id' => 'http://json-schema.org/draft-07/schema#',

lib/json_schemer/draft7/vocab/validation.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ def validate(instance, instance_location, keyword_location, context)
3535
end
3636
end
3737

38-
class ContentEncoding < Keyword
38+
class ContentEncoding < Draft202012::Vocab::Content::ContentEncoding
3939
def error(formatted_instance_location:, **)
4040
"string at #{formatted_instance_location} could not be decoded using encoding: #{value}"
4141
end
4242

4343
def validate(instance, instance_location, keyword_location, _context)
4444
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
4545

46-
valid, annotation = Format.decode_content_encoding(instance, value)
46+
valid, annotation = parsed.call(instance)
4747

4848
result(instance, instance_location, keyword_location, valid, :annotation => annotation)
4949
end
5050
end
5151

52-
class ContentMediaType < Keyword
52+
class ContentMediaType < Draft202012::Vocab::Content::ContentMediaType
5353
def error(formatted_instance_location:, **)
5454
"string at #{formatted_instance_location} could not be parsed using media type: #{value}"
5555
end
@@ -58,7 +58,7 @@ def validate(instance, instance_location, keyword_location, context)
5858
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
5959

6060
decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance
61-
valid, annotation = Format.parse_content_media_type(decoded_instance, value)
61+
valid, annotation = parsed.call(decoded_instance)
6262

6363
result(instance, instance_location, keyword_location, valid, :annotation => annotation)
6464
end

0 commit comments

Comments
 (0)