waterdrop 2.3.0 → 2.3.3
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +10 -38
- data/README.md +2 -2
- data/config/errors.yml +28 -5
- data/lib/waterdrop/config.rb +3 -1
- data/lib/waterdrop/configurable/leaf.rb +8 -0
- data/lib/waterdrop/configurable/node.rb +100 -0
- data/lib/waterdrop/configurable.rb +71 -0
- data/lib/waterdrop/contractable/contract.rb +183 -0
- data/lib/waterdrop/contractable/result.rb +57 -0
- data/lib/waterdrop/contractable/rule.rb +8 -0
- data/lib/waterdrop/contractable.rb +13 -0
- data/lib/waterdrop/contracts/config.rb +25 -15
- data/lib/waterdrop/contracts/message.rb +40 -20
- data/lib/waterdrop/instrumentation/vendors/datadog/listener.rb +11 -5
- data/lib/waterdrop/patches/rdkafka/producer.rb +9 -1
- data/lib/waterdrop/version.rb +1 -1
- data/lib/waterdrop.rb +3 -2
- data/waterdrop.gemspec +0 -2
- data.tar.gz.sig +0 -0
- metadata +10 -32
- metadata.gz.sig +0 -0
- data/lib/waterdrop/contracts/base.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1482f0d2955c43c2ab479372c9cbb4a8c039723121c26b2ff75d0e26700d71e
|
|
4
|
+
data.tar.gz: 1bb3e675c0c632d4940b2b661bdb75a74c44358a7e53fa9dc7051fdad62218b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8921844cc44625916974c63478562b66a7f30a833b6f95e71e8c3448109d673bd3520a8a48bdf0fe7296e5e00b988c15c3dd92cab2e11ee37914f667966a14de
|
|
7
|
+
data.tar.gz: 6c6038c4f9a718d107fd885e66bf29cc3dde09c6f6e0be2ba41651f3494672ddd925e71574f53ec8ec70d4e766024018f73a43a5fadf99402117ac3a0751b0c0
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.1.
|
|
1
|
+
3.1.2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# WaterDrop changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.3 (2022-07-18)
|
|
4
|
+
- Replace `dry-validation` with home-brew validation layer and drop direct dependency on `dry-validation`.
|
|
5
|
+
- Remove indirect dependency on dry-configurable from DataDog listener (no changes required).
|
|
6
|
+
|
|
7
|
+
## 2.3.2 (2022-07-17)
|
|
8
|
+
- Replace `dry-configurable` with home-brew config and drop direct dependency on `dry-configurable`.
|
|
9
|
+
|
|
10
|
+
## 2.3.1 (2022-06-17)
|
|
11
|
+
- Update rdkafka patches to align with `0.12.0` and `0.11.1` support.
|
|
12
|
+
|
|
3
13
|
## 2.3.0 (2022-04-03)
|
|
4
14
|
- Rename StdoutListener to LoggerListener (#240)
|
|
5
15
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
waterdrop (2.3.
|
|
4
|
+
waterdrop (2.3.3)
|
|
5
5
|
concurrent-ruby (>= 1.1)
|
|
6
|
-
dry-configurable (~> 0.13)
|
|
7
6
|
dry-monitor (~> 0.5)
|
|
8
|
-
dry-validation (~> 1.7)
|
|
9
7
|
rdkafka (>= 0.10)
|
|
10
8
|
zeitwerk (~> 2.3)
|
|
11
9
|
|
|
12
10
|
GEM
|
|
13
11
|
remote: https://rubygems.org/
|
|
14
12
|
specs:
|
|
15
|
-
activesupport (7.0.
|
|
13
|
+
activesupport (7.0.3)
|
|
16
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
17
15
|
i18n (>= 1.6, < 2)
|
|
18
16
|
minitest (>= 5.1)
|
|
@@ -21,54 +19,28 @@ GEM
|
|
|
21
19
|
concurrent-ruby (1.1.10)
|
|
22
20
|
diff-lcs (1.5.0)
|
|
23
21
|
docile (1.4.0)
|
|
24
|
-
dry-configurable (0.
|
|
22
|
+
dry-configurable (0.15.0)
|
|
25
23
|
concurrent-ruby (~> 1.0)
|
|
26
24
|
dry-core (~> 0.6)
|
|
27
|
-
dry-
|
|
28
|
-
concurrent-ruby (~> 1.0)
|
|
29
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
|
30
|
-
dry-core (0.7.1)
|
|
25
|
+
dry-core (0.8.0)
|
|
31
26
|
concurrent-ruby (~> 1.0)
|
|
32
27
|
dry-events (0.3.0)
|
|
33
28
|
concurrent-ruby (~> 1.0)
|
|
34
29
|
dry-core (~> 0.5, >= 0.5)
|
|
35
|
-
dry-
|
|
36
|
-
dry-initializer (3.1.1)
|
|
37
|
-
dry-logic (1.2.0)
|
|
38
|
-
concurrent-ruby (~> 1.0)
|
|
39
|
-
dry-core (~> 0.5, >= 0.5)
|
|
40
|
-
dry-monitor (0.5.0)
|
|
30
|
+
dry-monitor (0.6.1)
|
|
41
31
|
dry-configurable (~> 0.13, >= 0.13.0)
|
|
42
32
|
dry-core (~> 0.5, >= 0.5)
|
|
43
33
|
dry-events (~> 0.2)
|
|
44
|
-
|
|
45
|
-
concurrent-ruby (~> 1.0)
|
|
46
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
|
47
|
-
dry-core (~> 0.5, >= 0.5)
|
|
48
|
-
dry-initializer (~> 3.0)
|
|
49
|
-
dry-logic (~> 1.0)
|
|
50
|
-
dry-types (~> 1.5)
|
|
51
|
-
dry-types (1.5.1)
|
|
52
|
-
concurrent-ruby (~> 1.0)
|
|
53
|
-
dry-container (~> 0.3)
|
|
54
|
-
dry-core (~> 0.5, >= 0.5)
|
|
55
|
-
dry-inflector (~> 0.1, >= 0.1.2)
|
|
56
|
-
dry-logic (~> 1.0, >= 1.0.2)
|
|
57
|
-
dry-validation (1.8.0)
|
|
58
|
-
concurrent-ruby (~> 1.0)
|
|
59
|
-
dry-container (~> 0.7, >= 0.7.1)
|
|
60
|
-
dry-core (~> 0.5, >= 0.5)
|
|
61
|
-
dry-initializer (~> 3.0)
|
|
62
|
-
dry-schema (~> 1.9, >= 1.9.1)
|
|
34
|
+
zeitwerk (~> 2.5)
|
|
63
35
|
factory_bot (6.2.1)
|
|
64
36
|
activesupport (>= 5.0.0)
|
|
65
37
|
ffi (1.15.5)
|
|
66
38
|
i18n (1.10.0)
|
|
67
39
|
concurrent-ruby (~> 1.0)
|
|
68
40
|
mini_portile2 (2.8.0)
|
|
69
|
-
minitest (5.
|
|
41
|
+
minitest (5.16.0)
|
|
70
42
|
rake (13.0.6)
|
|
71
|
-
rdkafka (0.
|
|
43
|
+
rdkafka (0.12.0)
|
|
72
44
|
ffi (~> 1.15)
|
|
73
45
|
mini_portile2 (~> 2.6)
|
|
74
46
|
rake (> 12)
|
|
@@ -93,7 +65,7 @@ GEM
|
|
|
93
65
|
simplecov_json_formatter (0.1.4)
|
|
94
66
|
tzinfo (2.0.4)
|
|
95
67
|
concurrent-ruby (~> 1.0)
|
|
96
|
-
zeitwerk (2.
|
|
68
|
+
zeitwerk (2.6.0)
|
|
97
69
|
|
|
98
70
|
PLATFORMS
|
|
99
71
|
arm64-darwin-21
|
|
@@ -107,4 +79,4 @@ DEPENDENCIES
|
|
|
107
79
|
waterdrop!
|
|
108
80
|
|
|
109
81
|
BUNDLED WITH
|
|
110
|
-
2.3.
|
|
82
|
+
2.3.15
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# WaterDrop
|
|
2
2
|
|
|
3
|
-
**Note**: Documentation presented here refers to WaterDrop `2.
|
|
3
|
+
**Note**: Documentation presented here refers to WaterDrop `2.x`.
|
|
4
4
|
|
|
5
|
-
WaterDrop `2.
|
|
5
|
+
WaterDrop `2.x` does **not** work with Karafka `1.*` and aims to either work as a standalone producer outside of Karafka `1.*` ecosystem or as a part of soon to be released Karafka `2.0.*`.
|
|
6
6
|
|
|
7
7
|
Please refer to [this](https://github.com/karafka/waterdrop/tree/1.4) branch and its documentation for details about WaterDrop `1.*` usage.
|
|
8
8
|
|
data/config/errors.yml
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
en:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
validations:
|
|
3
|
+
config:
|
|
4
|
+
missing: must be present
|
|
5
|
+
logger_format: must be present
|
|
6
|
+
deliver_format: must be boolean
|
|
7
|
+
id_format: must be a non-empty string
|
|
8
|
+
max_payload_size_format: must be an integer that is equal or bigger than 1
|
|
9
|
+
wait_timeout_format: must be a numeric that is bigger than 0
|
|
10
|
+
max_wait_timeout_format: must be an integer that is equal or bigger than 0
|
|
11
|
+
kafka_format: must be a hash with symbol based keys
|
|
7
12
|
kafka_key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
|
|
13
|
+
|
|
14
|
+
message:
|
|
15
|
+
missing: must be present
|
|
16
|
+
partition_format: must be an integer greater or equal to -1
|
|
17
|
+
topic_format: 'does not match the topic allowed format'
|
|
18
|
+
partition_key_format: must be a non-empty string
|
|
19
|
+
timestamp_format: must be either time or integer
|
|
20
|
+
payload_format: must be string
|
|
21
|
+
headers_format: must be a hash
|
|
22
|
+
key_format: must be a non-empty string
|
|
23
|
+
payload_max_size: is more than `max_payload_size` config value
|
|
24
|
+
headers_invalid_key_type: all headers keys need to be of type String
|
|
25
|
+
headers_invalid_value_type: all headers values need to be of type String
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
missing: must be present
|
|
29
|
+
nested.id_format: 'is invalid'
|
|
30
|
+
nested.id2_format: 'is invalid'
|
data/lib/waterdrop/config.rb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
module WaterDrop
|
|
6
6
|
# Configuration object for setting up all options required by WaterDrop
|
|
7
7
|
class Config
|
|
8
|
-
include
|
|
8
|
+
include Configurable
|
|
9
9
|
|
|
10
10
|
# Defaults for kafka settings, that will be overwritten only if not present already
|
|
11
11
|
KAFKA_DEFAULTS = {
|
|
@@ -68,6 +68,8 @@ module WaterDrop
|
|
|
68
68
|
|
|
69
69
|
::Rdkafka::Config.logger = config.logger
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
self
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
private
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WaterDrop
|
|
4
|
+
module Configurable
|
|
5
|
+
# Single non-leaf node
|
|
6
|
+
# This is a core component for the configurable settings
|
|
7
|
+
#
|
|
8
|
+
# The idea here is simple: we collect settings (leafs) and children (nodes) information and we
|
|
9
|
+
# only compile/initialize the values prior to user running the `#configure` API. This API needs
|
|
10
|
+
# to run prior to using the result stuff even if there is nothing to configure
|
|
11
|
+
class Node
|
|
12
|
+
attr_reader :name, :nestings
|
|
13
|
+
|
|
14
|
+
# We need to be able to redefine children for deep copy
|
|
15
|
+
attr_accessor :children
|
|
16
|
+
|
|
17
|
+
# @param name [Symbol] node name
|
|
18
|
+
# @param nestings [Proc] block for nested settings
|
|
19
|
+
def initialize(name, nestings = ->(_) {})
|
|
20
|
+
@name = name
|
|
21
|
+
@children = []
|
|
22
|
+
@nestings = nestings
|
|
23
|
+
instance_eval(&nestings)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Allows for a single leaf or nested node definition
|
|
27
|
+
#
|
|
28
|
+
# @param name [Symbol] setting or nested node name
|
|
29
|
+
# @param default [Object] default value
|
|
30
|
+
# @param constructor [#call, nil] callable or nil
|
|
31
|
+
# @param block [Proc] block for nested settings
|
|
32
|
+
def setting(name, default: nil, constructor: nil, &block)
|
|
33
|
+
@children << if block
|
|
34
|
+
Node.new(name, block)
|
|
35
|
+
else
|
|
36
|
+
Leaf.new(name, default, constructor)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Allows for the configuration and setup of the settings
|
|
41
|
+
#
|
|
42
|
+
# Compile settings, allow for overrides via yielding
|
|
43
|
+
# @return [Node] returns self after configuration
|
|
44
|
+
def configure
|
|
45
|
+
compile
|
|
46
|
+
yield(self) if block_given?
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Hash] frozen config hash representation
|
|
51
|
+
def to_h
|
|
52
|
+
config = {}
|
|
53
|
+
|
|
54
|
+
@children.each do |value|
|
|
55
|
+
config[value.name] = if value.is_a?(Leaf)
|
|
56
|
+
public_send(value.name)
|
|
57
|
+
else
|
|
58
|
+
value.to_h
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
config.freeze
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Deep copies all the children nodes to allow us for templates building on a class level and
|
|
66
|
+
# non-side-effect usage on an instance/inherited.
|
|
67
|
+
# @return [Node] duplicated node
|
|
68
|
+
def deep_dup
|
|
69
|
+
dupped = Node.new(name, nestings)
|
|
70
|
+
|
|
71
|
+
dupped.children += children.map do |value|
|
|
72
|
+
value.is_a?(Leaf) ? value.dup : value.deep_dup
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
dupped
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Converts the settings definitions into end children
|
|
79
|
+
# @note It runs once, after things are compiled, they will not be recompiled again
|
|
80
|
+
def compile
|
|
81
|
+
@children.each do |value|
|
|
82
|
+
# Do not redefine something that was already set during compilation
|
|
83
|
+
# This will allow us to reconfigure things and skip override with defaults
|
|
84
|
+
next if respond_to?(value.name)
|
|
85
|
+
|
|
86
|
+
singleton_class.attr_accessor value.name
|
|
87
|
+
|
|
88
|
+
initialized = if value.is_a?(Leaf)
|
|
89
|
+
value.constructor ? value.constructor.call(value.default) : value.default
|
|
90
|
+
else
|
|
91
|
+
value.compile
|
|
92
|
+
value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
public_send("#{value.name}=", initialized)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WaterDrop
|
|
4
|
+
# A simple dry-configuration API compatible module for defining settings with defaults and a
|
|
5
|
+
# constructor.
|
|
6
|
+
module Configurable
|
|
7
|
+
# A simple settings layer that works similar to dry-configurable
|
|
8
|
+
# It allows us to define settings on a class and per instance level with templating on a class
|
|
9
|
+
# level. It handles inheritance and allows for nested settings.
|
|
10
|
+
#
|
|
11
|
+
# @note The core settings template needs to be defined on a class level
|
|
12
|
+
class << self
|
|
13
|
+
# Sets up all the class methods and inits the core root node.
|
|
14
|
+
# Useful when only per class settings are needed as does not include instance methods
|
|
15
|
+
# @param base [Class] class that we extend
|
|
16
|
+
def extended(base)
|
|
17
|
+
base.extend ClassMethods
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Sets up all the class and instance methods and inits the core root node
|
|
21
|
+
#
|
|
22
|
+
# @param base [Class] class to which we want to add configuration
|
|
23
|
+
#
|
|
24
|
+
# Needs to be used when per instance configuration is needed
|
|
25
|
+
def included(base)
|
|
26
|
+
base.include InstanceMethods
|
|
27
|
+
base.extend self
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Instance related methods
|
|
32
|
+
module InstanceMethods
|
|
33
|
+
# @return [Node] config root node
|
|
34
|
+
def config
|
|
35
|
+
@config ||= self.class.config.deep_dup
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Allows for a per instance configuration (if needed)
|
|
39
|
+
# @param block [Proc] block for configuration
|
|
40
|
+
def configure(&block)
|
|
41
|
+
config.configure(&block)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Class related methods
|
|
46
|
+
module ClassMethods
|
|
47
|
+
# @return [Node] root node for the settings
|
|
48
|
+
def config
|
|
49
|
+
return @config if @config
|
|
50
|
+
|
|
51
|
+
# This will handle inheritance
|
|
52
|
+
@config = if superclass.respond_to?(:config)
|
|
53
|
+
superclass.config.deep_dup
|
|
54
|
+
else
|
|
55
|
+
Node.new(:root)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Allows for a per class configuration (if needed)
|
|
60
|
+
# @param block [Proc] block for configuration
|
|
61
|
+
def configure(&block)
|
|
62
|
+
config.configure(&block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Pipes the settings setup to the config root node
|
|
66
|
+
def setting(...)
|
|
67
|
+
config.setting(...)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WaterDrop
|
|
4
|
+
module Contractable
|
|
5
|
+
# Base contract for all the contracts that check data format
|
|
6
|
+
#
|
|
7
|
+
# @note This contract does NOT support rules inheritance as it was never needed in Karafka
|
|
8
|
+
class Contract
|
|
9
|
+
extend Configurable
|
|
10
|
+
|
|
11
|
+
# Yaml based error messages data
|
|
12
|
+
setting(:error_messages)
|
|
13
|
+
|
|
14
|
+
# Class level API definitions
|
|
15
|
+
class << self
|
|
16
|
+
# @return [Array<Rule>] all the validation rules defined for a given contract
|
|
17
|
+
attr_reader :rules
|
|
18
|
+
|
|
19
|
+
# Allows for definition of a scope/namespace for nested validations
|
|
20
|
+
#
|
|
21
|
+
# @param path [Symbol] path in the hash for nesting
|
|
22
|
+
# @param block [Proc] nested rule code or more nestings inside
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# nested(:key) do
|
|
26
|
+
# required(:inside) { |inside| inside.is_a?(String) }
|
|
27
|
+
# end
|
|
28
|
+
def nested(path, &block)
|
|
29
|
+
init_accu
|
|
30
|
+
@nested << path
|
|
31
|
+
instance_eval(&block)
|
|
32
|
+
@nested.pop
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Defines a rule for a required field (required means, that will automatically create an
|
|
36
|
+
# error if missing)
|
|
37
|
+
#
|
|
38
|
+
# @param keys [Array<Symbol>] single or full path
|
|
39
|
+
# @param block [Proc] validation rule
|
|
40
|
+
def required(*keys, &block)
|
|
41
|
+
init_accu
|
|
42
|
+
@rules << Rule.new(@nested + keys, :required, block).freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param keys [Array<Symbol>] single or full path
|
|
46
|
+
# @param block [Proc] validation rule
|
|
47
|
+
def optional(*keys, &block)
|
|
48
|
+
init_accu
|
|
49
|
+
@rules << Rule.new(@nested + keys, :optional, block).freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param block [Proc] validation rule
|
|
53
|
+
#
|
|
54
|
+
# @note Virtual rules have different result expectations. Please see contracts or specs for
|
|
55
|
+
# details.
|
|
56
|
+
def virtual(&block)
|
|
57
|
+
init_accu
|
|
58
|
+
@rules << Rule.new([], :virtual, block).freeze
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Initializes nestings and rules building accumulator
|
|
64
|
+
def init_accu
|
|
65
|
+
@nested ||= []
|
|
66
|
+
@rules ||= []
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Runs the validation
|
|
71
|
+
#
|
|
72
|
+
# @param data [Hash] hash with data we want to validate
|
|
73
|
+
# @return [Result] validaton result
|
|
74
|
+
def call(data)
|
|
75
|
+
errors = []
|
|
76
|
+
|
|
77
|
+
self.class.rules.map do |rule|
|
|
78
|
+
case rule.type
|
|
79
|
+
when :required
|
|
80
|
+
validate_required(data, rule, errors)
|
|
81
|
+
when :optional
|
|
82
|
+
validate_optional(data, rule, errors)
|
|
83
|
+
when :virtual
|
|
84
|
+
validate_virtual(data, rule, errors)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Result.new(errors, self)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param data [Hash] data for validation
|
|
92
|
+
# @param error_class [Class] error class that should be used when validation fails
|
|
93
|
+
# @return [Boolean] true
|
|
94
|
+
# @raise [StandardError] any error provided in the error_class that inherits from the
|
|
95
|
+
# standard error
|
|
96
|
+
def validate!(data, error_class)
|
|
97
|
+
result = call(data)
|
|
98
|
+
|
|
99
|
+
return true if result.success?
|
|
100
|
+
|
|
101
|
+
raise error_class, result.errors
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# Runs validation for rules on fields that are required and adds errors (if any) to the
|
|
107
|
+
# errors array
|
|
108
|
+
#
|
|
109
|
+
# @param data [Hash] input hash
|
|
110
|
+
# @param rule [Rule] validation rule
|
|
111
|
+
# @param errors [Array] array with errors from previous rules (if any)
|
|
112
|
+
def validate_required(data, rule, errors)
|
|
113
|
+
for_checking = dig(data, rule.path)
|
|
114
|
+
|
|
115
|
+
if for_checking.first == :match
|
|
116
|
+
result = rule.validator.call(for_checking.last, data, errors, self)
|
|
117
|
+
|
|
118
|
+
return if result == true
|
|
119
|
+
|
|
120
|
+
errors << [rule.path, result || :format]
|
|
121
|
+
else
|
|
122
|
+
errors << [rule.path, :missing]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Runs validation for rules on fields that are optional and adds errors (if any) to the
|
|
127
|
+
# errors array
|
|
128
|
+
#
|
|
129
|
+
# @param data [Hash] input hash
|
|
130
|
+
# @param rule [Rule] validation rule
|
|
131
|
+
# @param errors [Array] array with errors from previous rules (if any)
|
|
132
|
+
def validate_optional(data, rule, errors)
|
|
133
|
+
for_checking = dig(data, rule.path)
|
|
134
|
+
|
|
135
|
+
return unless for_checking.first == :match
|
|
136
|
+
|
|
137
|
+
result = rule.validator.call(for_checking.last, data, errors, self)
|
|
138
|
+
|
|
139
|
+
return if result == true
|
|
140
|
+
|
|
141
|
+
errors << [rule.path, result || :format]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Runs validation for rules on virtual fields (aggregates, etc) and adds errors (if any) to
|
|
145
|
+
# the errors array
|
|
146
|
+
#
|
|
147
|
+
# @param data [Hash] input hash
|
|
148
|
+
# @param rule [Rule] validation rule
|
|
149
|
+
# @param errors [Array] array with errors from previous rules (if any)
|
|
150
|
+
def validate_virtual(data, rule, errors)
|
|
151
|
+
result = rule.validator.call(data, errors, self)
|
|
152
|
+
|
|
153
|
+
return if result == true
|
|
154
|
+
|
|
155
|
+
errors.push(*result)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Tries to dig for a given key in a hash and returns it with indication whether or not it was
|
|
159
|
+
# possible to find it (dig returns nil and we don't know if it wasn't the digged key value)
|
|
160
|
+
#
|
|
161
|
+
# @param data [Hash]
|
|
162
|
+
# @param keys [Array<Symbol>]
|
|
163
|
+
# @return [Array<Symbol, Object>] array where the first element is `:match` or `:miss` and
|
|
164
|
+
# the digged value or nil if not found
|
|
165
|
+
def dig(data, keys)
|
|
166
|
+
current = data
|
|
167
|
+
result = :match
|
|
168
|
+
|
|
169
|
+
keys.each do |nesting|
|
|
170
|
+
unless current.key?(nesting)
|
|
171
|
+
result = :miss
|
|
172
|
+
|
|
173
|
+
break
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
current = current[nesting]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
[result, current]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WaterDrop
|
|
4
|
+
module Contractable
|
|
5
|
+
# Representation of a validaton result with resolved error messages
|
|
6
|
+
class Result
|
|
7
|
+
attr_reader :errors
|
|
8
|
+
|
|
9
|
+
# Builds a result object and remaps (if needed) error keys to proper error messages
|
|
10
|
+
#
|
|
11
|
+
# @param errors [Array<Array>] array with sub-arrays with paths and error keys
|
|
12
|
+
# @param contract [Object] contract that generated the error
|
|
13
|
+
def initialize(errors, contract)
|
|
14
|
+
# Short track to skip object allocation for the happy path
|
|
15
|
+
if errors.empty?
|
|
16
|
+
@errors = errors
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
hashed = {}
|
|
21
|
+
|
|
22
|
+
errors.each do |error|
|
|
23
|
+
scope = error.first.map(&:to_s).join('.').to_sym
|
|
24
|
+
|
|
25
|
+
# This will allow for usage of custom messages instead of yaml keys if needed
|
|
26
|
+
hashed[scope] = if error.last.is_a?(String)
|
|
27
|
+
error.last
|
|
28
|
+
else
|
|
29
|
+
build_message(contract, scope, error.last)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@errors = hashed
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] true if no errors
|
|
37
|
+
def success?
|
|
38
|
+
errors.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Builds message based on the error messages
|
|
44
|
+
# @param contract [Object] contract for which we build the result
|
|
45
|
+
# @param scope [Symbol] path to the key that has an error
|
|
46
|
+
# @param error_key [Symbol] error key for yaml errors lookup
|
|
47
|
+
# @return [String] error message
|
|
48
|
+
def build_message(contract, scope, error_key)
|
|
49
|
+
messages = contract.class.config.error_messages
|
|
50
|
+
|
|
51
|
+
messages.fetch(error_key.to_s) do
|
|
52
|
+
messages.fetch("#{scope}_#{error_key}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WaterDrop
|
|
4
|
+
# Contract layer for WaterDrop and Karafka
|
|
5
|
+
# It aims to be "dry-validation" like but smaller and easier to handle + without dependencies
|
|
6
|
+
#
|
|
7
|
+
# It allows for nested validations, etc
|
|
8
|
+
#
|
|
9
|
+
# @note It is thread-safe to run but validations definitions should happen before threads are
|
|
10
|
+
# used.
|
|
11
|
+
module Contractable
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -3,27 +3,37 @@
|
|
|
3
3
|
module WaterDrop
|
|
4
4
|
module Contracts
|
|
5
5
|
# Contract with validation rules for WaterDrop configuration details
|
|
6
|
-
class Config <
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
required(:wait_timeout).filled(:number?, gt?: 0)
|
|
14
|
-
required(:kafka).filled(:hash?)
|
|
6
|
+
class Config < Contractable::Contract
|
|
7
|
+
configure do |config|
|
|
8
|
+
config.error_messages = YAML.safe_load(
|
|
9
|
+
File.read(
|
|
10
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
|
11
|
+
)
|
|
12
|
+
).fetch('en').fetch('validations').fetch('config')
|
|
15
13
|
end
|
|
16
14
|
|
|
15
|
+
required(:id) { |id| id.is_a?(String) && !id.empty? }
|
|
16
|
+
required(:logger) { |logger| !logger.nil? }
|
|
17
|
+
required(:deliver) { |deliver| [true, false].include?(deliver) }
|
|
18
|
+
required(:max_payload_size) { |ps| ps.is_a?(Integer) && ps >= 1 }
|
|
19
|
+
required(:max_wait_timeout) { |mwt| mwt.is_a?(Numeric) && mwt >= 0 }
|
|
20
|
+
required(:wait_timeout) { |wt| wt.is_a?(Numeric) && wt.positive? }
|
|
21
|
+
required(:kafka) { |kafka| kafka.is_a?(Hash) && !kafka.empty? }
|
|
22
|
+
|
|
17
23
|
# rdkafka allows both symbols and strings as keys for config but then casts them to strings
|
|
18
24
|
# This can be confusing, so we expect all keys to be symbolized
|
|
19
|
-
|
|
20
|
-
next unless
|
|
25
|
+
virtual do |config, errors|
|
|
26
|
+
next true unless errors.empty?
|
|
27
|
+
|
|
28
|
+
errors = []
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
config
|
|
31
|
+
.fetch(:kafka)
|
|
32
|
+
.keys
|
|
33
|
+
.reject { |key| key.is_a?(Symbol) }
|
|
34
|
+
.each { |key| errors << [[:kafka, key], :kafka_key_must_be_a_symbol] }
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
end
|
|
36
|
+
errors
|
|
27
37
|
end
|
|
28
38
|
end
|
|
29
39
|
end
|
|
@@ -4,36 +4,56 @@ module WaterDrop
|
|
|
4
4
|
module Contracts
|
|
5
5
|
# Contract with validation rules for validating that all the message options that
|
|
6
6
|
# we provide to producer ale valid and usable
|
|
7
|
-
class Message <
|
|
7
|
+
class Message < Contractable::Contract
|
|
8
|
+
configure do |config|
|
|
9
|
+
config.error_messages = YAML.safe_load(
|
|
10
|
+
File.read(
|
|
11
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
|
12
|
+
)
|
|
13
|
+
).fetch('en').fetch('validations').fetch('message')
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
# Regex to check that topic has a valid format
|
|
9
17
|
TOPIC_REGEXP = /\A(\w|-|\.)+\z/
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
STRING_ASSERTION = ->(value) { value.is_a?(String) }.to_proc
|
|
19
|
+
private_constant :TOPIC_REGEXP
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
attr_reader :max_payload_size
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
required(:payload).filled(:str?)
|
|
21
|
-
optional(:key).maybe(:str?, :filled?)
|
|
22
|
-
optional(:partition).filled(:int?, gteq?: -1)
|
|
23
|
-
optional(:partition_key).maybe(:str?, :filled?)
|
|
24
|
-
optional(:timestamp).maybe { time? | int? }
|
|
25
|
-
optional(:headers).maybe(:hash?)
|
|
23
|
+
# @param max_payload_size [Integer] max payload size
|
|
24
|
+
def initialize(max_payload_size:)
|
|
25
|
+
super()
|
|
26
|
+
@max_payload_size = max_payload_size
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
required(:topic) { |topic| topic.is_a?(String) && TOPIC_REGEXP.match?(topic) }
|
|
30
|
+
required(:payload) { |payload| payload.is_a?(String) }
|
|
31
|
+
optional(:key) { |key| key.nil? || (key.is_a?(String) && !key.empty?) }
|
|
32
|
+
optional(:partition) { |partition| partition.is_a?(Integer) && partition >= -1 }
|
|
33
|
+
optional(:partition_key) { |p_key| p_key.nil? || (p_key.is_a?(String) && !p_key.empty?) }
|
|
34
|
+
optional(:timestamp) { |ts| ts.nil? || (ts.is_a?(Time) || ts.is_a?(Integer)) }
|
|
35
|
+
optional(:headers) { |headers| headers.nil? || headers.is_a?(Hash) }
|
|
36
|
+
|
|
37
|
+
virtual do |config, errors|
|
|
38
|
+
next true unless errors.empty?
|
|
39
|
+
next true unless config.key?(:headers)
|
|
40
|
+
next true if config[:headers].nil?
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
errors = []
|
|
43
|
+
|
|
44
|
+
config.fetch(:headers).each do |key, value|
|
|
45
|
+
errors << [%i[headers], :invalid_key_type] unless key.is_a?(String)
|
|
46
|
+
errors << [%i[headers], :invalid_value_type] unless value.is_a?(String)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
errors
|
|
33
50
|
end
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
virtual do |config, errors, validator|
|
|
53
|
+
next true unless errors.empty?
|
|
54
|
+
next true if config[:payload].bytesize <= validator.max_payload_size
|
|
55
|
+
|
|
56
|
+
[[%i[payload], :max_size]]
|
|
37
57
|
end
|
|
38
58
|
end
|
|
39
59
|
end
|
|
@@ -11,26 +11,29 @@ module WaterDrop
|
|
|
11
11
|
#
|
|
12
12
|
# @note You need to setup the `dogstatsd-ruby` client and assign it
|
|
13
13
|
class Listener
|
|
14
|
-
include
|
|
14
|
+
include WaterDrop::Configurable
|
|
15
|
+
extend Forwardable
|
|
16
|
+
|
|
17
|
+
def_delegators :config, :client, :rd_kafka_metrics, :namespace, :default_tags
|
|
15
18
|
|
|
16
19
|
# Value object for storing a single rdkafka metric publishing details
|
|
17
20
|
RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
|
|
18
21
|
|
|
19
22
|
# Namespace under which the DD metrics should be published
|
|
20
|
-
setting :namespace, default: 'waterdrop'
|
|
23
|
+
setting :namespace, default: 'waterdrop'
|
|
21
24
|
|
|
22
25
|
# Datadog client that we should use to publish the metrics
|
|
23
|
-
setting :client
|
|
26
|
+
setting :client
|
|
24
27
|
|
|
25
28
|
# Default tags we want to publish (for example hostname)
|
|
26
29
|
# Format as followed (example for hostname): `["host:#{Socket.gethostname}"]`
|
|
27
|
-
setting :default_tags, default: []
|
|
30
|
+
setting :default_tags, default: []
|
|
28
31
|
|
|
29
32
|
# All the rdkafka metrics we want to publish
|
|
30
33
|
#
|
|
31
34
|
# By default we publish quite a lot so this can be tuned
|
|
32
35
|
# Note, that the once with `_d` come from WaterDrop, not rdkafka or Kafka
|
|
33
|
-
setting :rd_kafka_metrics,
|
|
36
|
+
setting :rd_kafka_metrics, default: [
|
|
34
37
|
# Client metrics
|
|
35
38
|
RdKafkaMetric.new(:count, :root, 'calls', 'tx_d'),
|
|
36
39
|
RdKafkaMetric.new(:histogram, :root, 'queue.size', 'msg_cnt_d'),
|
|
@@ -47,8 +50,11 @@ module WaterDrop
|
|
|
47
50
|
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p99', %w[rtt p99])
|
|
48
51
|
].freeze
|
|
49
52
|
|
|
53
|
+
configure
|
|
54
|
+
|
|
50
55
|
# @param block [Proc] configuration block
|
|
51
56
|
def initialize(&block)
|
|
57
|
+
configure
|
|
52
58
|
setup(&block) if block
|
|
53
59
|
end
|
|
54
60
|
|
|
@@ -10,7 +10,15 @@ module WaterDrop
|
|
|
10
10
|
# Adds a method that allows us to get the native kafka producer name
|
|
11
11
|
# @return [String] producer instance name
|
|
12
12
|
def name
|
|
13
|
-
|
|
13
|
+
unless @_native
|
|
14
|
+
version = ::Gem::Version.new(::Rdkafka::VERSION)
|
|
15
|
+
change = ::Gem::Version.new('0.12.0')
|
|
16
|
+
# 0.12.0 changed how the native producer client reference works.
|
|
17
|
+
# This code supports both older and newer versions of rdkafka
|
|
18
|
+
@_native = version >= change ? @client.native : @native_kafka
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
::Rdkafka::Bindings.rd_kafka_name(@_native)
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
end
|
data/lib/waterdrop/version.rb
CHANGED
data/lib/waterdrop.rb
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
# External components
|
|
4
4
|
# delegate should be removed because we don't need it, we just add it because of ruby-kafka
|
|
5
5
|
%w[
|
|
6
|
+
forwardable
|
|
6
7
|
concurrent/array
|
|
7
|
-
|
|
8
|
+
concurrent/hash
|
|
9
|
+
yaml
|
|
8
10
|
dry/monitor/notifications
|
|
9
|
-
dry-validation
|
|
10
11
|
rdkafka
|
|
11
12
|
json
|
|
12
13
|
zeitwerk
|
data/waterdrop.gemspec
CHANGED
|
@@ -17,9 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.license = 'MIT'
|
|
18
18
|
|
|
19
19
|
spec.add_dependency 'concurrent-ruby', '>= 1.1'
|
|
20
|
-
spec.add_dependency 'dry-configurable', '~> 0.13'
|
|
21
20
|
spec.add_dependency 'dry-monitor', '~> 0.5'
|
|
22
|
-
spec.add_dependency 'dry-validation', '~> 1.7'
|
|
23
21
|
spec.add_dependency 'rdkafka', '>= 0.10'
|
|
24
22
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
|
25
23
|
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: waterdrop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.3.
|
|
4
|
+
version: 2.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maciej Mensfeld
|
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
|
34
34
|
R2P11bWoCtr70BsccVrN8jEhzwXngMyI2gVt750Y+dbTu1KgRqZKp/ECe7ZzPzXj
|
|
35
35
|
pIy9vHxTANKYVyI4qj8OrFdEM5BQNu8oQpL0iQ==
|
|
36
36
|
-----END CERTIFICATE-----
|
|
37
|
-
date: 2022-
|
|
37
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
|
38
38
|
dependencies:
|
|
39
39
|
- !ruby/object:Gem::Dependency
|
|
40
40
|
name: concurrent-ruby
|
|
@@ -50,20 +50,6 @@ dependencies:
|
|
|
50
50
|
- - ">="
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: '1.1'
|
|
53
|
-
- !ruby/object:Gem::Dependency
|
|
54
|
-
name: dry-configurable
|
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
|
56
|
-
requirements:
|
|
57
|
-
- - "~>"
|
|
58
|
-
- !ruby/object:Gem::Version
|
|
59
|
-
version: '0.13'
|
|
60
|
-
type: :runtime
|
|
61
|
-
prerelease: false
|
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
63
|
-
requirements:
|
|
64
|
-
- - "~>"
|
|
65
|
-
- !ruby/object:Gem::Version
|
|
66
|
-
version: '0.13'
|
|
67
53
|
- !ruby/object:Gem::Dependency
|
|
68
54
|
name: dry-monitor
|
|
69
55
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -78,20 +64,6 @@ dependencies:
|
|
|
78
64
|
- - "~>"
|
|
79
65
|
- !ruby/object:Gem::Version
|
|
80
66
|
version: '0.5'
|
|
81
|
-
- !ruby/object:Gem::Dependency
|
|
82
|
-
name: dry-validation
|
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
|
84
|
-
requirements:
|
|
85
|
-
- - "~>"
|
|
86
|
-
- !ruby/object:Gem::Version
|
|
87
|
-
version: '1.7'
|
|
88
|
-
type: :runtime
|
|
89
|
-
prerelease: false
|
|
90
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
91
|
-
requirements:
|
|
92
|
-
- - "~>"
|
|
93
|
-
- !ruby/object:Gem::Version
|
|
94
|
-
version: '1.7'
|
|
95
67
|
- !ruby/object:Gem::Dependency
|
|
96
68
|
name: rdkafka
|
|
97
69
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -144,8 +116,14 @@ files:
|
|
|
144
116
|
- docker-compose.yml
|
|
145
117
|
- lib/waterdrop.rb
|
|
146
118
|
- lib/waterdrop/config.rb
|
|
119
|
+
- lib/waterdrop/configurable.rb
|
|
120
|
+
- lib/waterdrop/configurable/leaf.rb
|
|
121
|
+
- lib/waterdrop/configurable/node.rb
|
|
122
|
+
- lib/waterdrop/contractable.rb
|
|
123
|
+
- lib/waterdrop/contractable/contract.rb
|
|
124
|
+
- lib/waterdrop/contractable/result.rb
|
|
125
|
+
- lib/waterdrop/contractable/rule.rb
|
|
147
126
|
- lib/waterdrop/contracts.rb
|
|
148
|
-
- lib/waterdrop/contracts/base.rb
|
|
149
127
|
- lib/waterdrop/contracts/config.rb
|
|
150
128
|
- lib/waterdrop/contracts/message.rb
|
|
151
129
|
- lib/waterdrop/errors.rb
|
|
@@ -192,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
192
170
|
- !ruby/object:Gem::Version
|
|
193
171
|
version: '0'
|
|
194
172
|
requirements: []
|
|
195
|
-
rubygems_version: 3.3.
|
|
173
|
+
rubygems_version: 3.3.7
|
|
196
174
|
signing_key:
|
|
197
175
|
specification_version: 4
|
|
198
176
|
summary: Kafka messaging made easy!
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module WaterDrop
|
|
4
|
-
module Contracts
|
|
5
|
-
# Base for all the contracts in WaterDrop
|
|
6
|
-
class Base < Dry::Validation::Contract
|
|
7
|
-
config.messages.load_paths << File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
|
8
|
-
|
|
9
|
-
# @param data [Hash] data for validation
|
|
10
|
-
# @param error_class [Class] error class that should be used when validation fails
|
|
11
|
-
# @return [Boolean] true
|
|
12
|
-
# @raise [StandardError] any error provided in the error_class that inherits from the
|
|
13
|
-
# standard error
|
|
14
|
-
def validate!(data, error_class)
|
|
15
|
-
result = call(data)
|
|
16
|
-
|
|
17
|
-
return true if result.success?
|
|
18
|
-
|
|
19
|
-
raise error_class, result.errors.to_h
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|