Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add allPropertiesRequired and noAdditionalProperties options #494

Merged
merged 4 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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