Module: FakeDynamo::Validation

Included in:
DB, Filter, Item, Key, LocalSecondaryIndex, Projection, Table
Defined in:
lib/fake_dynamo/validation.rb

Instance Method Summary collapse

Instance Method Details

#add_errors(message) ⇒ Object



17
18
19
# File 'lib/fake_dynamo/validation.rb', line 17

def add_errors(message)
  @api_errors << message
end

#api_configObject



117
118
119
# File 'lib/fake_dynamo/validation.rb', line 117

def api_config
  @api_config ||= YAML.load_file(api_config_path)
end

#api_config_pathObject



121
122
123
# File 'lib/fake_dynamo/validation.rb', line 121

def api_config_path
  File.join File.expand_path(File.dirname(__FILE__)), 'api_2012-08-10.yml'
end

#api_input_spec(operation) ⇒ Object



109
110
111
# File 'lib/fake_dynamo/validation.rb', line 109

def api_input_spec(operation)
  api_config[:operations].find { |spec| spec[:name] == operation }[:inputs]
end

#available_operationsObject



113
114
115
# File 'lib/fake_dynamo/validation.rb', line 113

def available_operations
  @available_operations ||= api_config[:operations].map { |spec| spec[:name] }
end

#key_schema_mismatchObject



159
160
161
# File 'lib/fake_dynamo/validation.rb', line 159

def key_schema_mismatch
  raise ValidationException, "The provided key element does not match the schema"
end

#param(attribute, parents) ⇒ Object



105
106
107
# File 'lib/fake_dynamo/validation.rb', line 105

def param(attribute, parents)
  (parents + [attribute]).join('.')
end

#validate!(&block) ⇒ Object



6
7
8
9
10
11
12
13
14
# File 'lib/fake_dynamo/validation.rb', line 6

def validate!(&block)
  @api_errors = []
  yield
  unless @api_errors.empty?
    plural = @api_errors.size == 1 ? '' : 's'
    message = "#{@api_errors.size} error#{plural} detected: #{@api_errors.join('; ')}"
    raise ValidationException, message
  end
end

#validate_hash_condition(condition) ⇒ Object



200
201
202
203
204
205
206
207
208
# File 'lib/fake_dynamo/validation.rb', line 200

def validate_hash_condition(condition)
  unless condition
    raise ValidationException, "Query condition missed key schema element #{key_schema.hash_key.name}"
  end

  if condition['ComparisonOperator'] != 'EQ'
    raise ValidationException, "Query key condition not supported"
  end
end

#validate_hash_key(index, table) ⇒ Object



175
176
177
178
179
# File 'lib/fake_dynamo/validation.rb', line 175

def validate_hash_key(index, table)
  if index.hash_key != table.hash_key
    raise ValidationException, "Index KeySchema does not have the same leading hash key as table KeySchema for index"
  end
end

#validate_index_names(indexes) ⇒ Object



193
194
195
196
197
198
# File 'lib/fake_dynamo/validation.rb', line 193

def validate_index_names(indexes)
  names = indexes.map(&:name)
  if names.uniq.size != names.size
    raise ValidationException, "Duplicate index name: #{names.find { |n| names.count(n) > 1 }}"
  end
end

#validate_input(operation, data) ⇒ Object



33
34
35
36
37
# File 'lib/fake_dynamo/validation.rb', line 33

def validate_input(operation, data)
  api_input_spec(operation).each do |attribute, spec|
    validate_spec(attribute, data[attribute], spec, [])
  end
end

#validate_key_data(data, key_schema) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/fake_dynamo/validation.rb', line 146

def validate_key_data(data, key_schema)
  hash_key = data[key_schema.hash_key.name] or key_schema_mismatch
  validate_type(hash_key, key_schema.hash_key)

  if key_schema.range_key
    range_key = data[key_schema.range_key.name] or key_schema_mismatch
    validate_type(range_key, key_schema.range_key)
    key_schema_mismatch if data.size != 2
  else
    key_schema_mismatch if data.size != 1
  end
end

#validate_key_schema(data, key_schema) ⇒ Object



136
137
138
139
140
141
142
143
144
# File 'lib/fake_dynamo/validation.rb', line 136

def validate_key_schema(data, key_schema)
  key = data[key_schema.hash_key.name] or raise ValidationException, "Missing the key #{key_schema.hash_key.name} in the item"
  validate_type(key, key_schema.hash_key)

  if key_schema.range_key
    range_key = data[key_schema.range_key.name] or raise ValidationException, "Missing the key #{key_schema.range_key.name} in the item"
    validate_type(range_key, key_schema.range_key)
  end
end

#validate_operation(operation) ⇒ Object



29
30
31
# File 'lib/fake_dynamo/validation.rb', line 29

def validate_operation(operation)
  raise UnknownOperationException, "Unknown operation: #{operation}" unless available_operations.include? operation
end

#validate_payload(operation, data) ⇒ Object



21
22
23
24
25
26
27
# File 'lib/fake_dynamo/validation.rb', line 21

def validate_payload(operation, data)
  validate! do
    validate_request_size(data)
    validate_operation(operation)
    validate_input(operation, data)
  end
end

#validate_projection(projection) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/fake_dynamo/validation.rb', line 181

def validate_projection(projection)
  if projection.type == 'INCLUDE'
    unless projection.non_key_attributes
      raise ValidationException, "ProjectionType is #{projection.type}, but NonKeyAttributes is not specified"
    end
  else
    if projection.non_key_attributes
      raise ValidationException, "ProjectionType is #{projection.type}, but NonKeyAttributes is specified"
    end
  end
end

#validate_range_condition(condition, schema) ⇒ Object



210
211
212
213
214
# File 'lib/fake_dynamo/validation.rb', line 210

def validate_range_condition(condition, schema)
  unless condition.has_key?(schema.range_key.name)
    raise ValidationException, "Query condition missed key schema element #{schema.range_key.name}"
  end
end

#validate_range_key(key_schema) ⇒ Object



169
170
171
172
173
# File 'lib/fake_dynamo/validation.rb', line 169

def validate_range_key(key_schema)
  unless key_schema.range_key
    raise ValidationException, 'Table KeySchema does not have a range key'
  end
end

#validate_request_size(data) ⇒ Object



163
164
165
166
167
# File 'lib/fake_dynamo/validation.rb', line 163

def validate_request_size(data)
  if data.to_s.bytesize > 1 * 1024 * 1024
    raise ValidationException, "Request size can't exceed 1 mb"
  end
end

#validate_spec(attribute, data, spec, parents) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/fake_dynamo/validation.rb', line 39

def validate_spec(attribute, data, spec, parents)
  if not data
    if spec.include?(:required)
      add_errors("value null at '#{param(attribute, parents)}' failed to satisfy the constraint: Member must not be null")
    end
    return
  end

  spec.each do |constrain|
    case constrain
    when :string
      add_errors("The parameter '#{param(attribute, parents)}' must be a string") unless data.kind_of? String
    when :blob
      add_errors("The parameter '#{param(attribute, parents)}' must be a binary") unless data.kind_of? String
    when :long
      add_errors("The parameter '#{param(attribute, parents)}' must be a long") unless data.kind_of? Integer
    when :integer
      add_errors("The parameter '#{param(attribute, parents)}' must be a integer") unless data.kind_of? Integer
    when :boolean
      add_errors("The parameter '#{param(attribute, parents)}' must be a boolean") unless (data.kind_of? TrueClass or data.kind_of? FalseClass)
    when Hash
      new_parents = parents + [attribute]
      case constrain.keys.first
      when :pattern
        pattern = constrain[:pattern]
        unless data =~ pattern
          add_errors("The parameter '#{param(attribute, parents)}' should match the pattern #{pattern}")
        end
      when :within
        range = constrain[:within]
        unless range.include?(Numeric === data ? data : data.size)
          add_errors("The parameter '#{param(attribute, parents)}' value '#{data}' should be within #{range}")
        end
      when :enum
        enum = constrain[:enum]
        unless enum.include? data
          add_errors("Value '#{data}' at '#{param(attribute, parents)}' failed to satisfy the constraint: Member must satisfy enum values set: #{enum}")
        end
      when :structure
        structure = constrain[:structure]
        structure.each do |attribute, spec|
          validate_spec(attribute, data[attribute], spec, parents + ["member"])
        end
      when :map
        map = constrain[:map]
        raise ValidationException, "#{param(attribute, parents)} must be a Hash" unless data.kind_of? Hash
        data.each do |key, value|
          validate_spec(key, key, map[:key], new_parents)
          validate_spec(key, value, map[:value], new_parents)
        end
      when :list
        raise ValidationException, "#{param(attribute, parents)} must be a Array" unless data.kind_of? Array
        data.each_with_index do |element, i|
          validate_spec(element, element, constrain[:list], new_parents + [(i+1).to_s])
        end
      else
        raise "Unhandled constraint #{constrain}"
      end
    when :required
      # handled earlier
    else
      raise "Unhandled constraint #{constrain}"
    end
  end
end

#validate_type(value, attribute) ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'lib/fake_dynamo/validation.rb', line 125

def validate_type(value, attribute)
  if attribute.kind_of?(Attribute)
    expected_type = value.keys.first
    if expected_type != attribute.type
      raise ValidationException, "Type mismatch for key #{attribute.name}"
    end
  else
    raise 'Unknown attribute'
  end
end