Skip to content

Commit

Permalink
Merging custom formats
Browse files Browse the repository at this point in the history
  • Loading branch information
hoxworth committed Oct 26, 2014
2 parents 3adabea + 6a22929 commit 5a663c4
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 71 deletions.
16 changes: 16 additions & 0 deletions lib/json-schema/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ def self.validation_error(processor, message, fragments, current_schema, failed_
def self.validation_errors(validator)
validator.validation_errors
end

TYPE_CLASS_MAPPINGS = {
"string" => String,
"number" => Numeric,
"integer" => Integer,
"boolean" => [TrueClass, FalseClass],
"object" => Hash,
"array" => Array,
"null" => NilClass,
"any" => Object
}

def self.data_valid_for_type?(data, type)
valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
Array(valid_classes).any? { |c| data.is_a?(c) }
end
end
end
end
7 changes: 5 additions & 2 deletions lib/json-schema/attributes/format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ class Schema
class FormatAttribute < Attribute

def self.validate(current_schema, data, fragments, processor, validator, options = {})
validator = validator.formats[current_schema.schema['format'].to_s]
validator.validate(current_schema, data, fragments, processor, validator, options = {}) unless validator.nil?
if self.data_valid_for_type?(data, current_schema.schema['type'])
format = current_schema.schema['format'].to_s
validator = validator.formats[format]
validator.validate(current_schema, data, fragments, processor, validator, options) unless validator.nil?
end
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions lib/json-schema/attributes/formats/custom.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'json-schema/attribute'
require 'json-schema/errors/custom_format_error'

require 'uri'
module JSON
class Schema
class CustomFormat < FormatAttribute
def initialize(validation_proc)
@validation_proc = validation_proc
end

def validate(current_schema, data, fragments, processor, validator, options = {})
begin
@validation_proc.call data
rescue JSON::Schema::CustomFormatError => e
message = "The property '#{self.class.build_fragment(fragments)}' #{e.message}"
self.class.validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
end
end
end
end
end
36 changes: 11 additions & 25 deletions lib/json-schema/attributes/formats/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,21 @@
require 'uri'
module JSON
class Schema
class DateTimeFormat < FormatAttribute
class DateFormat < FormatAttribute
def self.validate(current_schema, data, fragments, processor, validator, options = {})
case current_schema.schema['format']

# Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ with optional decimal fraction of the second
when 'date-time'
if data.is_a?(String)
error_message = "The property '#{build_fragment(fragments)}' must be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss.ssZ"
r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)([\.,]\d+)?(Z|[+-](\d\d)(:?\d\d)?)?$')
if (m = r.match(data))
parts = data.split("T")
begin
Date.parse(parts[0])
rescue Exception
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
end
begin
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59
rescue Exception
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
end
else
if data.is_a?(String)
error_message = "The property '#{build_fragment(fragments)}' must be a date in the format of YYYY-MM-DD"
r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
if (r.match(data))
begin
Date.parse(data)
rescue Exception
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
end
else
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
end
end
end
Expand Down
20 changes: 15 additions & 5 deletions lib/json-schema/attributes/formats/date_time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@
require 'uri'
module JSON
class Schema
class DateFormat < FormatAttribute
class DateTimeFormat < FormatAttribute
def self.validate(current_schema, data, fragments, processor, validator, options = {})
# Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ with optional decimal fraction of the second
if data.is_a?(String)
error_message = "The property '#{build_fragment(fragments)}' must be a date in the format of YYYY-MM-DD"
r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
if (r.match(data))
error_message = "The property '#{build_fragment(fragments)}' must be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss.ssZ"
r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)([\.,]\d+)?(Z|[+-](\d\d)(:?\d\d)?)?$')
if (m = r.match(data))
parts = data.split("T")
begin
Date.parse(data)
Date.parse(parts[0])
rescue Exception
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
end
begin
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59
rescue Exception
validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
return
Expand Down
16 changes: 0 additions & 16 deletions lib/json-schema/attributes/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,6 @@ def self.validate(current_schema, data, fragments, processor, validator, options
end
end

TYPE_CLASS_MAPPINGS = {
"string" => String,
"number" => Numeric,
"integer" => Integer,
"boolean" => [TrueClass, FalseClass],
"object" => Hash,
"array" => Array,
"null" => NilClass,
"any" => Object
}

def self.data_valid_for_type?(data, type)
valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
Array(valid_classes).any? { |c| data.is_a?(c) }
end

# Lookup Schema type of given class instance
def self.type_of_data(data)
type, klass = TYPE_CLASS_MAPPINGS.map { |k,v| [k,v] }.sort_by { |i|
Expand Down
16 changes: 0 additions & 16 deletions lib/json-schema/attributes/type_v4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,6 @@ def self.validate(current_schema, data, fragments, processor, validator, options
end
end
end

TYPE_CLASS_MAPPINGS = {
"string" => String,
"number" => Numeric,
"integer" => Integer,
"boolean" => [TrueClass, FalseClass],
"object" => Hash,
"array" => Array,
"null" => NilClass,
"any" => Object
}

def self.data_valid_for_type?(data, type)
valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
Array(valid_classes).any? { |c| data.is_a?(c) }
end
end
end
end
6 changes: 6 additions & 0 deletions lib/json-schema/errors/custom_format_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module JSON
class Schema
class CustomFormatError < StandardError
end
end
end
3 changes: 3 additions & 0 deletions lib/json-schema/schema/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ module JSON
class Schema
class Validator
attr_accessor :attributes, :formats, :uri, :names, :metaschema
attr_reader :default_formats

def initialize()
@attributes = {}
@formats = {}
@default_formats = {}
@uri = nil
@names = []
@metaschema = ''
Expand Down
32 changes: 29 additions & 3 deletions lib/json-schema/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,7 @@ def validator_for_uri(schema_uri)

def validator_for_name(schema_name)
return default_validator unless schema_name
validator = validators.values.find do |v|
v.names.include?(schema_name.to_s)
end
validator = validators_for_names([schema_name]).first
if validator.nil?
raise JSON::Schema::SchemaError.new("The requested JSON schema version is not supported")
else
Expand All @@ -368,6 +366,25 @@ def register_default_validator(v)
@@default_validator = v
end

def register_format_validator(format, validation_proc, versions = ["draft1", "draft2", "draft3", "draft4"])
custom_format_validator = JSON::Schema::CustomFormat.new(validation_proc)
validators_for_names(versions).each do |validator|
validator.formats[format.to_s] = custom_format_validator
end
end

def deregister_format_validator(format, versions = ["draft1", "draft2", "draft3", "draft4"])
validators_for_names(versions).each do |validator|
validator.formats[format.to_s] = validator.default_formats[format.to_s]
end
end

def restore_default_formats(versions = ["draft1", "draft2", "draft3", "draft4"])
validators_for_names(versions).each do |validator|
validator.formats = validator.default_formats.clone
end
end

def json_backend
if defined?(MultiJson)
MultiJson.respond_to?(:adapter) ? MultiJson.adapter : MultiJson.engine
Expand Down Expand Up @@ -448,6 +465,15 @@ def parse(s)
}
end
end

private

def validators_for_names(names)
names.map! { |name| name.to_s }
validators.reduce([]) do |memo, (_, validator)|
memo.tap { |m| m << validator if (validator.names & names).any? }
end
end
end

private
Expand Down
3 changes: 2 additions & 1 deletion lib/json-schema/validators/draft1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ def initialize
"items" => JSON::Schema::ItemsAttribute,
"extends" => JSON::Schema::ExtendsAttribute
}
@formats = {
@default_formats = {
'date-time' => DateTimeFormat,
'date' => DateFormat,
'time' => TimeFormat,
'ip-address' => IP4Format,
'ipv6' => IP6Format,
'uri' => UriFormat
}
@formats = @default_formats.clone
@uri = URI.parse("http://json-schema.org/draft-01/schema#")
@names = ["draft1"]
@metaschema = File.join("resources", "draft-01.json")
Expand Down
3 changes: 2 additions & 1 deletion lib/json-schema/validators/draft2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ def initialize
"items" => JSON::Schema::ItemsAttribute,
"extends" => JSON::Schema::ExtendsAttribute
}
@formats = {
@default_formats = {
'date-time' => DateTimeFormat,
'date' => DateFormat,
'time' => TimeFormat,
'ip-address' => IP4Format,
'ipv6' => IP6Format,
'uri' => UriFormat
}
@formats = @default_formats.clone
@uri = URI.parse("http://json-schema.org/draft-02/schema#")
@names = ["draft2"]
@metaschema = File.join("resources", "draft-02.json")
Expand Down
3 changes: 2 additions & 1 deletion lib/json-schema/validators/draft3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ def initialize
"extends" => JSON::Schema::ExtendsAttribute,
"$ref" => JSON::Schema::RefAttribute
}
@formats = {
@default_formats = {
'date-time' => DateTimeFormat,
'date' => DateFormat,
'ip-address' => IP4Format,
'ipv6' => IP6Format,
'time' => TimeFormat,
'uri' => UriFormat
}
@formats = @default_formats.clone
@uri = URI.parse("http://json-schema.org/draft-03/schema#")
@names = ["draft3", "http://json-schema.org/draft-03/schema#"]
@metaschema = File.join("resources", "draft-03.json")
Expand Down
3 changes: 2 additions & 1 deletion lib/json-schema/validators/draft4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ def initialize
"extends" => JSON::Schema::ExtendsAttribute,
"$ref" => JSON::Schema::RefAttribute
}
@formats = {
@default_formats = {
'date-time' => DateTimeFormat,
'ipv4' => IP4Format,
'ipv6' => IP6Format,
'uri' => UriFormat
}
@formats = @default_formats.clone
@uri = URI.parse("http://json-schema.org/draft-04/schema#")
@names = ["draft4", "http://json-schema.org/draft-04/schema#"]
@metaschema = File.join("resources", "draft-04.json")
Expand Down
Loading

0 comments on commit 5a663c4

Please sign in to comment.