gate 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +9 -0
- data/.codeclimate.yml +2 -6
- data/README.md +17 -24
- data/gate.gemspec +7 -8
- data/lib/gate.rb +3 -16
- data/lib/gate/command.rb +47 -0
- data/lib/gate/version.rb +1 -1
- metadata +19 -37
- data/circle.yml +0 -3
- data/lib/gate/coercer.rb +0 -48
- data/lib/gate/coercer/any.rb +0 -7
- data/lib/gate/configuration.rb +0 -55
- data/lib/gate/guard.rb +0 -67
- data/lib/gate/result.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c5b6793497535f1a64c445c3dc79f96d51ab755
|
4
|
+
data.tar.gz: 8cb64c61b215efbb182cf7a8bc830d1c0d2e5f66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 522271efbce236d5a3b71fd5c695dd59fe2a21b823482bba9979481636dafe3247b4dbd5462998500a7e0df74f0546669d7886df80c3e5765dedb26de8b139c0
|
7
|
+
data.tar.gz: e6d172ccbee95ec78413c106a6c7fac857f092e11d6eaeaa353e5dc3b9d0a303ee264f2b2bcf42e771c869528682f95f0883ad0cf0704b4c4dd22ca873191c59
|
data/.codeclimate.yml
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[![Code Climate](https://codeclimate.com/github/monterail/gate/badges/gpa.svg)](https://codeclimate.com/github/monterail/gate)
|
7
7
|
[![Test Coverage](https://codeclimate.com/github/monterail/gate/badges/coverage.svg)](https://codeclimate.com/github/monterail/gate/coverage)
|
8
8
|
|
9
|
-
Gate is a small
|
9
|
+
Gate is a small wrapper on [dry-validation](http://dry-rb.org/gems/dry-validation/) that might be used as Command in CQRS pattern. It will raise `InvalidCommand` error for invalid input and provide simple struct with access to coerced input.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -29,38 +29,31 @@ Or install it yourself as:
|
|
29
29
|
Define structure
|
30
30
|
|
31
31
|
```ruby
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
class DoSomethingCommand
|
33
|
+
include Gate::Command
|
34
|
+
|
35
|
+
schema do
|
36
|
+
required(:id).filled
|
37
|
+
required(:message).schema do
|
38
|
+
required(:title).filled
|
39
|
+
optional(:value).maybe(:decimal?)
|
40
|
+
end
|
38
41
|
end
|
39
42
|
end
|
40
43
|
```
|
41
44
|
|
42
|
-
|
45
|
+
Use it
|
43
46
|
|
44
47
|
```ruby
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
If you need to handle `nil` values you can use `allow_nil` flag:
|
52
|
-
|
53
|
-
```ruby
|
54
|
-
gate = Gate.rules do
|
55
|
-
required :id, :Integer, allow_nil: true
|
56
|
-
required :message, allow_nil: true do
|
57
|
-
required :title
|
58
|
-
optional :value, :Decimal
|
59
|
-
end
|
48
|
+
begin
|
49
|
+
cmd = DoSomethingCommand.with(params)
|
50
|
+
cmd.id
|
51
|
+
cmd.message
|
52
|
+
rescue DoSomethingCommand::InvalidCommand => e
|
53
|
+
e.errors # => hash { key => [errors] }
|
60
54
|
end
|
61
55
|
```
|
62
56
|
|
63
|
-
|
64
57
|
## Development
|
65
58
|
|
66
59
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/gate.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Jan Dudulski"]
|
10
10
|
spec.email = ["jan@dudulski.pl"]
|
11
11
|
|
12
|
-
spec.summary = "
|
12
|
+
spec.summary = "CQRS Command"
|
13
13
|
spec.description = "Validate and coerce user input against defined structure."
|
14
14
|
spec.homepage = "https://github.com/monterail/gate"
|
15
15
|
spec.license = "MIT"
|
@@ -17,12 +17,11 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^test/}) }
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_runtime_dependency "
|
21
|
-
spec.add_runtime_dependency "axiom-types", "~> 0.1"
|
20
|
+
spec.add_runtime_dependency "dry-validation", "~> 0.11"
|
22
21
|
|
23
|
-
spec.add_development_dependency "bundler", "~> 1.
|
24
|
-
spec.add_development_dependency "rake", "~>
|
25
|
-
spec.add_development_dependency "pry", "~> 0.
|
26
|
-
spec.add_development_dependency "minitest", "~> 5.
|
27
|
-
spec.add_development_dependency "codeclimate-test-reporter", "~> 0
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
23
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
24
|
+
spec.add_development_dependency "pry", "~> 0.11"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.11"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0"
|
28
27
|
end
|
data/lib/gate.rb
CHANGED
@@ -1,21 +1,8 @@
|
|
1
1
|
module Gate
|
2
|
-
|
3
|
-
|
4
|
-
def self.rules(&block)
|
5
|
-
configuration = Configuration.new(&block)
|
6
|
-
|
7
|
-
Guard.new(configuration)
|
8
|
-
end
|
2
|
+
InvalidCommand = Class.new(StandardError)
|
9
3
|
end
|
10
4
|
|
11
|
-
require "
|
12
|
-
require "coercible"
|
13
|
-
require "forwardable"
|
14
|
-
require "set"
|
5
|
+
require "dry-validation"
|
15
6
|
|
16
|
-
require "gate/
|
17
|
-
require "gate/coercer/any"
|
18
|
-
require "gate/configuration"
|
19
|
-
require "gate/guard"
|
20
|
-
require "gate/result"
|
7
|
+
require "gate/command"
|
21
8
|
require "gate/version"
|
data/lib/gate/command.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gate
|
2
|
+
module Command
|
3
|
+
SchemaAlreadyRegistered = Class.new(StandardError)
|
4
|
+
SchemaNotDefined = Class.new(StandardError)
|
5
|
+
|
6
|
+
class InvalidCommand < StandardError
|
7
|
+
attr_reader :errors
|
8
|
+
|
9
|
+
def initialize(errors)
|
10
|
+
@errors = errors
|
11
|
+
super("Invalid command")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.send(:extend, ClassMethods)
|
17
|
+
base.send(:attr_reader, :result)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(data)
|
21
|
+
@result = data
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def schema(&block)
|
26
|
+
if block_given?
|
27
|
+
raise SchemaAlreadyRegistered if @schema
|
28
|
+
@schema = Dry::Validation.Form(&block)
|
29
|
+
@schema.rules.keys.each do |name|
|
30
|
+
define_method(name) do
|
31
|
+
result[name]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise SchemaNotDefined unless @schema
|
36
|
+
@schema
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def with(input)
|
41
|
+
result = schema.(input)
|
42
|
+
raise InvalidCommand, result.messages if result.failure?
|
43
|
+
new result.output
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/gate/version.rb
CHANGED
metadata
CHANGED
@@ -1,113 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Dudulski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: dry-validation
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.11'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: axiom-types
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.1'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.1'
|
26
|
+
version: '0.11'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: bundler
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
33
|
+
version: '1.16'
|
48
34
|
type: :development
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
40
|
+
version: '1.16'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
47
|
+
version: '12.0'
|
62
48
|
type: :development
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
54
|
+
version: '12.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: pry
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
61
|
+
version: '0.11'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
68
|
+
version: '0.11'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: minitest
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '5.
|
75
|
+
version: '5.11'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '5.
|
82
|
+
version: '5.11'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: codeclimate-test-reporter
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
87
|
- - "~>"
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0
|
89
|
+
version: '1.0'
|
104
90
|
type: :development
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
94
|
- - "~>"
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0
|
96
|
+
version: '1.0'
|
111
97
|
description: Validate and coerce user input against defined structure.
|
112
98
|
email:
|
113
99
|
- jan@dudulski.pl
|
@@ -115,6 +101,7 @@ executables: []
|
|
115
101
|
extensions: []
|
116
102
|
extra_rdoc_files: []
|
117
103
|
files:
|
104
|
+
- ".circleci/config.yml"
|
118
105
|
- ".codeclimate.yml"
|
119
106
|
- ".gitignore"
|
120
107
|
- ".rubocop.yml"
|
@@ -125,14 +112,9 @@ files:
|
|
125
112
|
- Rakefile
|
126
113
|
- bin/console
|
127
114
|
- bin/setup
|
128
|
-
- circle.yml
|
129
115
|
- gate.gemspec
|
130
116
|
- lib/gate.rb
|
131
|
-
- lib/gate/
|
132
|
-
- lib/gate/coercer/any.rb
|
133
|
-
- lib/gate/configuration.rb
|
134
|
-
- lib/gate/guard.rb
|
135
|
-
- lib/gate/result.rb
|
117
|
+
- lib/gate/command.rb
|
136
118
|
- lib/gate/version.rb
|
137
119
|
homepage: https://github.com/monterail/gate
|
138
120
|
licenses:
|
@@ -154,8 +136,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
136
|
version: '0'
|
155
137
|
requirements: []
|
156
138
|
rubyforge_project:
|
157
|
-
rubygems_version: 2.
|
139
|
+
rubygems_version: 2.6.14
|
158
140
|
signing_key:
|
159
141
|
specification_version: 4
|
160
|
-
summary:
|
142
|
+
summary: CQRS Command
|
161
143
|
test_files: []
|
data/circle.yml
DELETED
data/lib/gate/coercer.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
module Gate
|
2
|
-
class Coercer
|
3
|
-
PRIMITIVES = [TrueClass, FalseClass, Array, Hash, Numeric]
|
4
|
-
|
5
|
-
def initialize(engine, type, allow_nil: false)
|
6
|
-
unless coercible?(type)
|
7
|
-
fail CoercionError, "Doesn't know how to coerce into #{type}"
|
8
|
-
end
|
9
|
-
|
10
|
-
@engine = engine
|
11
|
-
@type = type
|
12
|
-
@allow_nil = allow_nil
|
13
|
-
end
|
14
|
-
|
15
|
-
def coerce(input)
|
16
|
-
engine[detect_input_type(input)].public_send(coercion_method, input)
|
17
|
-
rescue Coercible::UnsupportedCoercion
|
18
|
-
raise CoercionError, "Doesn't know how to coerce #{input} into #{type}"
|
19
|
-
end
|
20
|
-
|
21
|
-
def allow_nil?
|
22
|
-
@allow_nil
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
attr_reader :engine, :type
|
28
|
-
|
29
|
-
def coercible?(type)
|
30
|
-
type == :Any or Axiom::Types.const_defined?(type)
|
31
|
-
end
|
32
|
-
|
33
|
-
def detect_input_type(input)
|
34
|
-
case input
|
35
|
-
when *PRIMITIVES, Any
|
36
|
-
input.class
|
37
|
-
else
|
38
|
-
String
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def coercion_method
|
43
|
-
return :to_any if type == :Any
|
44
|
-
|
45
|
-
Axiom::Types.const_get(type).coercion_method
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
data/lib/gate/coercer/any.rb
DELETED
data/lib/gate/configuration.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
module Gate
|
2
|
-
class Configuration
|
3
|
-
extend Forwardable
|
4
|
-
|
5
|
-
attr_reader :coercer
|
6
|
-
attr_reader :required_set, :optional_set, :nested_set
|
7
|
-
attr_reader :rules
|
8
|
-
|
9
|
-
def_delegator :@rules, :reduce
|
10
|
-
|
11
|
-
def initialize(coercer: Coercible::Coercer.new, allow_nil: false, &block)
|
12
|
-
@coercer = coercer
|
13
|
-
@required_set = Set.new
|
14
|
-
@optional_set = Set.new
|
15
|
-
@nested_set = Set.new
|
16
|
-
@rules = {}
|
17
|
-
@allow_nil = allow_nil
|
18
|
-
|
19
|
-
instance_eval(&block)
|
20
|
-
end
|
21
|
-
|
22
|
-
def required?(name)
|
23
|
-
required_set.include?(name)
|
24
|
-
end
|
25
|
-
|
26
|
-
def allow_nil?
|
27
|
-
@allow_nil
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def required(name, type = :String, allow_nil: false, &block)
|
33
|
-
required_set.add(name)
|
34
|
-
register(name, type, allow_nil: allow_nil, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def optional(name, type = :String, allow_nil: false, &block)
|
38
|
-
optional_set.add(name)
|
39
|
-
register(name, type, allow_nil: allow_nil, &block)
|
40
|
-
end
|
41
|
-
|
42
|
-
def register(name, type, allow_nil:, &block)
|
43
|
-
@rules[name] = setup_rule(name, type, allow_nil: allow_nil, &block)
|
44
|
-
end
|
45
|
-
|
46
|
-
def setup_rule(name, type, allow_nil:, &block)
|
47
|
-
if block_given?
|
48
|
-
nested_set.add(name)
|
49
|
-
Configuration.new(coercer: coercer, allow_nil: allow_nil, &block)
|
50
|
-
else
|
51
|
-
Coercer.new(coercer, type, allow_nil: allow_nil)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/gate/guard.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
module Gate
|
2
|
-
class Guard
|
3
|
-
attr_reader :configuration
|
4
|
-
|
5
|
-
def initialize(configuration)
|
6
|
-
@configuration = configuration
|
7
|
-
end
|
8
|
-
|
9
|
-
def verify(input)
|
10
|
-
configuration.reduce(Result.new) do |result, (name, rule)|
|
11
|
-
Result.new(_verify(input, name, rule, result))
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def _verify(input, name, rule, result)
|
18
|
-
case
|
19
|
-
when input.key?(name)
|
20
|
-
coerced = handle(input[name], name, rule)
|
21
|
-
update(result, coerced)
|
22
|
-
when configuration.required?(name)
|
23
|
-
errors = result.errors.merge(name => :missing)
|
24
|
-
update(result, errors: errors)
|
25
|
-
else
|
26
|
-
result.to_h
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def handle(input, name, rule)
|
31
|
-
case
|
32
|
-
when input.nil?
|
33
|
-
coerce_nil(name, rule)
|
34
|
-
when rule.is_a?(Gate::Configuration)
|
35
|
-
coerce_nested(input, name, rule)
|
36
|
-
else
|
37
|
-
coerce(input, name, rule)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def coerce(input, name, rule)
|
42
|
-
{ attributes: { name => rule.coerce(input) } }
|
43
|
-
rescue CoercionError
|
44
|
-
# TODO: log error
|
45
|
-
{ errors: { name => :coercion_error } }
|
46
|
-
end
|
47
|
-
|
48
|
-
def coerce_nested(input, name, rule)
|
49
|
-
result = Gate::Guard.new(rule).verify(input)
|
50
|
-
{ attributes: { name => result.attributes },
|
51
|
-
errors: { name => result.errors } }
|
52
|
-
end
|
53
|
-
|
54
|
-
def coerce_nil(name, rule)
|
55
|
-
if rule.allow_nil?
|
56
|
-
{ attributes: { name => nil } }
|
57
|
-
else
|
58
|
-
{ errors: { name => :nil_not_allowed } }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def update(result, attributes: {}, errors: {})
|
63
|
-
{ attributes: result.attributes.merge(attributes),
|
64
|
-
errors: result.errors.merge(errors) }
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
data/lib/gate/result.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
module Gate
|
2
|
-
class Result
|
3
|
-
attr_reader :attributes
|
4
|
-
|
5
|
-
def initialize(attributes: {}, errors: {})
|
6
|
-
@attributes = attributes
|
7
|
-
@errors = errors
|
8
|
-
end
|
9
|
-
|
10
|
-
def valid?
|
11
|
-
errors.empty?
|
12
|
-
end
|
13
|
-
|
14
|
-
def errors
|
15
|
-
_errors(@errors)
|
16
|
-
end
|
17
|
-
|
18
|
-
def to_h
|
19
|
-
{
|
20
|
-
attributes: attributes,
|
21
|
-
errors: @errors
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def _errors(hash)
|
28
|
-
hash.each_with_object({}) do |(k, v), result|
|
29
|
-
if v.is_a?(Hash)
|
30
|
-
nested = _errors(v)
|
31
|
-
|
32
|
-
result[k] = nested if nested.any?
|
33
|
-
else
|
34
|
-
result[k] = v
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|