Skip to content

Commit

Permalink
Merge pull request #494 from a-lavis/split_strict_into_two_options
Browse files Browse the repository at this point in the history
Add `allPropertiesRequired` and `noAdditionalProperties` options
  • Loading branch information
ekohl committed May 1, 2023
2 parents f5071e1 + 1b844b9 commit 1b801a3
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 85 deletions.
6 changes: 3 additions & 3 deletions lib/json-schema/attributes/properties.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module JSON
class Schema
class PropertiesAttribute < Attribute
def self.required?(schema, options)
schema.fetch('required') { options[:strict] }
schema.fetch('required') { options[:allPropertiesRequired] }
end

def self.validate(current_schema, data, fragments, processor, validator, options = {})
Expand Down Expand Up @@ -33,8 +33,8 @@ def self.validate(current_schema, data, fragments, processor, validator, options
end
end

# When strict is true, ensure no undefined properties exist in the data
return unless options[:strict] == true && !schema.key?('additionalProperties')
# When noAdditionalProperties is true, ensure no undefined properties exist in the data
return unless options[:noAdditionalProperties] == true && !schema.key?('additionalProperties')

diff = data.select do |k, v|
k = k.to_s
Expand Down
2 changes: 1 addition & 1 deletion lib/json-schema/attributes/properties_v4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class PropertiesV4Attribute < PropertiesAttribute
# draft4 relies on its own RequiredAttribute validation at a higher level, rather than
# as an attribute of individual properties.
def self.required?(schema, options)
options[:strict] == true
options[:allPropertiesRequired] == true
end
end
end
Expand Down
10 changes: 9 additions & 1 deletion lib/json-schema/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class Validator
insert_defaults: false,
clear_cache: false,
strict: false,
allPropertiesRequired: false,
noAdditionalProperties: false,
parse_data: true,
}
@@validators = {}
Expand All @@ -45,7 +47,13 @@ def initialize(schema_data, opts = {})

@validation_options = @options[:record_errors] ? { record_errors: true } : {}
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
@validation_options[:strict] = true if @options[:strict] == true
if @options[:strict] == true
@validation_options[:allPropertiesRequired] = true
@validation_options[:noAdditionalProperties] = true
else
@validation_options[:allPropertiesRequired] = true if @options[:allPropertiesRequired]
@validation_options[:noAdditionalProperties] = true if @options[:noAdditionalProperties]
end
@validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache]

@@mutex.synchronize { @base_schema = initialize_schema(schema_data, configured_validator) }
Expand Down
24 changes: 24 additions & 0 deletions test/draft3_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,28 @@ def test_strict_properties_additional_props

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))
end

def test_strict_properties_pattern_props
Expand All @@ -157,24 +167,38 @@ def test_strict_properties_pattern_props

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 'cheese' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))
end

def test_disallow
Expand Down
195 changes: 115 additions & 80 deletions test/draft4_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,120 @@

require File.expand_path('../support/test_helper', __FILE__)

module StrictValidationV4
def test_strict_properties
schema = {
'$schema' => 'http://json-schema.org/draft-04/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))
end

def test_strict_properties_additional_props
schema = {
'$schema' => 'http://json-schema.org/draft-04/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
'additionalProperties' => { 'type' => 'integer' },
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))
end

def test_strict_properties_pattern_props
schema = {
'$schema' => 'http://json-schema.org/draft-03/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
'patternProperties' => { '\\d+ taco' => { 'type' => 'integer' } },
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))
assert(JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(JSON::Validator.validate(schema, data, noAdditionalProperties: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 'cheese' }
assert(!JSON::Validator.validate(schema, data, strict: true))
assert(!JSON::Validator.validate(schema, data, allPropertiesRequired: true))
assert(!JSON::Validator.validate(schema, data, noAdditionalProperties: true))
end
end

class Draft4Test < Minitest::Test
def validation_errors(schema, data, options)
super(schema, data, version: :draft4)
Expand Down Expand Up @@ -33,6 +147,7 @@ def ipv4_format
include ObjectValidation::PatternPropertiesTests

include StrictValidation
include StrictValidationV4

include StringValidation::ValueTests
include StringValidation::FormatTests
Expand Down Expand Up @@ -85,86 +200,6 @@ def test_max_properties
refute_valid schema, { 'a' => 1, 'b' => 2, 'c' => 3 }
end

def test_strict_properties
schema = {
'$schema' => 'http://json-schema.org/draft-04/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))
end

def test_strict_properties_additional_props
schema = {
'$schema' => 'http://json-schema.org/draft-04/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
'additionalProperties' => { 'type' => 'integer' },
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))
end

def test_strict_properties_pattern_props
schema = {
'$schema' => 'http://json-schema.org/draft-03/schema#',
'properties' => {
'a' => { 'type' => 'string' },
'b' => { 'type' => 'string' },
},
'patternProperties' => { '\\d+ taco' => { 'type' => 'integer' } },
}

data = { 'a' => 'a' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'b' => 'b' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b' }
assert(JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', 'c' => 3 }
assert(!JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 3 }
assert(JSON::Validator.validate(schema, data, strict: true))

data = { 'a' => 'a', 'b' => 'b', '23 taco' => 'cheese' }
assert(!JSON::Validator.validate(schema, data, strict: true))
end

def test_list_option
schema = {
'$schema' => 'http://json-schema.org/draft-04/schema#',
Expand Down
11 changes: 11 additions & 0 deletions test/schema_validation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,15 @@ def test_validate_schema_with_symbol_keys
}
assert(JSON::Validator.validate!(symbolized_schema, data, validate_schema: true))
end

def test_validate_schema_no_additional_properties
errors = JSON::Validator.fully_validate_schema(symbolized_schema, noAdditionalProperties: true)
assert_equal 1, errors.size
assert_match(/the property .* contained undefined properties: .*relationships/i, errors.first)

schema_without_additional_properties = symbolized_schema
schema_without_additional_properties.delete(:relationships)
errors = JSON::Validator.fully_validate_schema(schema_without_additional_properties, noAdditionalProperties: true)
assert_equal 0, errors.size
end
end

0 comments on commit 1b801a3

Please sign in to comment.