Skip to content

Commit 70d2e59

Browse files
committed
Raise errors for invalid combinations of interval
Reject illogical combinations of rule types with intervals that would never align with any occurrences.
1 parent c6ef94c commit 70d2e59

20 files changed

+211
-6
lines changed

lib/ice_cube/rules/daily_rule.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ def initialize(interval = 1)
1111
reset
1212
end
1313

14+
def verify_alignment(value, freq, rule_part)
15+
return unless freq == :wday || freq == :day
16+
return unless @validations[:interval]
17+
18+
interval_validation = @validations[:interval].first
19+
interval_value = (rule_part == :interval) ? value : interval_validation.interval
20+
return if interval_value == 1
21+
22+
if freq == :wday
23+
return if (interval_value % 7).zero?
24+
return if Array(@validations[:day]).empty?
25+
message = "day can only be used with multiples of interval(7)"
26+
else
27+
(fixed_validation = other_fixed_value_validations.first) or return
28+
message = "#{fixed_validation.key} can only be used with interval(1)"
29+
end
30+
31+
yield ArgumentError.new(message)
32+
end
33+
1434
end
1535

1636
end

lib/ice_cube/rules/monthly_rule.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ def initialize(interval = 1)
1111
reset
1212
end
1313

14+
def verify_alignment(value, freq, rule_part)
15+
return unless freq == :month
16+
return unless @validations[:interval]
17+
18+
interval_validation = @validations[:interval].first
19+
interval_value = (rule_part == :interval) ? value : interval_validation.interval
20+
return if interval_value == 1 || (interval_value % 12).zero?
21+
return if other_fixed_value_validations.empty?
22+
23+
message = "month_of_year can only be used with interval(1) or multiples of interval(12)"
24+
yield ArgumentError.new(message)
25+
end
26+
1427
end
1528

1629
end

lib/ice_cube/validated_rule.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ def other_interval_validations
5151
Array(@validations[base_interval_validation.type])
5252
end
5353

54+
def other_fixed_value_validations
55+
@validations.values.flatten.select { |v|
56+
interval_type = (v.type == :wday ? :day : v.type)
57+
v.class < Validations::FixedValue &&
58+
interval_type == base_interval_validation.type
59+
}
60+
end
61+
5462
# Compute the next time after (or including) the specified time in respect
5563
# to the given start time
5664
def next_time(time, start_time, closing_time)
@@ -185,6 +193,29 @@ def validation_names
185193
VALIDATION_ORDER & @validations.keys
186194
end
187195

196+
def verify_alignment(value, freq, rule_part, options={})
197+
@validations[:interval] or return
198+
interval_validation = @validations[:interval].first
199+
interval_validation.type == freq or return
200+
fixed_validations = other_fixed_value_validations
201+
(last_validation = fixed_validations.min_by(&:value)) or return
202+
203+
alignment = (value - last_validation.value) % interval_validation.interval
204+
return if alignment.zero?
205+
206+
validation_values = fixed_validations.map(&:value).join(', ')
207+
if rule_part == :interval
208+
message = "interval(#{value}) " \
209+
"must be a multiple of " \
210+
"intervals in #{last_validation.key}(#{validation_values})"
211+
else
212+
message = "intervals in #{last_validation.key}(#{validation_values}, #{value}) " \
213+
"must be multiples of " \
214+
"interval(#{interval_validation.interval})"
215+
end
216+
yield ArgumentError.new(message)
217+
end
218+
188219
end
189220

190221
end

lib/ice_cube/validations/daily_interval.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ module Validations::DailyInterval
44

55
# Add a new interval validation
66
def interval(interval)
7-
@interval = normalized_interval(interval)
7+
interval = normalized_interval(interval)
8+
verify_alignment(interval, :wday, :interval) { |error| raise error }
9+
verify_alignment(interval, :day, :interval) { |error| raise error }
10+
11+
@interval = interval
812
replace_validations_for(:interval, [Validation.new(@interval)])
913
clobber_base_validations(:wday, :day)
1014
self

lib/ice_cube/validations/day.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ def day(*days)
1010
raise ArgumentError, "expecting Integer or Symbol value for day, got #{day.inspect}"
1111
end
1212
day = TimeUtil.sym_to_wday(day)
13+
verify_alignment(day, :wday, :day) { |error| raise error }
14+
1315
validations_for(:day) << Validation.new(day)
1416
end
1517
clobber_base_validations(:wday, :day)
@@ -25,6 +27,10 @@ def initialize(day)
2527
@day = day
2628
end
2729

30+
def key
31+
:day
32+
end
33+
2834
def type
2935
:wday
3036
end

lib/ice_cube/validations/day_of_month.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def day_of_month(*days)
77
unless day.is_a?(Integer)
88
raise ArgumentError, "expecting Integer value for day, got #{day.inspect}"
99
end
10+
verify_alignment(day, :day, :day_of_month) { |error| raise error }
1011
validations_for(:day_of_month) << Validation.new(day)
1112
end
1213
clobber_base_validations(:day, :wday)
@@ -22,6 +23,10 @@ def initialize(day)
2223
@day = day
2324
end
2425

26+
def key
27+
:day_of_month
28+
end
29+
2530
def type
2631
:day
2732
end

lib/ice_cube/validations/hour_of_day.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ def hour_of_day(*hours)
88
unless hour.is_a?(Integer)
99
raise ArgumentError, "expecting Integer value for hour, got #{hour.inspect}"
1010
end
11+
12+
verify_alignment(hour, :hour, :hour_of_day) { |error| raise error }
13+
1114
validations_for(:hour_of_day) << Validation.new(hour)
1215
end
1316
clobber_base_validations(:hour)
@@ -23,6 +26,10 @@ def initialize(hour)
2326
@hour = hour
2427
end
2528

29+
def key
30+
:hour_of_day
31+
end
32+
2633
def type
2734
:hour
2835
end

lib/ice_cube/validations/hourly_interval.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module IceCube
33
module Validations::HourlyInterval
44

55
def interval(interval)
6+
verify_alignment(interval, :hour, :interval) { |error| raise error }
7+
68
@interval = normalized_interval(interval)
79
replace_validations_for(:interval, [Validation.new(@interval)])
810
clobber_base_validations(:hour)

lib/ice_cube/validations/minute_of_hour.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ def minute_of_hour(*minutes)
77
unless minute.is_a?(Integer)
88
raise ArgumentError, "expecting Integer value for minute, got #{minute.inspect}"
99
end
10+
11+
verify_alignment(minute, :min, :minute_of_hour) { |error| raise error }
12+
1013
validations_for(:minute_of_hour) << Validation.new(minute)
1114
end
1215
clobber_base_validations(:min)
@@ -22,6 +25,10 @@ def initialize(minute)
2225
@minute = minute
2326
end
2427

28+
def key
29+
:minute_of_hour
30+
end
31+
2532
def type
2633
:min
2734
end

lib/ice_cube/validations/minutely_interval.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module IceCube
33
module Validations::MinutelyInterval
44

55
def interval(interval)
6+
verify_alignment(interval, :min, :interval) { |error| raise error }
7+
68
@interval = normalized_interval(interval)
79
replace_validations_for(:interval, [Validation.new(@interval)])
810
clobber_base_validations(:min)

0 commit comments

Comments
 (0)