Files
datapipeline/lib/datapipeline/validation.rb
T

54 lines
1.7 KiB
Ruby

require 'date'
class DataPipeline
# raised by a validate step, handled like any other row error (on_error)
class ValidationError < StandardError; end
#
# rules per column
# required: true value present and not empty
# max: 35, min: 1 length of the value as text
# format: /\A\d+\z/ regex on the value as text
# in: %w[S H] allowed values
# type: :integer | :decimal | :date
#
# empty values are only checked by required
#
module Validation
TYPES = {
integer: ->(v) { Integer(v.to_s, 10) },
decimal: ->(v) { Float(v.to_s) },
date: ->(v) { v.is_a?(Date) ? v : Date.iso8601(v.to_s) }
}.freeze
module_function
def errors row, rules
rules.flat_map do |column, rule|
value = row.key?(column) ? row[column] : row[column.to_s]
check(column, value, rule)
end
end
def check column, value, rule
empty = value.nil? || value.to_s.strip.empty?
return(rule[:required] ? ["#{column}: missing"] : []) if empty
text = value.to_s
messages = []
messages << "#{column}: longer than #{rule[:max]}" if rule[:max] && text.length > rule[:max]
messages << "#{column}: shorter than #{rule[:min]}" if rule[:min] && text.length < rule[:min]
messages << "#{column}: format #{rule[:format].inspect}" if rule[:format] && !text.match?(rule[:format])
messages << "#{column}: not in #{rule[:in].inspect}" if rule[:in] && !rule[:in].include?(value)
if rule[:type]
begin
TYPES.fetch(rule[:type]).call(value)
rescue ArgumentError, TypeError, Date::Error
messages << "#{column}: not #{rule[:type]}"
end
end
messages
end
end
end