Module: Tap::Support::Validation

Defined in:
lib/tap/support/validation.rb

Overview

Validation generates blocks for common validations and transformations of configurations set through Configurable. In general these blocks load string inputs as YAML and valdiate the results; non-string inputs are simply validated.

integer = Validation.integer
integer.class             # => Proc
integer.call(1)           # => 1
integer.call('1')         # => 1
integer.call(nil)         # => ValidationError

– Note the unusual syntax for declaring constants that are blocks defined by lambda… ex:

block = lambda {}
CONST = block

This syntax plays well with RDoc, which otherwise gets jacked when you do it all in one step.

Defined Under Namespace

Classes: ValidationError, YamlizationError

Constant Summary collapse

STRING =
string_validation_block
STRING_OR_NIL =
string_or_nil_validation_block
SYMBOL =
yamlize_and_check(Symbol)
SYMBOL_OR_NIL =
yamlize_and_check(Symbol, nil)
BOOLEAN =
yamlize_and_check(true, false, nil)
SWITCH =
yamlize_and_check(true, false, nil)
FLAG =
yamlize_and_check(true, false, nil)
ARRAY =
yamlize_and_check(Array)
ARRAY_OR_NIL =
yamlize_and_check(Array, nil)
LIST =
list_block
HASH =
yamlize_and_check(Hash)
HASH_OR_NIL =
yamlize_and_check(Hash, nil)
INTEGER =
yamlize_and_check(Integer)
INTEGER_OR_NIL =
yamlize_and_check(Integer, nil)
FLOAT =
yamlize_and_check(Float)
FLOAT_OR_NIL =
yamlize_and_check(Float, nil)
NUMERIC =
yamlize_and_check(Numeric)
NUMERIC_OR_NIL =
yamlize_and_check(Numeric, nil)
REGEXP =
regexp_block
REGEXP_OR_NIL =
regexp_or_nil_block
RANGE =
range_block
RANGE_OR_NIL =
range_or_nil_block
TIME =
time_block
TIME_OR_NIL =
time_or_nil_block

Class Method Summary collapse

Class Method Details

.arrayObject

Returns a block that checks the input is an array. String inputs are loaded as yaml first.

array.class               # => Proc
array.call([1,2,3])       # => [1,2,3]
array.call('[1, 2, 3]')   # => [1,2,3]
array.call(nil)           # => ValidationError
array.call('str')         # => ValidationError


223
# File 'lib/tap/support/validation.rb', line 223

def array(); ARRAY; end

.array_or_nilObject

Same as array but allows nil:

array_or_nil.call('~')    # => nil
array_or_nil.call(nil)    # => nil


230
# File 'lib/tap/support/validation.rb', line 230

def array_or_nil(); ARRAY_OR_NIL; end

.booleanObject

Returns a block that checks the input is true, false or nil. String inputs are loaded as yaml first.

boolean.class             # => Proc
boolean.call(true)        # => true
boolean.call(false)       # => false
boolean.call(nil)         # => nil

boolean.call('true')      # => true
boolean.call('yes')       # => true
boolean.call('FALSE')     # => false

boolean.call(1)           # => ValidationError
boolean.call("str")       # => ValidationError


203
# File 'lib/tap/support/validation.rb', line 203

def boolean(); BOOLEAN; end

.check(*validations) ⇒ Object

Returns a block that calls validate using the block input and the input validations. Raises an error if no validations are specified.

Raises:

  • (ArgumentError)


96
97
98
99
# File 'lib/tap/support/validation.rb', line 96

def check(*validations)
  raise ArgumentError.new("no validations specified") if validations.empty?
  lambda {|input| validate(input, validations) }
end

.flagObject

Same as boolean.



211
# File 'lib/tap/support/validation.rb', line 211

def flag(); FLAG; end

.floatObject

Returns a block that checks the input is a float. String inputs are loaded as yaml first.

float.class               # => Proc
float.call(1.1)           # => 1.1
float.call('1.1')         # => 1.1
float.call('1.0e+6')      # => 1e6
float.call(1)             # => ValidationError
float.call(nil)           # => ValidationError
float.call('str')         # => ValidationError


303
# File 'lib/tap/support/validation.rb', line 303

def float(); FLOAT; end

.float_or_nilObject

Same as float but allows nil:

float_or_nil.call('~')    # => nil
float_or_nil.call(nil)    # => nil


310
# File 'lib/tap/support/validation.rb', line 310

def float_or_nil(); FLOAT_OR_NIL; end

.hashObject

Returns a block that checks the input is a hash. String inputs are loaded as yaml first.

hash.class                     # => Proc
hash.call({'key' => 'value'})  # => {'key' => 'value'}
hash.call('key: value')        # => {'key' => 'value'}
hash.call(nil)                 # => ValidationError
hash.call('str')               # => ValidationError


262
# File 'lib/tap/support/validation.rb', line 262

def hash(); HASH; end

.hash_or_nilObject

Same as hash but allows nil:

hash_or_nil.call('~')          # => nil
hash_or_nil.call(nil)          # => nil


269
# File 'lib/tap/support/validation.rb', line 269

def hash_or_nil(); HASH_OR_NIL; end

.integerObject

Returns a block that checks the input is an integer. String inputs are loaded as yaml first.

integer.class             # => Proc
integer.call(1)           # => 1
integer.call('1')         # => 1
integer.call(1.1)         # => ValidationError
integer.call(nil)         # => ValidationError
integer.call('str')       # => ValidationError


282
# File 'lib/tap/support/validation.rb', line 282

def integer(); INTEGER; end

.integer_or_nilObject

Same as integer but allows nil:

integer_or_nil.call('~')  # => nil
integer_or_nil.call(nil)  # => nil


289
# File 'lib/tap/support/validation.rb', line 289

def integer_or_nil(); INTEGER_OR_NIL; end

.listObject

Returns a block that checks the input is an array. If the input is a string the string is split along commas and each value yamlized into an array.

list.class                # => Proc
list.call([1,2,3])        # => [1,2,3]
list.call('1,2,3')        # => [1,2,3]
list.call('str')          # => ['str']
list.call(nil)            # => ValidationError


243
# File 'lib/tap/support/validation.rb', line 243

def list(); LIST; end

.numObject

Returns a block that checks the input is a number. String inputs are loaded as yaml first.

num.class               # => Proc
num.call(1.1)           # => 1.1
num.call(1)             # => 1
num.call(1e6)           # => 1e6
num.call('1.1')         # => 1.1
num.call('1.0e+6')      # => 1e6
num.call(nil)           # => ValidationError
num.call('str')         # => ValidationError


325
# File 'lib/tap/support/validation.rb', line 325

def num(); NUMERIC; end

.num_or_nilObject

Same as num but allows nil:

num_or_nil.call('~')    # => nil
num_or_nil.call(nil)    # => nil


332
# File 'lib/tap/support/validation.rb', line 332

def num_or_nil(); NUMERIC_OR_NIL; end

.rangeObject

Returns a block that checks the input is a range. String inputs are split into a beginning and end if possible, where each part is loaded as yaml before being used to construct a Range.a

range.class               # => Proc
range.call(1..10)         # => 1..10
range.call('1..10')       # => 1..10
range.call('a..z')        # => 'a'..'z'
range.call('-10...10')    # => -10...10
range.call(nil)           # => ValidationError
range.call('1.10')        # => ValidationError
range.call('a....z')      # => ValidationError


388
# File 'lib/tap/support/validation.rb', line 388

def range(); RANGE; end

.range_or_nilObject

Same as range but allows nil:

range_or_nil.call('~')    # => nil
range_or_nil.call(nil)    # => nil


401
# File 'lib/tap/support/validation.rb', line 401

def range_or_nil(); RANGE_OR_NIL; end

.regexpObject

Returns a block that checks the input is a regexp. String inputs are converted to regexps using Regexp#new.

regexp.class              # => Proc
regexp.call(/regexp/)     # => /regexp/
regexp.call('regexp')     # => /regexp/

# use of ruby-specific flags can turn on/off 
# features like case insensitive matching
regexp.call('(?i)regexp') # => /(?i)regexp/


347
# File 'lib/tap/support/validation.rb', line 347

def regexp(); REGEXP; end

.regexp_or_nilObject

Same as regexp but allows nil. Note the special behavior of the nil string ‘~’ – rather than being converted to a regexp, it is processed as nil to be consistent with the other [class]_or_nil methods.

regexp_or_nil.call('~')   # => nil
regexp_or_nil.call(nil)   # => nil


362
# File 'lib/tap/support/validation.rb', line 362

def regexp_or_nil(); REGEXP_OR_NIL; end

.stringObject

Returns a block that checks the input is a string. Moreover, strings are re-evaluated as string literals using %Q.

string.class              # => Proc
string.call('str')        # => 'str'
string.call('\n')         # => "\n"
string.call("\n")         # => "\n"
string.call("%s")         # => "%s"
string.call(nil)          # => ValidationError
string.call(:sym)         # => ValidationError


144
# File 'lib/tap/support/validation.rb', line 144

def string(); STRING; end

.string_or_nilObject

Same as string but allows nil. Note the special behavior of the nil string ‘~’ – rather than being treated as a string, it is processed as nil to be consistent with the other [class]_or_nil methods.

string_or_nil.call('~')   # => nil
string_or_nil.call(nil)   # => nil


159
# File 'lib/tap/support/validation.rb', line 159

def string_or_nil(); STRING_OR_NIL; end

.switchObject

Same as boolean.



207
# File 'lib/tap/support/validation.rb', line 207

def switch(); SWITCH; end

.symbolObject

Returns a block that checks the input is a symbol. String inputs are loaded as yaml first.

symbol.class              # => Proc
symbol.call(:sym)         # => :sym
symbol.call(':sym')       # => :sym
symbol.call(nil)          # => ValidationError
symbol.call('str')        # => ValidationError


178
# File 'lib/tap/support/validation.rb', line 178

def symbol(); SYMBOL; end

.symbol_or_nilObject

Same as symbol but allows nil:

symbol_or_nil.call('~')   # => nil
symbol_or_nil.call(nil)   # => nil


185
# File 'lib/tap/support/validation.rb', line 185

def symbol_or_nil(); SYMBOL_OR_NIL; end

.timeObject

Returns a block that checks the input is a Time. String inputs are loaded using Time.parse and then converted into times. Parsed times are local unless specified otherwise.

time.class               # => Proc

now = Time.now
time.call(now)           # => now

time.call('2008-08-08 20:00:00.00 +08:00').getutc.strftime('%Y/%m/%d %H:%M:%S')
#  => '2008/08/08 12:00:00'

time.call('2008-08-08').strftime('%Y/%m/%d %H:%M:%S')
#  => '2008/08/08 00:00:00'

time.call(1)             # => ValidationError
time.call(nil)           # => ValidationError

Warning: Time.parse will parse a valid time (Time.now) even when no time is specified:

time.call('str').strftime('%Y/%m/%d %H:%M:%S')      
# => Time.now.strftime('%Y/%m/%d %H:%M:%S')


442
443
444
445
446
447
# File 'lib/tap/support/validation.rb', line 442

def time()
  # adding this here is a compromise to lazy-load the parse
  # method (autoload doesn't work since Time already exists)
  require 'time' unless Time.respond_to?(:parse)
  TIME
end

.time_or_nilObject

Same as time but allows nil:

time_or_nil.call('~')    # => nil
time_or_nil.call(nil)    # => nil


459
460
461
462
463
464
# File 'lib/tap/support/validation.rb', line 459

def time_or_nil()
  # adding this check is a compromise to autoload the parse 
  # method (autoload doesn't work since Time already exists)
  require 'time' unless Time.respond_to?(:parse)
  TIME_OR_NIL
end

.validate(input, validations) ⇒ Object

Returns input if any of the validations match any of the inputs, as in a case statement. Raises a ValidationError otherwise. For example:

validate(10, [Integer, nil])

Does the same as:

case 10
when Integer, nil then input
else raise ValidationError.new(...)
end

Note the validations input must be an Array or nil; validate will raise an ArgumentError otherwise.

All inputs are considered VALID if validations == nil.



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/tap/support/validation.rb', line 69

def validate(input, validations)
  case validations
  when Array
  
    case input
    when *validations then input
    else raise ValidationError.new(input, validations)
    end
    
  when nil then input
  else raise ArgumentError.new("validations must be nil, or an array of valid inputs")
  end
end

.yaml(*validations) ⇒ Object

Returns a block that loads input strings as YAML, then calls validate with the result and the input validations. Non-string inputs are not converted.

b = yaml(Integer, nil)
b.class                 # => Proc
b.call(1)               # => 1
b.call("1")             # => 1
b.call(nil)             # => nil
b.call("str")           # => ValidationError

If no validations are specified, the result will be returned without validation.



114
115
116
117
118
119
# File 'lib/tap/support/validation.rb', line 114

def yaml(*validations)
  lambda do |input|
    res = input.kind_of?(String) ? yamlize(input) : input
    validations.empty? ? res : validate(res, validations)
  end
end

.yamlize(input) ⇒ Object

Attempts to load the input as YAML. Raises a YamlizationError for errors.



85
86
87
88
89
90
91
# File 'lib/tap/support/validation.rb', line 85

def yamlize(input)
  begin
    YAML.load(input)
  rescue
    raise YamlizationError.new(input, $!.message)
  end
end

.yamlize_and_check(*validations) ⇒ Object

Returns a block loads a String input as YAML then validates the result is valid using the input validations. If the input is not a String, the input is validated directly.



125
126
127
128
129
130
# File 'lib/tap/support/validation.rb', line 125

def yamlize_and_check(*validations)
  lambda do |input|
    input = yamlize(input) if input.kind_of?(String)
    validate(input, validations)
  end
end