Class: Karafka::Core::Contractable::Contract
- Inherits:
-
Object
- Object
- Karafka::Core::Contractable::Contract
- Extended by:
- Karafka::Core::Configurable
- Defined in:
- lib/karafka/core/contractable/contract.rb
Overview
This contract does NOT support rules inheritance as it was never needed in Karafka
Base contract for all the contracts that check data format
Class Attribute Summary collapse
-
.rules ⇒ Array<Rule>
readonly
All the validation rules defined for a given contract.
Class Method Summary collapse
-
.nested(path) ⇒ Object
Allows for definition of a scope/namespace for nested validations.
- .optional(*keys, &block) ⇒ Object
-
.required(*keys, &block) ⇒ Object
Defines a rule for a required field (required means, that will automatically create an error if missing).
-
.virtual(&block) ⇒ Object
Defines a virtual rule that validates the whole data rather than a single key.
Instance Method Summary collapse
-
#call(data, scope: EMPTY_ARRAY) ⇒ Result
Runs the validation.
-
#validate!(data, error_class, scope: EMPTY_ARRAY) ⇒ Boolean
True.
Methods included from Karafka::Core::Configurable
Class Attribute Details
.rules ⇒ Array<Rule> (readonly)
Returns all the validation rules defined for a given contract.
30 31 32 |
# File 'lib/karafka/core/contractable/contract.rb', line 30 def rules @rules end |
Class Method Details
.nested(path) ⇒ Object
Allows for definition of a scope/namespace for nested validations
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/karafka/core/contractable/contract.rb', line 40 def nested(path, &) init_accu @nested << path begin instance_eval(&) ensure # Always pop, even if the block raises. Otherwise a rescued exception during # contract definition would leave `path` on @nested and prefix it onto every rule # defined afterwards. @nested.pop end end |
.optional(*keys, &block) ⇒ Object
65 66 67 68 |
# File 'lib/karafka/core/contractable/contract.rb', line 65 def optional(*keys, &block) init_accu @rules << Rule.new(@nested + keys, :optional, block).freeze end |
.required(*keys, &block) ⇒ Object
Defines a rule for a required field (required means, that will automatically create an error if missing)
58 59 60 61 |
# File 'lib/karafka/core/contractable/contract.rb', line 58 def required(*keys, &block) init_accu @rules << Rule.new(@nested + keys, :required, block).freeze end |
.virtual(&block) ⇒ Object
The returned error Array is owned by the contract: #call prepends the current
scope onto each pair in place and collects them, so a rule must return a freshly
built Array on every call. Returning a memoized, shared or frozen Array is not
supported -- in-place scoping would accumulate the prefix across validations (or
raise FrozenError). Build the result in the block (e.g. [[%i[id], :invalid]])
rather than returning a constant.
Defines a virtual rule that validates the whole data rather than a single key. Unlike
required/optional, the block receives the full data and returns its own errors.
83 84 85 86 |
# File 'lib/karafka/core/contractable/contract.rb', line 83 def virtual(&block) init_accu @rules << Rule.new([], :virtual, block).freeze end |
Instance Method Details
#call(data, scope: EMPTY_ARRAY) ⇒ Result
Runs the validation
The per-rule handling is inlined instead of dispatching to per-type methods because
this runs per rule per validation, including the per-message validations in
WaterDrop. Required and optional rules share the whole flow except the missing-key
handling. DIG_MISS is compared via #equal? so we never dispatch #== to the
validated (user-provided) values.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/karafka/core/contractable/contract.rb', line 109 def call(data, scope: EMPTY_ARRAY) errors = [] # A non-Hash root has no keys to dig; resolve it once here so #dig stays on its lean # fast path and is only invoked with a Hash. Non-Hash data makes every required rule # report missing, consistent with the non-Hash intermediate handling inside #dig. data_is_hash = data.is_a?(Hash) self.class.rules.each do |rule| if rule.type == :virtual result = rule.validator.call(data, errors, self) # A virtual rule signals "no errors" with any non-Array result (true, but also a # falsy `nil`/`false` returned by e.g. `condition && [[...], :err]`). Only an Array # of error pairs is iterated; previously a `false` return reached `false.each` and # raised NoMethodError (a `nil` return was already tolerated by the safe navigation). next unless result.is_a?(Array) # Apply the scope prefix in place on the rule's returned pairs and collect them # directly. Per the `virtual` contract the rule hands back a freshly built Array # each call (see `DSL#virtual`), so mutating it here is safe and avoids allocating a # new pair per error. result.each do |sub_result| sub_result[0] = scope + sub_result[0] end errors.push(*result) else for_checking = data_is_hash ? dig(data, rule.path) : DIG_MISS if DIG_MISS.equal?(for_checking) errors << [scope + rule.path, :missing] if rule.type == :required else result = rule.validator.call(for_checking, data, errors, self) next if result == true errors << [scope + rule.path, result || :format] end end end return Result.success if errors.empty? Result.new(errors, self) end |
#validate!(data, error_class, scope: EMPTY_ARRAY) ⇒ Boolean
Returns true.
163 164 165 166 167 168 169 |
# File 'lib/karafka/core/contractable/contract.rb', line 163 def validate!(data, error_class, scope: EMPTY_ARRAY) result = call(data, scope: scope) return true if result.success? raise error_class, result.errors end |