Class: CircuitBreaker::Rules::DSL

Inherits:
Object
  • Object
show all
Defined in:
lib/circuit_breaker/rules.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDSL

Returns a new instance of DSL.



40
41
42
43
44
# File 'lib/circuit_breaker/rules.rb', line 40

def initialize
  @rules = {}
  @descriptions = {}
  @context = nil
end

Class Method Details

.define(&block) ⇒ Object



46
47
48
49
50
# File 'lib/circuit_breaker/rules.rb', line 46

def self.define(&block)
  dsl = new
  dsl.instance_eval(&block) if block_given?
  dsl
end

Instance Method Details

#all(*rules) ⇒ Object



176
177
178
179
180
181
# File 'lib/circuit_breaker/rules.rb', line 176

def all(*rules)
  ->(token) { 
    results = rules.map { |rule| rule.call(token) }
    results.reduce(RuleResult.new(true)) { |acc, result| acc & (result.is_a?(RuleResult) ? result : RuleResult.new(result)) }
  }
end

#any(*rules) ⇒ Object



183
184
185
186
187
188
# File 'lib/circuit_breaker/rules.rb', line 183

def any(*rules)
  ->(token) { 
    results = rules.map { |rule| rule.call(token) }
    results.reduce(RuleResult.new(false)) { |acc, result| acc | (result.is_a?(RuleResult) ? result : RuleResult.new(result)) }
  }
end

#chain(token) ⇒ Object



76
77
78
# File 'lib/circuit_breaker/rules.rb', line 76

def chain(token)
  RuleChain.new(self, token)
end

#contextObject



96
97
98
# File 'lib/circuit_breaker/rules.rb', line 96

def context
  @context
end

#custom(field = nil, message = nil, &block) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/circuit_breaker/rules.rb', line 198

def custom(field = nil, message = nil, &block)
  if field
    ->(token) { 
      result = block.call(token.send(field))
      result.is_a?(RuleResult) ? result : RuleResult.new(result, message ? [message] : [])
    }
  else
    ->(token) {
      result = block.call(token)
      result.is_a?(RuleResult) ? result : RuleResult.new(result, message ? [message] : [])
    }
  end
end

#depends_on(field, other_field, &block) ⇒ Object



212
213
214
215
216
217
218
219
220
# File 'lib/circuit_breaker/rules.rb', line 212

def depends_on(field, other_field, &block)
  ->(token) {
    other_value = token.send(other_field)
    return RuleResult.new(true) if other_value.nil?
    
    result = block.call(token.send(field), other_value)
    result.is_a?(RuleResult) ? result : RuleResult.new(result, ["#{field} dependency on #{other_field} failed"])
  }
end

#description(name) ⇒ Object



84
85
86
# File 'lib/circuit_breaker/rules.rb', line 84

def description(name)
  @descriptions[name]
end

#different_values(field1, field2) ⇒ Object



109
110
111
112
113
114
115
116
117
# File 'lib/circuit_breaker/rules.rb', line 109

def different_values(field1, field2)
  ->(token) {
    val1, val2 = token.send(field1), token.send(field2)
    result = presence(field1).call(token).valid? && 
            presence(field2).call(token).valid? && 
            val1 != val2
    RuleResult.new(result, result ? [] : ["#{field1} must be different from #{field2}"])
  }
end

#evaluate(rule_name, token) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/circuit_breaker/rules.rb', line 57

def evaluate(rule_name, token)
  raise RuleError, "Unknown rule: #{rule_name}" unless @rules.key?(rule_name)
  
  puts "Evaluating rule '#{rule_name}' for token #{token.id}"
  puts "  Context: #{@context.inspect}"
  result = @rules[rule_name].call(token)
  puts "  Result: #{result.inspect}"
  case result
  when RuleResult
    result.valid?
  when true, false
    result
  else
    !!result
  end
rescue StandardError => e
  raise RuleError, "Rule '#{rule_name}' failed for token #{token.id}: #{e.message}"
end

#json_schema(field, schema) ⇒ Object



148
149
150
151
152
153
154
155
156
157
# File 'lib/circuit_breaker/rules.rb', line 148

def json_schema(field, schema)
  ->(token) {
    begin
      JSON::Validator.validate!(schema, token.send(field))
      RuleResult.new(true)
    rescue JSON::Schema::ValidationError => e
      RuleResult.new(false, [e.message])
    end
  }
end

#length(field, options = {}) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/circuit_breaker/rules.rb', line 133

def length(field, options = {})
  ->(token) {
    val = token.send(field).to_s
    min_valid = !options[:min] || val.length >= options[:min]
    max_valid = !options[:max] || val.length <= options[:max]
    result = min_valid && max_valid
    
    errors = []
    errors << "#{field} must be at least #{options[:min]} characters" if !min_valid
    errors << "#{field} must be at most #{options[:max]} characters" if !max_valid
    
    RuleResult.new(result, errors)
  }
end

#matches(field, pattern, message = nil) ⇒ Object



119
120
121
122
123
124
# File 'lib/circuit_breaker/rules.rb', line 119

def matches(field, pattern, message = nil)
  ->(token) { 
    result = token.send(field).to_s.match?(pattern)
    RuleResult.new(result, result ? [] : [message || "#{field} does not match pattern"])
  }
end

#none(*rules) ⇒ Object



190
191
192
193
194
195
196
# File 'lib/circuit_breaker/rules.rb', line 190

def none(*rules)
  ->(token) { 
    results = rules.map { |rule| rule.call(token) }
    result = results.none? { |r| r.is_a?(RuleResult) ? r.valid? : r }
    RuleResult.new(result, result ? [] : ["none of the rules should pass"])
  }
end

#numericality(field, options = {}) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/circuit_breaker/rules.rb', line 159

def numericality(field, options = {})
  ->(token) {
    val = token.send(field)
    return RuleResult.new(false, ["#{field} must be a number"]) unless val.is_a?(Numeric)
    
    errors = []
    errors << "must be greater than #{options[:greater_than]}" if options[:greater_than] && !(val > options[:greater_than])
    errors << "must be greater than or equal to #{options[:greater_than_or_equal_to]}" if options[:greater_than_or_equal_to] && !(val >= options[:greater_than_or_equal_to])
    errors << "must be less than #{options[:less_than]}" if options[:less_than] && !(val < options[:less_than])
    errors << "must be less than or equal to #{options[:less_than_or_equal_to]}" if options[:less_than_or_equal_to] && !(val <= options[:less_than_or_equal_to])
    errors << "must be equal to #{options[:equal_to]}" if options[:equal_to] && val != options[:equal_to]
    errors << "must not be equal to #{options[:other_than]}" if options[:other_than] && val == options[:other_than]
    
    RuleResult.new(errors.empty?, errors.map { |e| "#{field} #{e}" })
  }
end

#one_of(field, values) ⇒ Object



126
127
128
129
130
131
# File 'lib/circuit_breaker/rules.rb', line 126

def one_of(field, values)
  ->(token) { 
    result = values.include?(token.send(field))
    RuleResult.new(result, result ? [] : ["#{field} must be one of: #{values.join(', ')}"])
  }
end

#presence(field) ⇒ Object

Helper methods for common rule conditions



101
102
103
104
105
106
107
# File 'lib/circuit_breaker/rules.rb', line 101

def presence(field)
  ->(token) { 
    val = token.send(field)
    result = !val.nil? && (!val.respond_to?(:empty?) || !val.empty?)
    RuleResult.new(result, result ? [] : ["#{field} must be present"])
  }
end

#rule(name, desc = nil, &block) ⇒ Object



52
53
54
55
# File 'lib/circuit_breaker/rules.rb', line 52

def rule(name, desc = nil, &block)
  @rules[name] = block
  @descriptions[name] = desc if desc
end

#rulesObject



80
81
82
# File 'lib/circuit_breaker/rules.rb', line 80

def rules
  @rules.keys
end

#with_context(context) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/circuit_breaker/rules.rb', line 88

def with_context(context)
  old_context = @context
  @context = context
  yield
ensure
  @context = old_context
end