54 lines
1.7 KiB
Ruby
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
|