objective 0.1.0
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 +7 -0
- data/lib/objective/allow.rb +10 -0
- data/lib/objective/deny.rb +10 -0
- data/lib/objective/discard.rb +10 -0
- data/lib/objective/errors/error_array.rb +21 -0
- data/lib/objective/errors/error_atom.rb +27 -0
- data/lib/objective/errors/error_hash.rb +65 -0
- data/lib/objective/errors/error_message_creator.rb +46 -0
- data/lib/objective/errors/validation_error.rb +14 -0
- data/lib/objective/filter.rb +166 -0
- data/lib/objective/filters/any_filter.rb +7 -0
- data/lib/objective/filters/array_filter.rb +49 -0
- data/lib/objective/filters/boolean_filter.rb +21 -0
- data/lib/objective/filters/date_filter.rb +29 -0
- data/lib/objective/filters/decimal_filter.rb +46 -0
- data/lib/objective/filters/duck_filter.rb +18 -0
- data/lib/objective/filters/file_filter.rb +22 -0
- data/lib/objective/filters/float_filter.rb +45 -0
- data/lib/objective/filters/hash_filter.rb +42 -0
- data/lib/objective/filters/integer_filter.rb +60 -0
- data/lib/objective/filters/model_filter.rb +23 -0
- data/lib/objective/filters/root_filter.rb +54 -0
- data/lib/objective/filters/string_filter.rb +30 -0
- data/lib/objective/filters/time_filter.rb +30 -0
- data/lib/objective/filters.rb +140 -0
- data/lib/objective/none.rb +10 -0
- data/lib/objective/outcome.rb +5 -0
- data/lib/objective/unit.rb +103 -0
- data/lib/objective.rb +53 -0
- data/spec/default_spec.rb +33 -0
- data/spec/errors_spec.rb +135 -0
- data/spec/filters/any_filter_spec.rb +34 -0
- data/spec/filters/array_filter_spec.rb +195 -0
- data/spec/filters/boolean_filter_spec.rb +66 -0
- data/spec/filters/date_filter_spec.rb +145 -0
- data/spec/filters/decimal_filter_spec.rb +199 -0
- data/spec/filters/duck_filter_spec.rb +49 -0
- data/spec/filters/file_filter_spec.rb +93 -0
- data/spec/filters/float_filter_spec.rb +140 -0
- data/spec/filters/hash_filter_spec.rb +65 -0
- data/spec/filters/integer_filter_spec.rb +150 -0
- data/spec/filters/model_filter_spec.rb +98 -0
- data/spec/filters/root_filter_spec.rb +113 -0
- data/spec/filters/string_filter_spec.rb +251 -0
- data/spec/filters/time_filter_spec.rb +123 -0
- data/spec/inheritance_spec.rb +36 -0
- data/spec/simple_unit.rb +18 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/unit_spec.rb +244 -0
- metadata +167 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
class IntegerFilter < Objective::Filter
|
5
|
+
private
|
6
|
+
|
7
|
+
def coerce(datum)
|
8
|
+
return datum if datum.blank?
|
9
|
+
|
10
|
+
datum_str = raw_to_string(datum)
|
11
|
+
return datum unless datum_str
|
12
|
+
|
13
|
+
clean_str = datum_str.tr(options.delimiter, '').tr(options.decimal_mark, '.')
|
14
|
+
return datum unless clean_str =~ /\A[-+]?\d*\.?0*\z/
|
15
|
+
clean_str.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def raw_to_string(raw)
|
19
|
+
if raw.is_a?(Float)
|
20
|
+
raw.to_s
|
21
|
+
elsif raw.is_a?(BigDecimal)
|
22
|
+
raw.to_s('F')
|
23
|
+
elsif raw.is_a?(String)
|
24
|
+
raw
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def coerce_error(coerced)
|
29
|
+
return :integer unless coerced.is_a?(Integer)
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate(coerced)
|
33
|
+
return :in unless included?(coerced)
|
34
|
+
return :min unless above_min?(coerced)
|
35
|
+
return :max unless below_max?(coerced)
|
36
|
+
return :scale unless within_scale?(coerced)
|
37
|
+
end
|
38
|
+
|
39
|
+
def included?(datum)
|
40
|
+
return true if options.in.nil?
|
41
|
+
options.in.include?(datum)
|
42
|
+
end
|
43
|
+
|
44
|
+
def above_min?(datum)
|
45
|
+
return true if options.min.nil?
|
46
|
+
datum >= options.min
|
47
|
+
end
|
48
|
+
|
49
|
+
def below_max?(datum)
|
50
|
+
return true if options.max.nil?
|
51
|
+
datum <= options.max
|
52
|
+
end
|
53
|
+
|
54
|
+
def within_scale?(datum)
|
55
|
+
return true if options.scale.nil?
|
56
|
+
(datum - datum.round(options.scale)).zero?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
class ModelFilter < Objective::Filter
|
5
|
+
private
|
6
|
+
|
7
|
+
def coerce_error(coerced)
|
8
|
+
return :model unless coerced.is_a?(class_constant)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(coerced)
|
12
|
+
return :new_records if !options.new_records && (coerced.respond_to?(:new_record?) && coerced.new_record?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def class_constant
|
16
|
+
klass = options[:class]
|
17
|
+
return key.to_s.camelize.constantize if klass.nil?
|
18
|
+
return klass if klass.instance_of? Class
|
19
|
+
klass.to_s.constantize
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
class RootFilter < Objective::Filter
|
5
|
+
def filter(&block)
|
6
|
+
instance_eval(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def keys
|
10
|
+
sub_filters.map(&:key)
|
11
|
+
end
|
12
|
+
|
13
|
+
def feed(*raw)
|
14
|
+
result = OpenStruct.new
|
15
|
+
result.raw = raw
|
16
|
+
result.coerced = coerce(raw)
|
17
|
+
|
18
|
+
inputs = OpenStruct.new
|
19
|
+
errors = Objective::Errors::ErrorHash.new
|
20
|
+
|
21
|
+
data = result.coerced
|
22
|
+
sub_filters_hash.each_pair do |key, key_filter|
|
23
|
+
datum = data.to_h.key?(key) ? data[key] : Objective::NONE
|
24
|
+
key_filter_result = key_filter.feed(datum)
|
25
|
+
next if key_filter_result == Objective::DISCARD
|
26
|
+
|
27
|
+
sub_data = key_filter_result.inputs
|
28
|
+
sub_error = key_filter_result.errors
|
29
|
+
|
30
|
+
if sub_error.nil?
|
31
|
+
inputs[key] = sub_data
|
32
|
+
else
|
33
|
+
errors[key] = key_filter.handle_errors(sub_error)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
result.inputs = inputs
|
38
|
+
result.errors = errors.present? ? errors : nil
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def coerce(raw)
|
43
|
+
raw.each_with_object(OpenStruct.new) do |datum, result|
|
44
|
+
raise_argument_error unless datum.respond_to?(:each_pair)
|
45
|
+
datum.each_pair { |key, value| result[key] = value }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def raise_argument_error
|
50
|
+
raise(ArgumentError, 'All Objective arguments must be a Hash or OpenStruct')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
class StringFilter < Objective::Filter
|
5
|
+
def coerce(raw)
|
6
|
+
return raw unless raw.is_a?(String) || coercable?(raw)
|
7
|
+
tmp = raw.is_a?(BigDecimal) ? raw.to_s(options.decimal_format) : raw.to_s
|
8
|
+
tmp = tmp.gsub(/[^[:print:]\t\r\n]+/, ' ') unless options.allow_control_characters
|
9
|
+
tmp = tmp.strip if options.strip
|
10
|
+
tmp
|
11
|
+
end
|
12
|
+
|
13
|
+
def coercable?(raw)
|
14
|
+
options.coercable_classes.map { |klass| raw.is_a?(klass) }.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def coerce_error(coerced)
|
18
|
+
return :string unless coerced.is_a?(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(coerced)
|
22
|
+
return :empty if coerced.empty?
|
23
|
+
return :min if options.min && coerced.length < options.min
|
24
|
+
return :max if options.max && coerced.length > options.max
|
25
|
+
return :in if options.in && !options.in.include?(coerced)
|
26
|
+
return :matches if options.matches && (options.matches !~ coerced)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
class TimeFilter < Objective::Filter
|
5
|
+
private
|
6
|
+
|
7
|
+
def coerce(raw)
|
8
|
+
return raw if raw.is_a?(Time)
|
9
|
+
return parse(raw) if raw.is_a?(String)
|
10
|
+
return raw.to_time if raw.respond_to?(:to_time)
|
11
|
+
raw
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(raw)
|
15
|
+
options.format ? Time.strptime(raw, options.format) : Time.parse(raw)
|
16
|
+
rescue ArgumentError
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def coerce_error(coerced)
|
21
|
+
return :time unless coerced.is_a?(Time)
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate(coerced)
|
25
|
+
return :after if options.after && coerced <= options.after
|
26
|
+
return :before if options.before && coerced >= options.before
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Filters
|
4
|
+
Config = OpenStruct.new
|
5
|
+
|
6
|
+
Config.any = OpenStruct.new(
|
7
|
+
none: Objective::DENY,
|
8
|
+
nils: Objective::ALLOW,
|
9
|
+
invalid: Objective::DENY
|
10
|
+
)
|
11
|
+
|
12
|
+
Config.array = OpenStruct.new(
|
13
|
+
none: Objective::DENY,
|
14
|
+
nils: Objective::DENY,
|
15
|
+
invalid: Objective::DENY,
|
16
|
+
wrap: false
|
17
|
+
)
|
18
|
+
|
19
|
+
Config.boolean = OpenStruct.new(
|
20
|
+
none: Objective::DENY,
|
21
|
+
nils: Objective::DENY,
|
22
|
+
invalid: Objective::DENY,
|
23
|
+
coercion_map: {
|
24
|
+
'true' => true,
|
25
|
+
'false' => false,
|
26
|
+
'1' => true,
|
27
|
+
'0' => false
|
28
|
+
}.freeze
|
29
|
+
)
|
30
|
+
|
31
|
+
Config.date = OpenStruct.new(
|
32
|
+
none: Objective::DENY,
|
33
|
+
nils: Objective::DENY,
|
34
|
+
invalid: Objective::DENY,
|
35
|
+
format: nil, # If nil, Date.parse will be used for coercion. If something like "%Y-%m-%d", Date.strptime is used
|
36
|
+
after: nil, # A date object, representing the minimum date allowed, inclusive
|
37
|
+
before: nil # A date object, representing the maximum date allowed, inclusive
|
38
|
+
)
|
39
|
+
|
40
|
+
Config.decimal = OpenStruct.new(
|
41
|
+
none: Objective::DENY,
|
42
|
+
nils: Objective::DENY,
|
43
|
+
invalid: Objective::DENY,
|
44
|
+
delimiter: ', ',
|
45
|
+
decimal_mark: '.',
|
46
|
+
min: nil,
|
47
|
+
max: nil,
|
48
|
+
scale: nil
|
49
|
+
)
|
50
|
+
|
51
|
+
Config.duck = OpenStruct.new(
|
52
|
+
none: Objective::DENY,
|
53
|
+
nils: Objective::DENY,
|
54
|
+
invalid: Objective::DENY,
|
55
|
+
methods: nil
|
56
|
+
)
|
57
|
+
|
58
|
+
Config.file = OpenStruct.new(
|
59
|
+
none: Objective::DENY,
|
60
|
+
nils: Objective::DENY,
|
61
|
+
invalid: Objective::DENY,
|
62
|
+
upload: false,
|
63
|
+
size: nil
|
64
|
+
)
|
65
|
+
|
66
|
+
Config.float = OpenStruct.new(
|
67
|
+
none: Objective::DENY,
|
68
|
+
nils: Objective::DENY,
|
69
|
+
invalid: Objective::DENY,
|
70
|
+
delimiter: ', ',
|
71
|
+
decimal_mark: '.',
|
72
|
+
min: nil,
|
73
|
+
max: nil,
|
74
|
+
scale: nil
|
75
|
+
)
|
76
|
+
|
77
|
+
Config.hash = OpenStruct.new(
|
78
|
+
none: Objective::DENY,
|
79
|
+
nils: Objective::DENY,
|
80
|
+
invalid: Objective::DENY
|
81
|
+
)
|
82
|
+
|
83
|
+
Config.integer = OpenStruct.new(
|
84
|
+
none: Objective::DENY,
|
85
|
+
nils: Objective::DENY,
|
86
|
+
invalid: Objective::DENY,
|
87
|
+
delimiter: ', ',
|
88
|
+
decimal_mark: '.',
|
89
|
+
min: nil,
|
90
|
+
max: nil,
|
91
|
+
scale: nil,
|
92
|
+
in: nil
|
93
|
+
)
|
94
|
+
|
95
|
+
Config.model = OpenStruct.new(
|
96
|
+
none: Objective::DENY,
|
97
|
+
nils: Objective::DENY,
|
98
|
+
invalid: Objective::DENY,
|
99
|
+
class: nil,
|
100
|
+
new_records: false
|
101
|
+
)
|
102
|
+
|
103
|
+
Config.root = OpenStruct.new
|
104
|
+
|
105
|
+
Config.string = OpenStruct.new(
|
106
|
+
none: Objective::DENY,
|
107
|
+
nils: Objective::DENY,
|
108
|
+
invalid: Objective::DENY,
|
109
|
+
allow_control_characters: false,
|
110
|
+
strip: true,
|
111
|
+
empty: Objective::DENY,
|
112
|
+
min: nil,
|
113
|
+
max: nil,
|
114
|
+
in: nil,
|
115
|
+
matches: nil,
|
116
|
+
decimal_format: 'F',
|
117
|
+
coercable_classes: [
|
118
|
+
Symbol,
|
119
|
+
TrueClass,
|
120
|
+
FalseClass,
|
121
|
+
Integer,
|
122
|
+
Float,
|
123
|
+
BigDecimal
|
124
|
+
].freeze
|
125
|
+
)
|
126
|
+
|
127
|
+
Config.time = OpenStruct.new(
|
128
|
+
none: Objective::DENY,
|
129
|
+
nils: Objective::DENY,
|
130
|
+
invalid: Objective::DENY,
|
131
|
+
format: nil,
|
132
|
+
after: nil,
|
133
|
+
before: nil
|
134
|
+
)
|
135
|
+
|
136
|
+
def self.config
|
137
|
+
yield Config
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Objective
|
3
|
+
module Unit
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attr_reader :inputs, :raw_inputs, :built
|
8
|
+
const_set('ALLOW', Objective::ALLOW)
|
9
|
+
const_set('DENY', Objective::DENY)
|
10
|
+
const_set('DISCARD', Objective::DISCARD)
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def filter(&block)
|
15
|
+
root_filter.filter(&block)
|
16
|
+
root_filter.keys.each do |key|
|
17
|
+
define_method(key) { inputs[key] }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def root_filter
|
22
|
+
@root_filter ||=
|
23
|
+
superclass.try(:root_filter).try(:dup) ||
|
24
|
+
Objective::Filters::RootFilter.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def build(*args)
|
28
|
+
new.build(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build!(*args)
|
32
|
+
outcome = build(*args)
|
33
|
+
return outcome.result if outcome.success
|
34
|
+
raise Objective::ValidationError, outcome.errors
|
35
|
+
end
|
36
|
+
|
37
|
+
def run(*args)
|
38
|
+
new.run(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def run!(*args)
|
42
|
+
outcome = run(*args)
|
43
|
+
return outcome.result if outcome.success
|
44
|
+
raise Objective::ValidationError, outcome.errors
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# INSTANCE METHODS
|
49
|
+
|
50
|
+
def build(*args)
|
51
|
+
filter_result = self.class.root_filter.feed(*args)
|
52
|
+
@raw_inputs = filter_result.coerced
|
53
|
+
@inputs = filter_result.inputs
|
54
|
+
@errors = filter_result.errors
|
55
|
+
@built = true
|
56
|
+
try('validate') if valid?
|
57
|
+
outcome
|
58
|
+
end
|
59
|
+
|
60
|
+
def run(*args)
|
61
|
+
build(*args) unless built
|
62
|
+
result = valid? ? try('execute') : nil
|
63
|
+
outcome(result)
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid?
|
67
|
+
@errors.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def outcome(result = nil)
|
71
|
+
Objective::Outcome.new(
|
72
|
+
success: valid?,
|
73
|
+
result: result,
|
74
|
+
errors: @errors,
|
75
|
+
inputs: inputs
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def add_error(key, kind, message = nil)
|
82
|
+
raise(ArgumentError, 'Invalid kind') unless kind.is_a?(Symbol)
|
83
|
+
|
84
|
+
@errors ||= Objective::Errors::ErrorHash.new
|
85
|
+
@errors.tap do |root_error_hash|
|
86
|
+
path = key.to_s.split('.')
|
87
|
+
last = path.pop
|
88
|
+
|
89
|
+
inner = path.inject(root_error_hash) do |current_error_hash, path_key|
|
90
|
+
current_error_hash[path_key] ||= Objective::Errors::ErrorHash.new
|
91
|
+
end
|
92
|
+
|
93
|
+
inner[last] = Objective::Errors::ErrorAtom.new(key, kind, message: message)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def merge_errors(hash)
|
98
|
+
return unless hash.any?
|
99
|
+
@errors ||= Objective::Errors::ErrorHash.new
|
100
|
+
@errors.merge!(hash)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/objective.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ostruct'
|
3
|
+
require 'date'
|
4
|
+
require 'time'
|
5
|
+
require 'bigdecimal'
|
6
|
+
require 'bigdecimal/util'
|
7
|
+
|
8
|
+
require 'active_support/concern'
|
9
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
10
|
+
require 'active_support/core_ext/object/try'
|
11
|
+
require 'active_support/core_ext/object/blank'
|
12
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
13
|
+
require 'active_support/core_ext/hash/deep_merge'
|
14
|
+
require 'active_support/core_ext/array/wrap'
|
15
|
+
require 'active_support/core_ext/string/inflections'
|
16
|
+
require 'active_support/core_ext/integer/inflections'
|
17
|
+
|
18
|
+
require 'objective/allow'
|
19
|
+
require 'objective/deny'
|
20
|
+
require 'objective/discard'
|
21
|
+
require 'objective/invalid'
|
22
|
+
require 'objective/none'
|
23
|
+
|
24
|
+
require 'objective/errors/error_atom'
|
25
|
+
require 'objective/errors/error_hash'
|
26
|
+
require 'objective/errors/error_array'
|
27
|
+
require 'objective/errors/error_message_creator'
|
28
|
+
require 'objective/errors/validation_error'
|
29
|
+
|
30
|
+
require 'objective/filter'
|
31
|
+
require 'objective/filters'
|
32
|
+
require 'objective/filters/any_filter'
|
33
|
+
require 'objective/filters/array_filter'
|
34
|
+
require 'objective/filters/boolean_filter'
|
35
|
+
require 'objective/filters/date_filter'
|
36
|
+
require 'objective/filters/decimal_filter'
|
37
|
+
require 'objective/filters/duck_filter'
|
38
|
+
require 'objective/filters/file_filter'
|
39
|
+
require 'objective/filters/float_filter'
|
40
|
+
require 'objective/filters/hash_filter'
|
41
|
+
require 'objective/filters/integer_filter'
|
42
|
+
require 'objective/filters/model_filter'
|
43
|
+
require 'objective/filters/root_filter'
|
44
|
+
require 'objective/filters/string_filter'
|
45
|
+
require 'objective/filters/time_filter'
|
46
|
+
|
47
|
+
require 'objective/unit'
|
48
|
+
require 'objective/outcome'
|
49
|
+
|
50
|
+
module Objective
|
51
|
+
mattr_accessor :error_message_creator
|
52
|
+
self.error_message_creator = Errors::ErrorMessageCreator.new
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'simple_unit'
|
4
|
+
|
5
|
+
describe 'Objective - defaults' do
|
6
|
+
class DefaultUnit
|
7
|
+
include Objective::Unit
|
8
|
+
filter do
|
9
|
+
string :name, none: 'Bob Jones'
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
inputs.to_h
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should have a default if no value is passed' do
|
18
|
+
outcome = DefaultUnit.run
|
19
|
+
assert_equal({ name: 'Bob Jones' }, outcome.result)
|
20
|
+
assert_equal true, outcome.success
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have the passed value if a value is passed' do
|
24
|
+
outcome = DefaultUnit.run(name: 'Fred')
|
25
|
+
assert_equal true, outcome.success
|
26
|
+
assert_equal({ name: 'Fred' }, outcome.result)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be an error if nil is passed on a required field with a default' do
|
30
|
+
outcome = DefaultUnit.run(name: nil)
|
31
|
+
assert_equal false, outcome.success
|
32
|
+
end
|
33
|
+
end
|
data/spec/errors_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Objective - errors' do
|
5
|
+
class GivesErrors
|
6
|
+
include Objective::Unit
|
7
|
+
filter do
|
8
|
+
string :str1
|
9
|
+
string :str2, in: %w(opt1 opt2 opt3)
|
10
|
+
integer :int1, none: ALLOW
|
11
|
+
|
12
|
+
hash :hash1, none: ALLOW do
|
13
|
+
boolean :bool1
|
14
|
+
boolean :bool2
|
15
|
+
end
|
16
|
+
|
17
|
+
array :arr1, none: ALLOW do
|
18
|
+
integer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute
|
23
|
+
inputs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns an ErrorHash as the top level error object, and ErrorAtom\'s inside' do
|
28
|
+
o = GivesErrors.run(hash1: 1, arr1: 'bob')
|
29
|
+
assert !o.success
|
30
|
+
assert o.errors.is_a?(Objective::Errors::ErrorHash)
|
31
|
+
assert o.errors[:str1].is_a?(Objective::Errors::ErrorAtom)
|
32
|
+
assert o.errors[:str2].is_a?(Objective::Errors::ErrorAtom)
|
33
|
+
assert_nil o.errors[:int1]
|
34
|
+
assert o.errors[:hash1].is_a?(Objective::Errors::ErrorAtom)
|
35
|
+
assert o.errors[:arr1].is_a?(Objective::Errors::ErrorAtom)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns an ErrorHash for nested hashes' do
|
39
|
+
o = GivesErrors.run(hash1: { bool1: 'ooooo' })
|
40
|
+
|
41
|
+
assert !o.success
|
42
|
+
assert o.errors.is_a?(Objective::Errors::ErrorHash)
|
43
|
+
assert o.errors[:hash1].is_a?(Objective::Errors::ErrorHash)
|
44
|
+
assert o.errors[:hash1][:bool1].is_a?(Objective::Errors::ErrorAtom)
|
45
|
+
assert o.errors[:hash1][:bool2].is_a?(Objective::Errors::ErrorAtom)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns an ErrorArray for errors in arrays' do
|
49
|
+
o = GivesErrors.run(str1: 'a', str2: 'opt1', arr1: ['bob', 1, 'sally'])
|
50
|
+
|
51
|
+
assert !o.success
|
52
|
+
assert o.errors.is_a?(Objective::Errors::ErrorHash)
|
53
|
+
assert o.errors[:arr1].is_a?(Objective::Errors::ErrorArray)
|
54
|
+
assert o.errors[:arr1][0].is_a?(Objective::Errors::ErrorAtom)
|
55
|
+
assert_nil o.errors[:arr1][1]
|
56
|
+
assert o.errors[:arr1][2].is_a?(Objective::Errors::ErrorAtom)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'titleizes keys' do
|
60
|
+
atom = Objective::Errors::ErrorAtom.new(:newsletter_subscription, :boolean)
|
61
|
+
assert_equal 'Newsletter Subscription must be a boolean', atom.message
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'Bunch o errors' do
|
65
|
+
before do
|
66
|
+
@outcome = GivesErrors.run(
|
67
|
+
str1: '', str2: 'opt9', int1: 'zero', hash1: { bool1: 'bob' }, arr1: ['bob', 1, 'sally']
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'gives coded errors' do
|
72
|
+
expected = {
|
73
|
+
'str1' => :empty,
|
74
|
+
'str2' => :in,
|
75
|
+
'int1' => :integer,
|
76
|
+
'hash1' => { 'bool1' => :boolean, 'bool2' => :required },
|
77
|
+
'arr1' => [:integer, nil, :integer]
|
78
|
+
}
|
79
|
+
|
80
|
+
assert_equal expected, @outcome.errors.codes
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'gives messages' do
|
84
|
+
expected = {
|
85
|
+
'str1' => 'Str1 cannot be empty',
|
86
|
+
'str2' => 'Str2 is not an available option',
|
87
|
+
'int1' => 'Int1 must be an integer',
|
88
|
+
'hash1' => {
|
89
|
+
'bool1' => 'Bool1 must be a boolean',
|
90
|
+
'bool2' => 'Bool2 is required'
|
91
|
+
},
|
92
|
+
'arr1' => ['1st Arr1 must be an integer', nil, '3rd Arr1 must be an integer']
|
93
|
+
}
|
94
|
+
|
95
|
+
assert_equal expected, @outcome.errors.message
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'can flatten those messages' do
|
99
|
+
expected = [
|
100
|
+
'Str1 cannot be empty',
|
101
|
+
'Str2 is not an available option',
|
102
|
+
'Int1 must be an integer',
|
103
|
+
'Bool1 must be a boolean',
|
104
|
+
'Bool2 is required',
|
105
|
+
'1st Arr1 must be an integer',
|
106
|
+
'3rd Arr1 must be an integer'
|
107
|
+
]
|
108
|
+
|
109
|
+
assert_equal expected, @outcome.errors.message_list
|
110
|
+
expected.each { |e| assert @outcome.errors.message_list.include?(e) }
|
111
|
+
|
112
|
+
assert_equal expected.size, @outcome.errors.message_list.size
|
113
|
+
expected.each { |e| assert @outcome.errors.message_list.include?(e) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class WithShrinkingArray
|
118
|
+
include Objective::Unit
|
119
|
+
filter do
|
120
|
+
array :arr_one do
|
121
|
+
integer nils: DISCARD
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'returns an ErrorArray for errors in arrays' do
|
127
|
+
o = WithShrinkingArray.run(arr_one: [nil, 1, 'sally'])
|
128
|
+
|
129
|
+
assert !o.success
|
130
|
+
assert o.errors.is_a?(Objective::Errors::ErrorHash)
|
131
|
+
assert o.errors[:arr_one].is_a?(Objective::Errors::ErrorArray)
|
132
|
+
assert_nil o.errors[:arr_one][0]
|
133
|
+
assert o.errors[:arr_one][1].is_a?(Objective::Errors::ErrorAtom)
|
134
|
+
end
|
135
|
+
end
|