Skip to content

Commit 66becfb

Browse files
committed
Escape JSON pointer tokens
When building `data_pointer` and `schema_pointer` in error objects. [JSON Pointer RFC][0]: ``` Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in JSON Pointer, '~' needs to be encoded as '~0' and '/' needs to be encoded as '~1' when these characters appear in a reference token. ``` Fixes: #121 [0]: https://www.rfc-editor.org/rfc/rfc6901#section-3
1 parent a73283a commit 66becfb

2 files changed

Lines changed: 34 additions & 6 deletions

File tree

lib/json_schemer/schema/base.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def merge(
4444
end
4545
end
4646

47+
JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' }
48+
JSON_POINTER_TOKEN_ESCAPE_REGEXP = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys)
49+
4750
def initialize(
4851
schema,
4952
format: true,
@@ -531,7 +534,8 @@ def validate_object(instance, &block)
531534
dependencies.each do |key, value|
532535
next unless data.key?(key)
533536
subschema = value.is_a?(Array) ? { 'required' => value } : value
534-
subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{key}")
537+
escaped_key = escape_json_pointer_token(key)
538+
subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{escaped_key}")
535539
validate_instance(subinstance, &block)
536540
end
537541
end
@@ -545,6 +549,8 @@ def validate_object(instance, &block)
545549

546550
regex_pattern_properties = nil
547551
data.each do |key, value|
552+
escaped_key = escape_json_pointer_token(key)
553+
548554
unless property_names.nil?
549555
subinstance = instance.merge(
550556
data: key,
@@ -559,9 +565,9 @@ def validate_object(instance, &block)
559565
if properties && properties.key?(key)
560566
subinstance = instance.merge(
561567
data: value,
562-
data_pointer: "#{instance.data_pointer}/#{key}",
568+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
563569
schema: properties[key],
564-
schema_pointer: "#{instance.schema_pointer}/properties/#{key}"
570+
schema_pointer: "#{instance.schema_pointer}/properties/#{escaped_key}"
565571
)
566572
validate_instance(subinstance, &block)
567573
matched_key = true
@@ -572,12 +578,13 @@ def validate_object(instance, &block)
572578
[pattern, resolve_regexp(pattern), property_schema]
573579
end
574580
regex_pattern_properties.each do |pattern, regex, property_schema|
581+
escaped_pattern = escape_json_pointer_token(pattern)
575582
if regex.match?(key)
576583
subinstance = instance.merge(
577584
data: value,
578-
data_pointer: "#{instance.data_pointer}/#{key}",
585+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
579586
schema: property_schema,
580-
schema_pointer: "#{instance.schema_pointer}/patternProperties/#{pattern}"
587+
schema_pointer: "#{instance.schema_pointer}/patternProperties/#{escaped_pattern}"
581588
)
582589
validate_instance(subinstance, &block)
583590
matched_key = true
@@ -590,7 +597,7 @@ def validate_object(instance, &block)
590597
unless additional_properties.nil?
591598
subinstance = instance.merge(
592599
data: value,
593-
data_pointer: "#{instance.data_pointer}/#{key}",
600+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
594601
schema: additional_properties,
595602
schema_pointer: "#{instance.schema_pointer}/additionalProperties"
596603
)
@@ -614,6 +621,10 @@ def safe_strict_decode64(data)
614621
nil
615622
end
616623

624+
def escape_json_pointer_token(token)
625+
token.gsub(JSON_POINTER_TOKEN_ESCAPE_REGEXP, JSON_POINTER_TOKEN_ESCAPE_CHARS)
626+
end
627+
617628
def join_uri(a, b)
618629
b = URI.parse(b) if b
619630
if a && b && a.relative? && b.relative?

test/json_schemer_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,4 +1156,21 @@ def test_it_handles_multiple_of_floats
11561156
refute(JSONSchemer.schema({ 'multipleOf' => 0.01 }).valid?(8.666))
11571157
assert(JSONSchemer.schema({ 'multipleOf' => 0.001 }).valid?(8.666))
11581158
end
1159+
1160+
def test_it_escapes_json_pointer_tokens
1161+
schemer = JSONSchemer.schema(
1162+
{
1163+
'type' => 'object',
1164+
'properties' => {
1165+
'foo/bar~' => {
1166+
'type' => 'string'
1167+
}
1168+
}
1169+
}
1170+
)
1171+
errors = schemer.validate({ 'foo/bar~' => 1 }).to_a
1172+
assert_equal(1, errors.size)
1173+
assert_equal('/foo~1bar~0', errors.first.fetch('data_pointer'))
1174+
assert_equal('/properties/foo~1bar~0', errors.first.fetch('schema_pointer'))
1175+
end
11591176
end

0 commit comments

Comments
 (0)