parameters_schema 0.42 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +3 -3
- data/README.md +286 -286
- data/Rakefile +7 -7
- data/lib/parameters_schema/core_ext.rb +21 -21
- data/lib/parameters_schema/exceptions.rb +23 -23
- data/lib/parameters_schema/options.rb +79 -79
- data/lib/parameters_schema/schema.rb +295 -295
- data/lib/parameters_schema.rb +8 -8
- data/test/helpers.rb +56 -56
- data/test/test_options.rb +74 -74
- data/test/test_schema_allow.rb +114 -114
- data/test/test_schema_allow_empty.rb +29 -29
- data/test/test_schema_allow_nil.rb +33 -33
- data/test/test_schema_hash.rb +90 -90
- data/test/test_schema_required.rb +74 -74
- data/test/test_schema_simple.rb +58 -58
- data/test/test_schema_types.rb +452 -452
- data/test/test_schema_types_complex.rb +126 -126
- metadata +9 -10
| @@ -1,296 +1,296 @@ | |
| 1 | 
            -
            module ParametersSchema
         | 
| 2 | 
            -
              class Schema
         | 
| 3 | 
            -
                def initialize(&schema)
         | 
| 4 | 
            -
                  @schema = schema
         | 
| 5 | 
            -
                end
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                def validate!(params)
         | 
| 8 | 
            -
                  # Make sure we have params we can work with.
         | 
| 9 | 
            -
                  @params = __prepare_params(params)
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                  # Parse and validate each param.
         | 
| 12 | 
            -
                  @sanitized_params = []
         | 
| 13 | 
            -
                  instance_eval(&@schema)
         | 
| 14 | 
            -
                  # Serve the params if valid, otherwise throw exception.
         | 
| 15 | 
            -
                  __handle_errors
         | 
| 16 | 
            -
                  __serve
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                private
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                def param(name, options = {}, &inner_params)
         | 
| 22 | 
            -
                  options[:required] = !options.has_key?(:required) || options[:required].present?
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  options[:type] = [options[:type] || String].flatten
         | 
| 25 | 
            -
                  options[:allow] = [options[:allow].present? ? options[:allow] : ParametersSchema::Options.any_keyword].flatten
         | 
| 26 | 
            -
                  options[:deny] = [options[:deny].present? ? options[:deny] : ParametersSchema::Options.none_keyword].flatten
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  [ParametersSchema::Options.any_keyword, ParametersSchema::Options.none_keyword].each do |dominant_value|
         | 
| 29 | 
            -
                    [:allow, :deny, :type].each do |key|
         | 
| 30 | 
            -
                      options[key] = [dominant_value] if options[key].include?(dominant_value)
         | 
| 31 | 
            -
                    end
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  options[:array] = options[:array] || false
         | 
| 35 | 
            -
                  options[:parent] = options[:parent] || @params
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                  options[:type].map! do |type|
         | 
| 38 | 
            -
                    # Limit to { key => value }
         | 
| 39 | 
            -
                    if type.kind_of?(Hash) && type.count > 1
         | 
| 40 | 
            -
                      type = { type.first[0] => type.first[1] }
         | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                    # Limit to { Array => value }
         | 
| 44 | 
            -
                    if type.kind_of?(Hash) && type.first[0] != Array
         | 
| 45 | 
            -
                      type = type.first[0]
         | 
| 46 | 
            -
                    end
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                    # Apply :array keyword if not already in the format { Array => value }
         | 
| 49 | 
            -
                    if options.delete(:array) && !type.kind_of?(Hash)
         | 
| 50 | 
            -
                      type = { Array => type }
         | 
| 51 | 
            -
                    end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                    # The format...
         | 
| 54 | 
            -
                    #
         | 
| 55 | 
            -
                    #   param :potatoe do
         | 
| 56 | 
            -
                    #     ...
         | 
| 57 | 
            -
                    #   end
         | 
| 58 | 
            -
                    #
         | 
| 59 | 
            -
                    # ... is always an Hash.
         | 
| 60 | 
            -
                    if type.kind_of?(Hash) && inner_params.present?
         | 
| 61 | 
            -
                      type = { Array => Hash }
         | 
| 62 | 
            -
                    elsif inner_params.present?
         | 
| 63 | 
            -
                      type = Hash
         | 
| 64 | 
            -
                    end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                    type
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                  @sanitized_params.push(__validate_param(name, options, inner_params))
         | 
| 70 | 
            -
                end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                def __prepare_params(params)
         | 
| 73 | 
            -
                  params ||= {}
         | 
| 74 | 
            -
                  params = {} unless params.kind_of?(Hash)
         | 
| 75 | 
            -
                  params = params.clone.with_indifferent_access
         | 
| 76 | 
            -
                  ParametersSchema::Options.skip_parameters.each{ |param| params.delete(param) }
         | 
| 77 | 
            -
                  params
         | 
| 78 | 
            -
                end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                def __validate_param(name, options, inner_params)
         | 
| 81 | 
            -
                  # Validate the presence of the parameter.
         | 
| 82 | 
            -
                  value, error = __validate_param_presence(name, options)
         | 
| 83 | 
            -
                  return __stop_validation(name, value, error, options) if error || (!options[:required] && value.nil?)
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                  # Validate nil value.
         | 
| 86 | 
            -
                  value, error = __validate_param_value_nil(value, options)
         | 
| 87 | 
            -
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                  # Validate empty value (except hash).
         | 
| 90 | 
            -
                  value, error = __validate_param_value_empty(value, options)
         | 
| 91 | 
            -
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                  # Validate the type of the parameter.
         | 
| 94 | 
            -
                  [options[:type]].flatten.each do |type|
         | 
| 95 | 
            -
                    value, error = __validate_type_and_cast(value, type, options, inner_params)
         | 
| 96 | 
            -
                    break if error.blank?
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                  # Validate the allowed and denied values of the parameter
         | 
| 101 | 
            -
                  unless value.kind_of?(Array) || value.kind_of?(Hash)
         | 
| 102 | 
            -
                    [:allow, :deny].each do |allow_or_deny|
         | 
| 103 | 
            -
                      value, error = __validate_param_value_format(value, options, allow_or_deny)
         | 
| 104 | 
            -
                      return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 105 | 
            -
                    end
         | 
| 106 | 
            -
                  end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                  # Validate empty value for hash.
         | 
| 109 | 
            -
                  # This is done at this point to let the validation emit errors when inner parameters are missing.
         | 
| 110 | 
            -
                  # It is preferable that { key: {} } emit { key: { name: :missing } } than { key: :empty }.
         | 
| 111 | 
            -
                  value, error = __validate_param_value_hash_empty(value, options)
         | 
| 112 | 
            -
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                  __stop_validation(name, value, error, options)
         | 
| 115 | 
            -
                end
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                def __validate_param_presence(name, options)
         | 
| 118 | 
            -
                  error = nil
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                  if options[:required] && !options[:parent].has_key?(name)
         | 
| 121 | 
            -
                    error = ParametersSchema::ErrorCode::MISSING
         | 
| 122 | 
            -
                  elsif options[:parent].has_key?(name)
         | 
| 123 | 
            -
                    value = options[:parent][name]
         | 
| 124 | 
            -
                  end
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                  [value, error]
         | 
| 127 | 
            -
                end
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                def __validate_param_value_nil(value, options)
         | 
| 130 | 
            -
                  error = nil
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                  if !options[:allow].include?(ParametersSchema::Options.nil_keyword) && value.nil?
         | 
| 133 | 
            -
                    error = ParametersSchema::ErrorCode::NIL
         | 
| 134 | 
            -
                  end
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                  [value, error]
         | 
| 137 | 
            -
                end
         | 
| 138 | 
            -
             | 
| 139 | 
            -
                def __validate_param_value_empty(value, options)
         | 
| 140 | 
            -
                  error = nil
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                  if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && !value.kind_of?(Hash) && value.respond_to?(:empty?) && value.empty?
         | 
| 143 | 
            -
                    error = ParametersSchema::ErrorCode::EMPTY
         | 
| 144 | 
            -
                  end
         | 
| 145 | 
            -
             | 
| 146 | 
            -
                  [value, error]
         | 
| 147 | 
            -
                end
         | 
| 148 | 
            -
             | 
| 149 | 
            -
                def __validate_param_value_hash_empty(value, options)
         | 
| 150 | 
            -
                  error = nil
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                  if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && value.kind_of?(Hash) && value.empty?
         | 
| 153 | 
            -
                    error = ParametersSchema::ErrorCode::EMPTY
         | 
| 154 | 
            -
                  end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                  [value, error]
         | 
| 157 | 
            -
                end
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                def __validate_param_value_format(value, options, allow_or_deny)
         | 
| 160 | 
            -
                  conditions = options[allow_or_deny] - [ParametersSchema::Options.empty_keyword, ParametersSchema::Options.nil_keyword]
         | 
| 161 | 
            -
                  inverse = allow_or_deny == :deny
         | 
| 162 | 
            -
                  accept_all_keyword = inverse ? ParametersSchema::Options.none_keyword : ParametersSchema::Options.any_keyword
         | 
| 163 | 
            -
                  refuse_all_keyword = inverse ? ParametersSchema::Options.any_keyword : ParametersSchema::Options.none_keyword
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                  return [value, nil] if conditions.include?(accept_all_keyword)
         | 
| 166 | 
            -
                  return [value, ParametersSchema::ErrorCode::DISALLOWED] if conditions.include?(refuse_all_keyword)
         | 
| 167 | 
            -
             | 
| 168 | 
            -
                  error = nil
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  conditions.each do |condition|
         | 
| 171 | 
            -
                    error = nil
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                    if condition.kind_of?(Range)
         | 
| 174 | 
            -
                      condition_passed = condition.include?(value)
         | 
| 175 | 
            -
                      condition_passed = !condition_passed if inverse
         | 
| 176 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 177 | 
            -
                    elsif condition.kind_of?(Regexp) && !value.kind_of?(String)
         | 
| 178 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 179 | 
            -
                    elsif condition.kind_of?(Regexp) && value.kind_of?(String)
         | 
| 180 | 
            -
                      condition_passed = (condition =~ value).present?
         | 
| 181 | 
            -
                      condition_passed = !condition_passed if inverse
         | 
| 182 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 183 | 
            -
                    else
         | 
| 184 | 
            -
                      condition_passed = condition == value
         | 
| 185 | 
            -
                      condition_passed = !condition_passed if inverse
         | 
| 186 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 187 | 
            -
                    end
         | 
| 188 | 
            -
             | 
| 189 | 
            -
                    break if inverse ? error.present? : error.blank?
         | 
| 190 | 
            -
                  end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                  [value, error]
         | 
| 193 | 
            -
                end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                def __validate_type_and_cast(value, type, options, inner_params)
         | 
| 196 | 
            -
                  if type.kind_of?(Hash)
         | 
| 197 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(Array)
         | 
| 198 | 
            -
                    value, error = __validate_array(value, type.values.first, options, inner_params) unless error
         | 
| 199 | 
            -
                  elsif inner_params.present?
         | 
| 200 | 
            -
                    begin
         | 
| 201 | 
            -
                      inner_schema = ParametersSchema::Schema.new(&inner_params)
         | 
| 202 | 
            -
                      value = inner_schema.validate!(value)
         | 
| 203 | 
            -
                    rescue ParametersSchema::InvalidParameters => e
         | 
| 204 | 
            -
                      error = e.errors
         | 
| 205 | 
            -
                    end
         | 
| 206 | 
            -
                  elsif type == ParametersSchema::Options.boolean_keyword
         | 
| 207 | 
            -
                    value = true if ParametersSchema::Options.boolean_true_values.include?(value.kind_of?(String) ? value.downcase : value)
         | 
| 208 | 
            -
                    value = false if ParametersSchema::Options.boolean_false_values.include?(value.kind_of?(String) ? value.downcase : value)
         | 
| 209 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(TrueClass) && !value.kind_of?(FalseClass)
         | 
| 210 | 
            -
                  elsif type == Fixnum
         | 
| 211 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
         | 
| 212 | 
            -
                    value = value.to_i if error.blank? # cast to right type.
         | 
| 213 | 
            -
                  elsif type == Float
         | 
| 214 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
         | 
| 215 | 
            -
                    value = value.to_f if error.blank? # cast to right type.
         | 
| 216 | 
            -
                  elsif type == Regexp
         | 
| 217 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value =~ options[:regex]
         | 
| 218 | 
            -
                  elsif type == ParametersSchema::Options.any_keyword
         | 
| 219 | 
            -
                    # No validation required.
         | 
| 220 | 
            -
                  elsif type == ParametersSchema::Options.none_keyword
         | 
| 221 | 
            -
                    # Always fail. Why would you want to do that?
         | 
| 222 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 223 | 
            -
                  elsif type == String
         | 
| 224 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value.kind_of?(String) || value.kind_of?(Symbol)
         | 
| 225 | 
            -
                    value = value.to_s if error.blank? # cast to right type.
         | 
| 226 | 
            -
                  elsif type == Symbol
         | 
| 227 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value.respond_to?(:to_sym)
         | 
| 228 | 
            -
                    value = value.to_sym if error.blank? # cast to right type.
         | 
| 229 | 
            -
                  elsif type == Date
         | 
| 230 | 
            -
                    begin 
         | 
| 231 | 
            -
                      value = value.kind_of?(String) ? Date.parse(value) : value.to_date
         | 
| 232 | 
            -
                    rescue
         | 
| 233 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 234 | 
            -
                    end
         | 
| 235 | 
            -
                  elsif type == DateTime
         | 
| 236 | 
            -
                    begin 
         | 
| 237 | 
            -
                      value = value.kind_of?(String) ? DateTime.parse(value) : value.to_datetime
         | 
| 238 | 
            -
                    rescue
         | 
| 239 | 
            -
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 240 | 
            -
                    end
         | 
| 241 | 
            -
                  else
         | 
| 242 | 
            -
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(type)
         | 
| 243 | 
            -
                  end
         | 
| 244 | 
            -
             | 
| 245 | 
            -
                  [value, error]
         | 
| 246 | 
            -
                end
         | 
| 247 | 
            -
             | 
| 248 | 
            -
                def __validate_array(value, type, options, inner_params)
         | 
| 249 | 
            -
                  if !value.kind_of?(Array)
         | 
| 250 | 
            -
                    return [value, ParametersSchema::ErrorCode::DISALLOWED]
         | 
| 251 | 
            -
                  end
         | 
| 252 | 
            -
             | 
| 253 | 
            -
                  value_opts = {
         | 
| 254 | 
            -
                    required: true,
         | 
| 255 | 
            -
                    type: type,
         | 
| 256 | 
            -
                    parent: { value: nil },
         | 
| 257 | 
            -
                    allow: options[:allow],
         | 
| 258 | 
            -
                    deny: options[:deny]
         | 
| 259 | 
            -
                  }
         | 
| 260 | 
            -
             | 
| 261 | 
            -
                  value.map! do |v|
         | 
| 262 | 
            -
                    value_opts[:parent][:value] = v
         | 
| 263 | 
            -
                    __validate_param(:value, value_opts, inner_params)
         | 
| 264 | 
            -
                  end
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                  # For now, take the first error.
         | 
| 267 | 
            -
                  [value.map{ |v| v[:value] }, value.find{ |v| v[:error].present? }.try(:[], :error)]
         | 
| 268 | 
            -
                end
         | 
| 269 | 
            -
             | 
| 270 | 
            -
                def __stop_validation(name, value, error, options)
         | 
| 271 | 
            -
                  { param: name, error: error, value: value, keep_if_nil: options[:allow].include?(ParametersSchema::Options.nil_keyword) }
         | 
| 272 | 
            -
                end
         | 
| 273 | 
            -
             | 
| 274 | 
            -
                def __handle_errors
         | 
| 275 | 
            -
                  errors = @sanitized_params
         | 
| 276 | 
            -
                    .select{ |p| p[:error].present? }
         | 
| 277 | 
            -
                    .each_with_object({}.with_indifferent_access) do |p, h|
         | 
| 278 | 
            -
                      h[p[:param]] = p[:error] == :nested_errors ? p[:value] : p[:error]
         | 
| 279 | 
            -
                    end
         | 
| 280 | 
            -
             | 
| 281 | 
            -
                  (@params.keys.map(&:to_sym) - @sanitized_params.map{ |p| p[:param] }).each do |extra_param|
         | 
| 282 | 
            -
                    errors[extra_param] = ParametersSchema::ErrorCode::UNKNOWN
         | 
| 283 | 
            -
                  end
         | 
| 284 | 
            -
             | 
| 285 | 
            -
                  raise ParametersSchema::InvalidParameters.new(errors) if errors.any?
         | 
| 286 | 
            -
                end
         | 
| 287 | 
            -
             | 
| 288 | 
            -
                def __serve
         | 
| 289 | 
            -
                  @sanitized_params
         | 
| 290 | 
            -
                    .reject{ |p| p[:value].nil? && !p[:keep_if_nil] }
         | 
| 291 | 
            -
                    .each_with_object({}.with_indifferent_access) do |p, h|
         | 
| 292 | 
            -
                      h[p[:param]] = p[:value]
         | 
| 293 | 
            -
                    end
         | 
| 294 | 
            -
                end
         | 
| 295 | 
            -
              end
         | 
| 1 | 
            +
            module ParametersSchema
         | 
| 2 | 
            +
              class Schema
         | 
| 3 | 
            +
                def initialize(&schema)
         | 
| 4 | 
            +
                  @schema = schema
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def validate!(params)
         | 
| 8 | 
            +
                  # Make sure we have params we can work with.
         | 
| 9 | 
            +
                  @params = __prepare_params(params)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # Parse and validate each param.
         | 
| 12 | 
            +
                  @sanitized_params = []
         | 
| 13 | 
            +
                  instance_eval(&@schema)
         | 
| 14 | 
            +
                  # Serve the params if valid, otherwise throw exception.
         | 
| 15 | 
            +
                  __handle_errors
         | 
| 16 | 
            +
                  __serve
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def param(name, options = {}, &inner_params)
         | 
| 22 | 
            +
                  options[:required] = !options.has_key?(:required) || options[:required].present?
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  options[:type] = [options[:type] || String].flatten
         | 
| 25 | 
            +
                  options[:allow] = [options[:allow].present? ? options[:allow] : ParametersSchema::Options.any_keyword].flatten
         | 
| 26 | 
            +
                  options[:deny] = [options[:deny].present? ? options[:deny] : ParametersSchema::Options.none_keyword].flatten
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  [ParametersSchema::Options.any_keyword, ParametersSchema::Options.none_keyword].each do |dominant_value|
         | 
| 29 | 
            +
                    [:allow, :deny, :type].each do |key|
         | 
| 30 | 
            +
                      options[key] = [dominant_value] if options[key].include?(dominant_value)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  options[:array] = options[:array] || false
         | 
| 35 | 
            +
                  options[:parent] = options[:parent] || @params
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  options[:type].map! do |type|
         | 
| 38 | 
            +
                    # Limit to { key => value }
         | 
| 39 | 
            +
                    if type.kind_of?(Hash) && type.count > 1
         | 
| 40 | 
            +
                      type = { type.first[0] => type.first[1] }
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # Limit to { Array => value }
         | 
| 44 | 
            +
                    if type.kind_of?(Hash) && type.first[0] != Array
         | 
| 45 | 
            +
                      type = type.first[0]
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # Apply :array keyword if not already in the format { Array => value }
         | 
| 49 | 
            +
                    if options.delete(:array) && !type.kind_of?(Hash)
         | 
| 50 | 
            +
                      type = { Array => type }
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    # The format...
         | 
| 54 | 
            +
                    #
         | 
| 55 | 
            +
                    #   param :potatoe do
         | 
| 56 | 
            +
                    #     ...
         | 
| 57 | 
            +
                    #   end
         | 
| 58 | 
            +
                    #
         | 
| 59 | 
            +
                    # ... is always an Hash.
         | 
| 60 | 
            +
                    if type.kind_of?(Hash) && inner_params.present?
         | 
| 61 | 
            +
                      type = { Array => Hash }
         | 
| 62 | 
            +
                    elsif inner_params.present?
         | 
| 63 | 
            +
                      type = Hash
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    type
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  @sanitized_params.push(__validate_param(name, options, inner_params))
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def __prepare_params(params)
         | 
| 73 | 
            +
                  params ||= {}
         | 
| 74 | 
            +
                  params = {} unless params.kind_of?(Hash)
         | 
| 75 | 
            +
                  params = params.clone.with_indifferent_access
         | 
| 76 | 
            +
                  ParametersSchema::Options.skip_parameters.each{ |param| params.delete(param) }
         | 
| 77 | 
            +
                  params
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def __validate_param(name, options, inner_params)
         | 
| 81 | 
            +
                  # Validate the presence of the parameter.
         | 
| 82 | 
            +
                  value, error = __validate_param_presence(name, options)
         | 
| 83 | 
            +
                  return __stop_validation(name, value, error, options) if error || (!options[:required] && value.nil?)
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  # Validate nil value.
         | 
| 86 | 
            +
                  value, error = __validate_param_value_nil(value, options)
         | 
| 87 | 
            +
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  # Validate empty value (except hash).
         | 
| 90 | 
            +
                  value, error = __validate_param_value_empty(value, options)
         | 
| 91 | 
            +
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  # Validate the type of the parameter.
         | 
| 94 | 
            +
                  [options[:type]].flatten.each do |type|
         | 
| 95 | 
            +
                    value, error = __validate_type_and_cast(value, type, options, inner_params)
         | 
| 96 | 
            +
                    break if error.blank?
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  # Validate the allowed and denied values of the parameter
         | 
| 101 | 
            +
                  unless value.kind_of?(Array) || value.kind_of?(Hash)
         | 
| 102 | 
            +
                    [:allow, :deny].each do |allow_or_deny|
         | 
| 103 | 
            +
                      value, error = __validate_param_value_format(value, options, allow_or_deny)
         | 
| 104 | 
            +
                      return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  # Validate empty value for hash.
         | 
| 109 | 
            +
                  # This is done at this point to let the validation emit errors when inner parameters are missing.
         | 
| 110 | 
            +
                  # It is preferable that { key: {} } emit { key: { name: :missing } } than { key: :empty }.
         | 
| 111 | 
            +
                  value, error = __validate_param_value_hash_empty(value, options)
         | 
| 112 | 
            +
                  return __stop_validation(name, value, error, options) if error || value.nil?
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  __stop_validation(name, value, error, options)
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def __validate_param_presence(name, options)
         | 
| 118 | 
            +
                  error = nil
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  if options[:required] && !options[:parent].has_key?(name)
         | 
| 121 | 
            +
                    error = ParametersSchema::ErrorCode::MISSING
         | 
| 122 | 
            +
                  elsif options[:parent].has_key?(name)
         | 
| 123 | 
            +
                    value = options[:parent][name]
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  [value, error]
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def __validate_param_value_nil(value, options)
         | 
| 130 | 
            +
                  error = nil
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  if !options[:allow].include?(ParametersSchema::Options.nil_keyword) && value.nil?
         | 
| 133 | 
            +
                    error = ParametersSchema::ErrorCode::NIL
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  [value, error]
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def __validate_param_value_empty(value, options)
         | 
| 140 | 
            +
                  error = nil
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && !value.kind_of?(Hash) && value.respond_to?(:empty?) && value.empty?
         | 
| 143 | 
            +
                    error = ParametersSchema::ErrorCode::EMPTY
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  [value, error]
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def __validate_param_value_hash_empty(value, options)
         | 
| 150 | 
            +
                  error = nil
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && value.kind_of?(Hash) && value.empty?
         | 
| 153 | 
            +
                    error = ParametersSchema::ErrorCode::EMPTY
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  [value, error]
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def __validate_param_value_format(value, options, allow_or_deny)
         | 
| 160 | 
            +
                  conditions = options[allow_or_deny] - [ParametersSchema::Options.empty_keyword, ParametersSchema::Options.nil_keyword]
         | 
| 161 | 
            +
                  inverse = allow_or_deny == :deny
         | 
| 162 | 
            +
                  accept_all_keyword = inverse ? ParametersSchema::Options.none_keyword : ParametersSchema::Options.any_keyword
         | 
| 163 | 
            +
                  refuse_all_keyword = inverse ? ParametersSchema::Options.any_keyword : ParametersSchema::Options.none_keyword
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                  return [value, nil] if conditions.include?(accept_all_keyword)
         | 
| 166 | 
            +
                  return [value, ParametersSchema::ErrorCode::DISALLOWED] if conditions.include?(refuse_all_keyword)
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  error = nil
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  conditions.each do |condition|
         | 
| 171 | 
            +
                    error = nil
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    if condition.kind_of?(Range)
         | 
| 174 | 
            +
                      condition_passed = condition.include?(value)
         | 
| 175 | 
            +
                      condition_passed = !condition_passed if inverse
         | 
| 176 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 177 | 
            +
                    elsif condition.kind_of?(Regexp) && !value.kind_of?(String)
         | 
| 178 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 179 | 
            +
                    elsif condition.kind_of?(Regexp) && value.kind_of?(String)
         | 
| 180 | 
            +
                      condition_passed = (condition =~ value).present?
         | 
| 181 | 
            +
                      condition_passed = !condition_passed if inverse
         | 
| 182 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 183 | 
            +
                    else
         | 
| 184 | 
            +
                      condition_passed = condition == value
         | 
| 185 | 
            +
                      condition_passed = !condition_passed if inverse
         | 
| 186 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
         | 
| 187 | 
            +
                    end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    break if inverse ? error.present? : error.blank?
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  [value, error]
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                def __validate_type_and_cast(value, type, options, inner_params)
         | 
| 196 | 
            +
                  if type.kind_of?(Hash)
         | 
| 197 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(Array)
         | 
| 198 | 
            +
                    value, error = __validate_array(value, type.values.first, options, inner_params) unless error
         | 
| 199 | 
            +
                  elsif inner_params.present?
         | 
| 200 | 
            +
                    begin
         | 
| 201 | 
            +
                      inner_schema = ParametersSchema::Schema.new(&inner_params)
         | 
| 202 | 
            +
                      value = inner_schema.validate!(value)
         | 
| 203 | 
            +
                    rescue ParametersSchema::InvalidParameters => e
         | 
| 204 | 
            +
                      error = e.errors
         | 
| 205 | 
            +
                    end
         | 
| 206 | 
            +
                  elsif type == ParametersSchema::Options.boolean_keyword
         | 
| 207 | 
            +
                    value = true if ParametersSchema::Options.boolean_true_values.include?(value.kind_of?(String) ? value.downcase : value)
         | 
| 208 | 
            +
                    value = false if ParametersSchema::Options.boolean_false_values.include?(value.kind_of?(String) ? value.downcase : value)
         | 
| 209 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(TrueClass) && !value.kind_of?(FalseClass)
         | 
| 210 | 
            +
                  elsif type == Fixnum
         | 
| 211 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
         | 
| 212 | 
            +
                    value = value.to_i if error.blank? # cast to right type.
         | 
| 213 | 
            +
                  elsif type == Float
         | 
| 214 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
         | 
| 215 | 
            +
                    value = value.to_f if error.blank? # cast to right type.
         | 
| 216 | 
            +
                  elsif type == Regexp
         | 
| 217 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value =~ options[:regex]
         | 
| 218 | 
            +
                  elsif type == ParametersSchema::Options.any_keyword
         | 
| 219 | 
            +
                    # No validation required.
         | 
| 220 | 
            +
                  elsif type == ParametersSchema::Options.none_keyword
         | 
| 221 | 
            +
                    # Always fail. Why would you want to do that?
         | 
| 222 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 223 | 
            +
                  elsif type == String
         | 
| 224 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value.kind_of?(String) || value.kind_of?(Symbol)
         | 
| 225 | 
            +
                    value = value.to_s if error.blank? # cast to right type.
         | 
| 226 | 
            +
                  elsif type == Symbol
         | 
| 227 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED unless value.respond_to?(:to_sym)
         | 
| 228 | 
            +
                    value = value.to_sym if error.blank? # cast to right type.
         | 
| 229 | 
            +
                  elsif type == Date
         | 
| 230 | 
            +
                    begin 
         | 
| 231 | 
            +
                      value = value.kind_of?(String) ? Date.parse(value) : value.to_date
         | 
| 232 | 
            +
                    rescue
         | 
| 233 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 234 | 
            +
                    end
         | 
| 235 | 
            +
                  elsif type == DateTime
         | 
| 236 | 
            +
                    begin 
         | 
| 237 | 
            +
                      value = value.kind_of?(String) ? DateTime.parse(value) : value.to_datetime
         | 
| 238 | 
            +
                    rescue
         | 
| 239 | 
            +
                      error = ParametersSchema::ErrorCode::DISALLOWED
         | 
| 240 | 
            +
                    end
         | 
| 241 | 
            +
                  else
         | 
| 242 | 
            +
                    error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(type)
         | 
| 243 | 
            +
                  end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                  [value, error]
         | 
| 246 | 
            +
                end
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                def __validate_array(value, type, options, inner_params)
         | 
| 249 | 
            +
                  if !value.kind_of?(Array)
         | 
| 250 | 
            +
                    return [value, ParametersSchema::ErrorCode::DISALLOWED]
         | 
| 251 | 
            +
                  end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                  value_opts = {
         | 
| 254 | 
            +
                    required: true,
         | 
| 255 | 
            +
                    type: type,
         | 
| 256 | 
            +
                    parent: { value: nil },
         | 
| 257 | 
            +
                    allow: options[:allow],
         | 
| 258 | 
            +
                    deny: options[:deny]
         | 
| 259 | 
            +
                  }
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                  value.map! do |v|
         | 
| 262 | 
            +
                    value_opts[:parent][:value] = v
         | 
| 263 | 
            +
                    __validate_param(:value, value_opts, inner_params)
         | 
| 264 | 
            +
                  end
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                  # For now, take the first error.
         | 
| 267 | 
            +
                  [value.map{ |v| v[:value] }, value.find{ |v| v[:error].present? }.try(:[], :error)]
         | 
| 268 | 
            +
                end
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                def __stop_validation(name, value, error, options)
         | 
| 271 | 
            +
                  { param: name, error: error, value: value, keep_if_nil: options[:allow].include?(ParametersSchema::Options.nil_keyword) }
         | 
| 272 | 
            +
                end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                def __handle_errors
         | 
| 275 | 
            +
                  errors = @sanitized_params
         | 
| 276 | 
            +
                    .select{ |p| p[:error].present? }
         | 
| 277 | 
            +
                    .each_with_object({}.with_indifferent_access) do |p, h|
         | 
| 278 | 
            +
                      h[p[:param]] = p[:error] == :nested_errors ? p[:value] : p[:error]
         | 
| 279 | 
            +
                    end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                  (@params.keys.map(&:to_sym) - @sanitized_params.map{ |p| p[:param] }).each do |extra_param|
         | 
| 282 | 
            +
                    errors[extra_param] = ParametersSchema::ErrorCode::UNKNOWN
         | 
| 283 | 
            +
                  end
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                  raise ParametersSchema::InvalidParameters.new(errors) if errors.any?
         | 
| 286 | 
            +
                end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                def __serve
         | 
| 289 | 
            +
                  @sanitized_params
         | 
| 290 | 
            +
                    .reject{ |p| p[:value].nil? && !p[:keep_if_nil] }
         | 
| 291 | 
            +
                    .each_with_object({}.with_indifferent_access) do |p, h|
         | 
| 292 | 
            +
                      h[p[:param]] = p[:value]
         | 
| 293 | 
            +
                    end
         | 
| 294 | 
            +
                end
         | 
| 295 | 
            +
              end
         | 
| 296 296 | 
             
            end
         | 
    
        data/lib/parameters_schema.rb
    CHANGED
    
    | @@ -1,8 +1,8 @@ | |
| 1 | 
            -
            require 'active_support/core_ext/hash/indifferent_access'
         | 
| 2 | 
            -
            require 'active_support/core_ext/object/blank'
         | 
| 3 | 
            -
            require 'active_support/core_ext/object/try'
         | 
| 4 | 
            -
            require 'date'
         | 
| 5 | 
            -
            require 'parameters_schema/core_ext'
         | 
| 6 | 
            -
            require 'parameters_schema/options'
         | 
| 7 | 
            -
            require 'parameters_schema/exceptions'
         | 
| 8 | 
            -
            require 'parameters_schema/schema'
         | 
| 1 | 
            +
            require 'active_support/core_ext/hash/indifferent_access'
         | 
| 2 | 
            +
            require 'active_support/core_ext/object/blank'
         | 
| 3 | 
            +
            require 'active_support/core_ext/object/try'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
            require 'parameters_schema/core_ext'
         | 
| 6 | 
            +
            require 'parameters_schema/options'
         | 
| 7 | 
            +
            require 'parameters_schema/exceptions'
         | 
| 8 | 
            +
            require 'parameters_schema/schema'
         |