dry-logic 0.3.0 → 0.4.0

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -5
  3. data/CHANGELOG.md +28 -0
  4. data/Gemfile +7 -4
  5. data/dry-logic.gemspec +1 -0
  6. data/lib/dry/logic.rb +2 -3
  7. data/lib/dry/logic/appliable.rb +33 -0
  8. data/lib/dry/logic/evaluator.rb +2 -0
  9. data/lib/dry/logic/operations.rb +13 -0
  10. data/lib/dry/logic/operations/abstract.rb +44 -0
  11. data/lib/dry/logic/operations/and.rb +35 -0
  12. data/lib/dry/logic/operations/attr.rb +17 -0
  13. data/lib/dry/logic/operations/binary.rb +26 -0
  14. data/lib/dry/logic/operations/check.rb +52 -0
  15. data/lib/dry/logic/operations/each.rb +32 -0
  16. data/lib/dry/logic/operations/implication.rb +37 -0
  17. data/lib/dry/logic/operations/key.rb +66 -0
  18. data/lib/dry/logic/operations/negation.rb +18 -0
  19. data/lib/dry/logic/operations/or.rb +35 -0
  20. data/lib/dry/logic/operations/set.rb +35 -0
  21. data/lib/dry/logic/operations/unary.rb +24 -0
  22. data/lib/dry/logic/operations/xor.rb +27 -0
  23. data/lib/dry/logic/operators.rb +25 -0
  24. data/lib/dry/logic/predicates.rb +143 -136
  25. data/lib/dry/logic/result.rb +76 -33
  26. data/lib/dry/logic/rule.rb +62 -46
  27. data/lib/dry/logic/rule/predicate.rb +28 -0
  28. data/lib/dry/logic/rule_compiler.rb +16 -17
  29. data/lib/dry/logic/version.rb +1 -1
  30. data/spec/integration/result_spec.rb +59 -0
  31. data/spec/integration/rule_spec.rb +53 -0
  32. data/spec/shared/predicates.rb +6 -0
  33. data/spec/shared/rule.rb +67 -0
  34. data/spec/spec_helper.rb +10 -3
  35. data/spec/support/mutant.rb +9 -0
  36. data/spec/unit/operations/and_spec.rb +64 -0
  37. data/spec/unit/operations/attr_spec.rb +27 -0
  38. data/spec/unit/operations/check_spec.rb +49 -0
  39. data/spec/unit/operations/each_spec.rb +47 -0
  40. data/spec/unit/operations/implication_spec.rb +30 -0
  41. data/spec/unit/operations/key_spec.rb +119 -0
  42. data/spec/unit/operations/negation_spec.rb +40 -0
  43. data/spec/unit/operations/or_spec.rb +73 -0
  44. data/spec/unit/operations/set_spec.rb +41 -0
  45. data/spec/unit/operations/xor_spec.rb +61 -0
  46. data/spec/unit/predicates_spec.rb +23 -0
  47. data/spec/unit/rule/predicate_spec.rb +53 -0
  48. data/spec/unit/rule_compiler_spec.rb +38 -38
  49. data/spec/unit/rule_spec.rb +94 -0
  50. metadata +67 -40
  51. data/lib/dry/logic/predicate.rb +0 -100
  52. data/lib/dry/logic/predicate_set.rb +0 -23
  53. data/lib/dry/logic/result/each.rb +0 -20
  54. data/lib/dry/logic/result/multi.rb +0 -14
  55. data/lib/dry/logic/result/named.rb +0 -17
  56. data/lib/dry/logic/result/set.rb +0 -10
  57. data/lib/dry/logic/result/value.rb +0 -17
  58. data/lib/dry/logic/rule/attr.rb +0 -13
  59. data/lib/dry/logic/rule/check.rb +0 -40
  60. data/lib/dry/logic/rule/composite.rb +0 -91
  61. data/lib/dry/logic/rule/each.rb +0 -13
  62. data/lib/dry/logic/rule/key.rb +0 -37
  63. data/lib/dry/logic/rule/negation.rb +0 -15
  64. data/lib/dry/logic/rule/set.rb +0 -31
  65. data/lib/dry/logic/rule/value.rb +0 -48
  66. data/spec/unit/predicate_spec.rb +0 -115
  67. data/spec/unit/rule/attr_spec.rb +0 -29
  68. data/spec/unit/rule/check_spec.rb +0 -44
  69. data/spec/unit/rule/conjunction_spec.rb +0 -30
  70. data/spec/unit/rule/disjunction_spec.rb +0 -38
  71. data/spec/unit/rule/each_spec.rb +0 -31
  72. data/spec/unit/rule/exclusive_disjunction_spec.rb +0 -19
  73. data/spec/unit/rule/implication_spec.rb +0 -16
  74. data/spec/unit/rule/key_spec.rb +0 -121
  75. data/spec/unit/rule/set_spec.rb +0 -30
  76. data/spec/unit/rule/value_spec.rb +0 -99
@@ -1,30 +0,0 @@
1
- RSpec.describe Rule::Composite::Conjunction do
2
- include_context 'predicates'
3
-
4
- subject(:rule) { Rule::Composite::Conjunction.new(left, right) }
5
-
6
- let(:left) { Rule::Value.new(int?) }
7
- let(:right) { Rule::Value.new(gt?.curry(18)) }
8
-
9
- describe '#call' do
10
- it 'calls left and right' do
11
- expect(rule.(18)).to be_failure
12
- end
13
- end
14
-
15
- describe '#and' do
16
- let(:other) { Rule::Value.new(lt?.curry(30)) }
17
-
18
- it 'creates conjunction with the other' do
19
- expect(rule.and(other).(31)).to be_failure
20
- end
21
- end
22
-
23
- describe '#or' do
24
- let(:other) { Rule::Value.new(lt?.curry(14)) }
25
-
26
- it 'creates disjunction with the other' do
27
- expect(rule.or(other).(13)).to be_success
28
- end
29
- end
30
- end
@@ -1,38 +0,0 @@
1
- RSpec.describe Rule::Composite::Disjunction do
2
- include_context 'predicates'
3
-
4
- subject(:rule) { Rule::Composite::Disjunction.new(left, right) }
5
-
6
- let(:left) { Rule::Value.new(none?) }
7
- let(:right) { Rule::Value.new(gt?.curry(18)) }
8
-
9
- let(:other) do
10
- Rule::Value.new(int?) & Rule::Value.new(lt?.curry(14))
11
- end
12
-
13
- describe '#call' do
14
- it 'calls left and right' do
15
- expect(rule.(nil)).to be_success
16
- expect(rule.(19)).to be_success
17
- expect(rule.(18)).to be_failure
18
- end
19
- end
20
-
21
- describe '#and' do
22
- it 'creates conjunction with the other' do
23
- expect(rule.and(other).(nil)).to be_failure
24
- expect(rule.and(other).(19)).to be_failure
25
- expect(rule.and(other).(13)).to be_failure
26
- expect(rule.and(other).(14)).to be_failure
27
- end
28
- end
29
-
30
- describe '#or' do
31
- it 'creates disjunction with the other' do
32
- expect(rule.or(other).(nil)).to be_success
33
- expect(rule.or(other).(19)).to be_success
34
- expect(rule.or(other).(13)).to be_success
35
- expect(rule.or(other).(14)).to be_failure
36
- end
37
- end
38
- end
@@ -1,31 +0,0 @@
1
- require 'dry/logic/rule'
2
-
3
- RSpec.describe Dry::Logic::Rule::Each do
4
- include_context 'predicates'
5
-
6
- subject(:address_rule) do
7
- Dry::Logic::Rule::Each.new(is_string)
8
- end
9
-
10
- let(:is_string) { Dry::Logic::Rule::Value.new(str?) }
11
-
12
- describe '#call' do
13
- it 'applies its rules to all elements in the input' do
14
- expect(address_rule.(['Address'])).to be_success
15
-
16
- expect(address_rule.([nil, 'Address'])).to be_failure
17
- expect(address_rule.([:Address, 'Address'])).to be_failure
18
- end
19
-
20
- it 'returns result ast' do
21
- expect(address_rule.([nil, nil]).to_ast).to eql([
22
- :result, [[nil, nil], [
23
- :each, [
24
- [:el, [0, [:result, [nil, [:val, [:predicate, [:str?, [[:input, nil]]]]]]]]],
25
- [:el, [1, [:result, [nil, [:val, [:predicate, [:str?, [[:input, nil]]]]]]]]]
26
- ]
27
- ]]
28
- ])
29
- end
30
- end
31
- end
@@ -1,19 +0,0 @@
1
- RSpec.describe Rule::ExclusiveDisjunction do
2
- include_context 'predicates'
3
-
4
- subject(:rule) do
5
- Rule::ExclusiveDisjunction.new(left, right)
6
- end
7
-
8
- let(:left) { Rule::Key.new(true?, name: :eat_cake) }
9
- let(:right) { Rule::Key.new(true?, name: :have_cake) }
10
-
11
- describe '#call' do
12
- it 'calls left and right' do
13
- expect(rule.(eat_cake: true, have_cake: false)).to be_success
14
- expect(rule.(eat_cake: false, have_cake: true)).to be_success
15
- expect(rule.(eat_cake: false, have_cake: false)).to be_failure
16
- expect(rule.(eat_cake: true, have_cake: true)).to be_failure
17
- end
18
- end
19
- end
@@ -1,16 +0,0 @@
1
- RSpec.describe Rule::Composite::Implication do
2
- include_context 'predicates'
3
-
4
- subject(:rule) { Rule::Composite::Implication.new(left, right) }
5
-
6
- let(:left) { Rule::Value.new(int?) }
7
- let(:right) { Rule::Value.new(gt?.curry(18)) }
8
-
9
- describe '#call' do
10
- it 'calls left and right' do
11
- expect(rule.('19')).to be_success
12
- expect(rule.(19)).to be_success
13
- expect(rule.(18)).to be_failure
14
- end
15
- end
16
- end
@@ -1,121 +0,0 @@
1
- require 'dry/logic/rule'
2
-
3
- RSpec.describe Rule::Key do
4
- include_context 'predicates'
5
-
6
- subject(:rule) do
7
- Rule::Key.new(predicate, name: :user)
8
- end
9
-
10
- let(:predicate) do
11
- key?.curry(:name)
12
- end
13
-
14
- describe '#call' do
15
- context 'with a plain predicate' do
16
- it 'applies predicate to the value' do
17
- expect(rule.(user: { name: 'Jane' })).to be_success
18
- expect(rule.(user: {})).to be_failure
19
- end
20
-
21
- context 'with a custom predicate' do
22
- let(:predicate) { -> input { double(success?: true, to_ast: [:foo]) } }
23
-
24
- let(:result) { rule.(test: true) }
25
-
26
- it 'delegates to_ast to response' do
27
- expect(result.to_ast).to eql([:foo])
28
- end
29
- end
30
- end
31
-
32
- context 'with a set rule as predicate' do
33
- subject(:rule) do
34
- Rule::Key.new(predicate, name: :address)
35
- end
36
-
37
- let(:predicate) do
38
- Rule::Set.new(
39
- [Rule::Value.new(key?.curry(:city)), Rule::Value.new(key?.curry(:zipcode))]
40
- )
41
- end
42
-
43
- it 'applies set rule to the value that passes' do
44
- result = rule.(address: { city: 'NYC', zipcode: '123' })
45
-
46
- expect(result).to be_success
47
- end
48
-
49
- it 'applies set rule to the value that fails' do
50
- result = rule.(address: { city: 'NYC' })
51
-
52
- expect(result).to be_failure
53
-
54
- expect(result.to_ast).to eql([
55
- :input, [
56
- :address,
57
- [:result, [
58
- { city: "NYC" },
59
- [:set, [[:result, [{ city: 'NYC' }, [:val, [:predicate, [:key?, [[:name, :zipcode], [:input, {:city=>"NYC"}]]]]]]]]]
60
- ]]
61
- ]
62
- ])
63
- end
64
- end
65
-
66
- context 'with an each rule as predicate' do
67
- subject(:rule) do
68
- Rule::Key.new(predicate, name: :nums)
69
- end
70
-
71
- let(:predicate) do
72
- Rule::Each.new(Rule::Value.new(str?))
73
- end
74
-
75
- it 'applies each rule to the value that passses' do
76
- result = rule.(nums: %w(1 2 3))
77
-
78
- expect(result).to be_success
79
-
80
- expect(result.to_ast).to eql([
81
- :input, [:nums, [:result, [%w(1 2 3), [:each, []]]]]
82
- ])
83
- end
84
-
85
- it 'applies each rule to the value that fails' do
86
- failure = rule.(nums: [1, '3', 3])
87
-
88
- expect(failure).to be_failure
89
-
90
- expect(failure.to_ast).to eql([
91
- :input, [
92
- :nums, [
93
- :result, [
94
- [1, '3', 3],
95
- [:each, [
96
- [:el, [0, [:result, [1, [:val, [:predicate, [:str?, [[:input, 1]]]]]]]]],
97
- [:el, [2, [:result, [3, [:val, [:predicate, [:str?, [[:input, 3]]]]]]]]]
98
- ]]
99
- ]
100
- ]
101
- ]
102
- ])
103
- end
104
- end
105
- end
106
-
107
- describe '#and' do
108
- let(:other) do
109
- Rule::Key.new(str?, name: [:user, :name])
110
- end
111
-
112
- it 'returns conjunction rule where value is passed to the right' do
113
- present_and_string = rule.and(other)
114
-
115
- expect(present_and_string.(user: { name: 'Jane' })).to be_success
116
-
117
- expect(present_and_string.(user: {})).to be_failure
118
- expect(present_and_string.(user: { name: 1 })).to be_failure
119
- end
120
- end
121
- end
@@ -1,30 +0,0 @@
1
- require 'dry/logic/rule'
2
-
3
- RSpec.describe Dry::Logic::Rule::Set do
4
- include_context 'predicates'
5
-
6
- subject(:rule) do
7
- Dry::Logic::Rule::Set.new([is_string, min_size.curry(6)])
8
- end
9
-
10
- let(:is_string) { Dry::Logic::Rule::Value.new(str?) }
11
- let(:min_size) { Dry::Logic::Rule::Value.new(min_size?) }
12
-
13
- describe '#call' do
14
- it 'applies its rules to the input' do
15
- expect(rule.('Address')).to be_success
16
- expect(rule.('Addr')).to be_failure
17
- end
18
- end
19
-
20
- describe '#to_ast' do
21
- it 'returns an array representation' do
22
- expect(rule.to_ast).to eql([
23
- :set, [
24
- [:val, [:predicate, [:str?, [[:input, Predicate::Undefined]]]]],
25
- [:val, [:predicate, [:min_size?, [[:num, 6], [:input, Predicate::Undefined]]]]]
26
- ]
27
- ])
28
- end
29
- end
30
- end
@@ -1,99 +0,0 @@
1
- require 'dry/logic/rule'
2
-
3
- RSpec.describe Dry::Logic::Rule::Value do
4
- include_context 'predicates'
5
-
6
- let(:is_nil) { Dry::Logic::Rule::Value.new(none?) }
7
-
8
- let(:is_string) { Dry::Logic::Rule::Value.new(str?) }
9
-
10
- let(:min_size) { Dry::Logic::Rule::Value.new(min_size?) }
11
-
12
- describe '#call' do
13
- it 'returns result of a predicate' do
14
- expect(is_string.(1)).to be_failure
15
- expect(is_string.('1')).to be_success
16
- end
17
-
18
- context 'with a composite rule' do
19
- subject(:rule) { Dry::Logic::Rule::Value.new(is_nil | is_string) }
20
-
21
- it 'returns a success for valid input' do
22
- expect(rule.(nil)).to be_success
23
- expect(rule.('foo')).to be_success
24
- end
25
-
26
- it 'returns a failure for invalid input' do
27
- expect(rule.(312)).to be_failure
28
- end
29
-
30
- it 'returns a failure result with curried args' do
31
- expect(rule.(312).to_ast).to eql(
32
- [:result, [312, [:val, [:predicate, [:str?, [[:input, 312]]]]]]]
33
- )
34
- end
35
- end
36
-
37
- context 'with a custom predicate' do
38
- subject(:rule) { Dry::Logic::Rule::Value.new(predicate) }
39
-
40
- let(:response) { double("response", success?: true) }
41
- let(:predicate) { double("predicate", arity: 1, curry: curried, call: Result.new(response, double("rule"), test: true)) }
42
- let(:curried) { double("curried", arity: 1, call: Result.new(response, double("rule"), test: true)) }
43
-
44
- let(:result) { rule.(test: true) }
45
-
46
- it 'calls its predicate returning custom result' do
47
- expect(result).to be_success
48
- end
49
-
50
- it 'exposes access to nested result' do
51
- expect(response).to receive(:[]).with(:foo).and_return(:bar)
52
- expect(result[:foo]).to be(:bar)
53
- end
54
-
55
- it 'returns nil from [] when response does not respond to it' do
56
- expect(result[:foo]).to be(nil)
57
- end
58
-
59
- it 'has no name by default' do
60
- expect(result.name).to be(nil)
61
- end
62
-
63
- context "works with predicates.arity == 0" do
64
- subject(:rule) { Dry::Logic::Rule::Value.new(predicate) }
65
-
66
- let(:predicate) { Dry::Logic::Predicate.new(:without_args) { true } }
67
- let(:result) { rule.('sutin') }
68
-
69
- it "calls its predicate without any args" do
70
- expect(result).to be_success
71
- end
72
- end
73
- end
74
- end
75
-
76
- describe '#and' do
77
- it 'returns a conjunction' do
78
- string_and_min_size = is_string.and(min_size.curry(3))
79
-
80
- expect(string_and_min_size.('abc')).to be_success
81
- expect(string_and_min_size.('abcd')).to be_success
82
-
83
- expect(string_and_min_size.(1)).to be_failure
84
- expect(string_and_min_size.('ab')).to be_failure
85
- end
86
- end
87
-
88
- describe '#or' do
89
- it 'returns a disjunction' do
90
- nil_or_string = is_nil.or(is_string)
91
-
92
- expect(nil_or_string.(nil)).to be_success
93
- expect(nil_or_string.('abcd')).to be_success
94
-
95
- expect(nil_or_string.(true)).to be_failure
96
- expect(nil_or_string.(1)).to be_failure
97
- end
98
- end
99
- end