module Recurly::Schema::SchemaValidator
This module is responsible for validating that the raw data passed in to attributes matches the schema belonging to this class. It should be mixed in to the Request
class.
Public Instance Methods
get_did_you_mean(schema, misspelled_attr)
click to toggle source
Gets the closest term to the misspelled attribute
# File lib/recurly/schema/schema_validator.rb, line 61 def get_did_you_mean(schema, misspelled_attr) closest = schema.attributes.keys.sort_by do |v| levenshtein_distance(v, misspelled_attr) end.first if closest && levenshtein_distance(closest, misspelled_attr) <= 4 closest end end
validate!()
click to toggle source
Validates the attributes and throws an error if something is wrong.
@example
Recurly::Requests::PlanCreate.new(code: 'plan123').validate! #=> {:code=>"plan123"}
@example
Recurly::Requests::PlanCreate.new(code: 3.14).validate! #=> ArgumentError: Attribute 'code' on the resource Recurly::Requests::PlanCreate is type Float but should be a String.
@example
Recurly::Requests::PlanCreate.new(kode: 'plan123').validate! #=> ArgumentError: Attribute 'kode' does not exist on request Recurly::Requests::PlanCreate. Did you mean 'code'?
@raise [ArgumentError] if the attribute data does not match the schema.
# File lib/recurly/schema/schema_validator.rb, line 20 def validate! attributes.each do |attr_name, val| schema_attr = schema.get_attribute(attr_name) if schema_attr.nil? err_msg = "Attribute '#{attr_name}' does not exist on request #{self.class.name}." if did_you_mean = get_did_you_mean(schema, attr_name) err_msg << " Did you mean '#{did_you_mean}'?" end raise ArgumentError, err_msg else validate_attribute!(attr_name, schema_attr, val) end end end
validate_attribute!(name, schema_attr, val)
click to toggle source
Validates an individual attribute
# File lib/recurly/schema/schema_validator.rb, line 36 def validate_attribute!(name, schema_attr, val) unless val.nil? || schema_attr.is_valid?(val) # If it's safely castable, the json deserializer or server # will take care of it for us unless safely_castable?(val.class, schema_attr.type) expected = case schema_attr when Schema::ArrayAttribute "Array of #{schema_attr.type}s" else schema_attr.type end raise ArgumentError, "Attribute '#{name}' on the resource #{self.class.name} is type #{val.class} but should be a #{expected}" end end # This is the convention for a recurly object if schema_attr.is_a?(Schema::ResourceAttribute) && val.is_a?(Hash) # Using send because the initializer may be private instance = schema_attr.recurly_class.send(:new, val) instance.validate! end end
Private Instance Methods
levenshtein_distance(str1, str2)
click to toggle source
This code is copied directly from the did_you mean gem which is based directly on the Text gem implementation.
did_you_mean: Copyright © 2014-2016 Yuki Nishijima. Text: Copyright © 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
Returns a value representing the “cost” of transforming str1 into str2
# File lib/recurly/schema/schema_validator.rb, line 99 def levenshtein_distance(str1, str2) str1 = str1.to_s unless str1.is_a? String str2 = str2.to_s unless str2.is_a? String n = str1.length m = str2.length return m if n.zero? return n if m.zero? d = (0..m).to_a x = nil # to avoid duplicating an enumerable object, create it outside of the loop str2_codepoints = str2.codepoints str1.each_codepoint.with_index(1) do |char1, i| j = 0 while j < m cost = (char1 == str2_codepoints[j]) ? 0 : 1 x = min3( d[j + 1] + 1, # insertion i + 1, # deletion d[j] + cost # substitution ) d[j] = i i = x j += 1 end d[m] = x end x end
min3(a, b, c)
click to toggle source
# File lib/recurly/schema/schema_validator.rb, line 133 def min3(a, b, c) if a < b && a < c a elsif b < c b else c end end
safely_castable?(from_type, to_type)
click to toggle source
# File lib/recurly/schema/schema_validator.rb, line 73 def safely_castable?(from_type, to_type) # TODO we can drop this switch when 2.3 support is dropped int_class = if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.4.0") :Integer else :Fixnum end int_class = Kernel.const_get(int_class) case [from_type, to_type] when [Symbol, String] true when [int_class, Float] true else false end end