blood_contracts-core 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d50ef9d60bc174f486385734c98d18ade0fa6a21cdc809d0a3bead98c044aac
4
- data.tar.gz: c55c10f1248097678f6feaab0bfd055494c2de7da69b663ec2458b9449b8b560
3
+ metadata.gz: bc10cd9d71e3f255a6e3e520acb9fa39e307308a97b227107a0deca3d97b5cb6
4
+ data.tar.gz: 96d8127169a6f4bef13b1f14128c599b78038c151547521ca5faf668f6d18975
5
5
  SHA512:
6
- metadata.gz: 469b03faa75fa6a46448aee7592cbbabe2a4ad53c5dca11c7395e67a9fdfcfc6c4bc4364afde47440778befaf78344f856b65f3b6c1ad0c267d1943629152c53
7
- data.tar.gz: 4354966854dc2cacaf189fec6531555e5f5784b915e34757db40d896fd095f9666a58b62dacd00874b41b467557abf0fd83dc6fdd7395a0d2353b154e4e5ba46
6
+ metadata.gz: 41554f77f48de3d91bc3fbb63662db1a3064f6e2c1fe44a7913c3e14658501b7de908a230e0f67410779ce4403d116157f64e687b16090d7be0e0acb04e5c9b0
7
+ data.tar.gz: 9d0c18ec90d9f283a13754f1dc78c43c117afe0c688bd321a4aa26101c0396eda9bb0a46061ceae2ee99288b8d4733e5981e000faeea176a7d487cf609aaf897
data/bin/console CHANGED
@@ -7,8 +7,8 @@ require "blood_contracts/core"
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
10
+ require "pry"
11
+ Pry.start
12
12
 
13
- require "irb"
14
- IRB.start(__FILE__)
13
+ # require "irb"
14
+ # IRB.start(__FILE__)
@@ -23,9 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_runtime_dependency "dry-initializer", "~> 2.0"
27
-
28
26
  spec.add_development_dependency "bundler", "~> 2.0"
27
+ spec.add_development_dependency "pry"
29
28
  spec.add_development_dependency "rake", "~> 10.0"
30
29
  spec.add_development_dependency "rspec", "~> 3.0"
31
30
  end
@@ -2,55 +2,74 @@ require 'json'
2
2
  require 'blood_contracts/core'
3
3
 
4
4
  module Types
5
- class JSON < BloodType
6
- param :json_string
7
-
5
+ class JSON < BC::Refined
8
6
  def match
9
- @parsed = ::JSON.parse(unpack_refined(json_string))
10
- self
11
- rescue StandardError => error
12
- @errors << error
13
- self
7
+ super do
8
+ begin
9
+ context[:parsed] = ::JSON.parse(unpack_refined(@value))
10
+ self
11
+ rescue StandardError => error
12
+ failure(error)
13
+ end
14
+ end
14
15
  end
15
16
 
16
17
  def unpack
17
- @parsed
18
+ super { |match| match.context[:parsed] }
18
19
  end
19
20
  end
20
21
 
21
- class Tariff < BloodType
22
- param :json
23
-
22
+ class Tariff < BC::Refined
24
23
  def match
25
24
  super do
26
- @parsed = unpack_refined(self.json).slice("cost", "cur").compact
27
- @errors << :not_a_tariff if @parsed.size != 2
28
- self
25
+ context[:data] = unpack_refined(@value).slice("cost", "cur").compact
26
+ context[:tariff_context] = 1
27
+ return self if context[:data].size == 2
28
+ failure(:not_a_tariff)
29
29
  end
30
30
  end
31
- end
32
31
 
33
- class Error < BloodType
34
- param :json
32
+ def unpack
33
+ super { |match| match.context[:data] }
34
+ end
35
+ end
35
36
 
37
+ class Error < BC::Refined
36
38
  def match
37
39
  super do
38
- @parsed = unpack_refined(self.json).slice("code", "message").compact
39
- @errors << :not_an_error if @parsed.size != 2
40
- self
40
+ context[:data] = unpack_refined(value).slice("code", "message").compact
41
+ context[:known_error_context] = 1
42
+ return self if context[:data].size == 2
43
+ failure(:not_a_known_error)
41
44
  end
42
45
  end
43
- end
44
46
 
45
- class Response < BloodType
46
- option :raw, BC::Anything.method(:new)
47
- option :parsed, JSON.method(:new), default: -> { raw }
48
- option :mapped, (Tariff | Error).method(:new), default: -> { parsed }
47
+ def unpack
48
+ super { |match| match.context[:data] }
49
+ end
49
50
  end
51
+
52
+ Response = BC::Pipe.new(
53
+ BC::Anything, JSON, (Tariff | Error | Tariff),
54
+ names: [:raw, :parsed, :mapped]
55
+ )
56
+
57
+ # The same is
58
+ # Response = BC::Anything.and_then(JSON).and_then(Tariff | Error) do
59
+ # class Response
60
+ # self.names = [:raw, :parsed, :mapped]
61
+ # end
62
+ #
63
+ # or
64
+ #
65
+ # Response = BC::Pipe.new(BC::Anything, JSON, Tariff | Error) do
66
+ # self.names = [:raw, :parsed, :mapped]
67
+ # end
50
68
  end
51
69
 
52
70
  def match_response(response)
53
- case (match = Types::Response.match(raw: response))
71
+ match = Types::Response.match(response)
72
+ case match
54
73
  when BC::ContractFailure
55
74
  puts "Honeybadger.notify 'Unexpected behavior in Russian Post', context: #{match.context}"
56
75
  puts "render json: { errors: 'Ooops! Not working, we've been notified. Please, try again later' }"
@@ -61,12 +80,14 @@ def match_response(response)
61
80
  end
62
81
  when Types::Tariff
63
82
  # работаем с тарифом
64
- puts "render json: { tariff: #{match.unpack.mapped} }"
83
+ puts "match.context # => #{match.context} \n\n"
84
+ puts "render json: { tariff: #{match.unpack} }"
65
85
  when Types::Error
66
86
  # работаем с ошибкой, e.g. адрес слишком длинный
67
- puts "render json: { errors: [#{match.unpack.mapped.unpack['message']}] } }"
87
+ puts "match.context # => #{match.context} \n\n"
88
+ puts "render json: { errors: [#{match.unpack['message']}] } }"
68
89
  else
69
- binding.irb
90
+ require'pry';binding.pry
70
91
  end
71
92
  end
72
93
 
@@ -85,7 +106,7 @@ match_response(error_response)
85
106
  puts "#{'=' * 20}================================#{'=' * 20}"
86
107
 
87
108
 
88
- puts "\n\n\n"
109
+ puts "ss => errors }\n\n\n"
89
110
  puts "#{'=' * 20} WHEN UNEXPECTED RESPONSE: #{'=' * 20}"
90
111
  invalid_response = '<xml>'
91
112
  match_response(invalid_response)
@@ -0,0 +1,72 @@
1
+ module BloodContracts
2
+ module Core
3
+ class Pipe < Refined
4
+ class << self
5
+ attr_reader :steps, :names, :finalized
6
+
7
+ def new(*args)
8
+ return super(*args) if finalized
9
+ if args.last.is_a?(Hash)
10
+ names = args.pop.delete(:names)
11
+ end
12
+ names ||= []
13
+
14
+ raise ArgumentError unless args.all?(Class)
15
+ pipe = Class.new(Pipe) { def inspect; super; end }
16
+ pipe.instance_variable_set(:@steps, args)
17
+ pipe.instance_variable_set(:@names, names)
18
+ pipe.instance_variable_set(:@finalized, true)
19
+ pipe
20
+ end
21
+
22
+ def and_then(other_type)
23
+ raise ArgumentError unless Class === other_type
24
+ pipe = Class.new(Pipe) { def inspect; super; end }
25
+ pipe.instance_variable_set(:@steps, args)
26
+ pipe.instance_variable_set(:@names, kwargs[:names].to_a)
27
+ pipe.instance_variable_set(:@finalized, true)
28
+ pipe
29
+ end
30
+ alias :> :and_then
31
+ end
32
+
33
+ def match
34
+ super do
35
+ index = 0
36
+ self.class.steps.reduce(value) do |next_value, step|
37
+ unpacked_value = unpack_refined(next_value)
38
+ match = step.match(unpacked_value)
39
+
40
+ share_context_with(match) do |context|
41
+ context[:steps][step_name(index)] = unpacked_value
42
+ index += 1
43
+ end
44
+
45
+ break match if match.invalid?
46
+ next refine_value(yield(match)) if block_given?
47
+ match
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def step_name(index)
55
+ self.class.names[index] || index
56
+ end
57
+
58
+ def steps_with_names
59
+ steps = if self.class.names.empty?
60
+ self.class.steps.map(&:to_s)
61
+ else
62
+ self.class.steps.zip(self.class.names).map { |k, n| "#{k}(#{n})" }
63
+ end
64
+ end
65
+
66
+ def inspect
67
+ require'pry';binding.pry
68
+ "#<pipe #{self.class.name} = #{steps_with_names.join(' > ')} (value=#{@value})>"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,126 @@
1
+ module BloodContracts
2
+ module Core
3
+ class Refined
4
+ class << self
5
+ def or_a(other_type)
6
+ BC::Sum.new(self, other_type)
7
+ end
8
+ alias :or_an :or_a
9
+ alias :| :or_a
10
+
11
+ def and_then(other_type)
12
+ BC::Pipe.new(self, other_type)
13
+ end
14
+ alias :> :and_then
15
+
16
+ def match(*args)
17
+ new(*args).match
18
+ end
19
+ alias :call :match
20
+
21
+ def ===(object)
22
+ return object.to_ary.any?(self) if object.is_a?(Tuple)
23
+ super
24
+ end
25
+ end
26
+
27
+ attr_accessor :context
28
+ attr_reader :errors, :value
29
+
30
+ def initialize(value, context: Hash.new { |h,k| h[k] = Hash.new }, **)
31
+ @errors = []
32
+ @context = context
33
+ @value = value
34
+ end
35
+
36
+ def match
37
+ return @match if defined? @match
38
+ return @match = yield if block_given?
39
+ self
40
+ end
41
+ alias :call :match
42
+
43
+ def valid?
44
+ match.errors.empty?
45
+ end
46
+ def invalid?; !valid?; end
47
+
48
+ def unpack
49
+ raise "This is not what you're looking for" if match.invalid?
50
+ return yield(match) if block_given?
51
+
52
+ unpack_refined @value
53
+ end
54
+
55
+ def failure(error = nil, errors: @errors, context: @context)
56
+ errors << error if error
57
+ ContractFailure.new(
58
+ { self.class => errors }, context: context
59
+ )
60
+ end
61
+
62
+ def inspect
63
+ require'pry';binding.pry
64
+ super
65
+ end
66
+
67
+ protected
68
+
69
+ def refined?(object)
70
+ object.class < BloodContracts::Core::Refined
71
+ end
72
+
73
+ def share_context_with(match)
74
+ match.context = @context.merge!(match.context)
75
+ yield(match.context)
76
+ end
77
+
78
+ def refine_value(value)
79
+ refined?(value) ? value.match : Anything.new(value)
80
+ end
81
+
82
+ def unpack_refined(value)
83
+ refined?(value) ? value.unpack : value
84
+ end
85
+
86
+ def errors_by_type(matches)
87
+ Hash[
88
+ matches.map(&:class).zip(matches.map(&:errors))
89
+ ].delete_if { |_, errors| errors.empty? }
90
+ end
91
+ end
92
+
93
+ class ContractFailure < Refined
94
+ def initialize(*)
95
+ super
96
+ @context.merge!(errors: @value.to_h)
97
+ end
98
+
99
+ def errors
100
+ context[:errors]
101
+ end
102
+
103
+ def match
104
+ self
105
+ end
106
+
107
+ def unpack
108
+ context
109
+ end
110
+ end
111
+
112
+ class Anything < Refined
113
+ def match
114
+ self
115
+ end
116
+
117
+ def valid?
118
+ true
119
+ end
120
+
121
+ def unpack
122
+ @value
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,48 @@
1
+ require 'set'
2
+
3
+ module BloodContracts
4
+ module Core
5
+ class Sum < Refined
6
+ class << self
7
+ attr_reader :sum_of, :finalized
8
+
9
+ def new(*args)
10
+ return super if finalized
11
+
12
+ sum = Class.new(Sum) { def inspect; super; end }
13
+ sum.instance_variable_set(:@sum_of, ::Set.new(args))
14
+ sum.instance_variable_set(:@finalized, true)
15
+ sum
16
+ end
17
+
18
+ def or_a(other_type)
19
+ sum = Class.new(Sum) { def inspect; super; end }
20
+ sum.instance_variable_set(:@sum_of, ::Set.new(self.sum_of << other_type))
21
+ sum.instance_variable_set(:@finalized, true)
22
+ sum
23
+ end
24
+ alias :or_an :or_a
25
+ alias :| :or_a
26
+ end
27
+
28
+ def match
29
+ super do
30
+ or_matches = self.class.sum_of.map do |type|
31
+ match = type.match(@value, context: @context)
32
+ end
33
+
34
+ if (match = or_matches.find(&:valid?))
35
+ match.context[:errors].merge(errors_by_type(or_matches))
36
+ match
37
+ else
38
+ failure(errors: errors_by_type(or_matches))
39
+ end
40
+ end
41
+ end
42
+
43
+ def inspect
44
+ "#<sum #{self.class.name} is #{self.class.sum_of.to_a.join(' or ')} (value=#{@value})>"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ module BloodContracts
2
+ module Core
3
+ class Tuple < Refined
4
+ class << self
5
+ attr_reader :attributes, :names, :finalized
6
+
7
+ def new(*args, **kwargs)
8
+ return super(*args, **kwargs) if finalized
9
+
10
+ raise ArgumentError unless args.all?(Class)
11
+ pipe = Class.new(Tuple) { def inspect; super; end }
12
+ pipe.instance_variable_set(:@attributes, args)
13
+ pipe.instance_variable_set(:@names, kwargs[:names].to_a)
14
+ pipe.instance_variable_set(:@finalized, true)
15
+ pipe
16
+ end
17
+ end
18
+
19
+ attr_reader :values
20
+ def initialize(*args)
21
+ super
22
+ @values = args
23
+ end
24
+
25
+ def match
26
+ super do
27
+ matches = self.class.attributes.zip(values).map do |(type, value)|
28
+ type.match(value, context: @context)
29
+ end
30
+ next self unless (failure = matches.find?(&:invalid?)).nil?
31
+ failure
32
+ end
33
+ end
34
+
35
+ def unpack
36
+ super { |match| match.values.map(&method(:unpack_refined)) }
37
+ end
38
+ alias :to_ary :unpack
39
+
40
+ private def values_by_names
41
+ if self.class.names.empty?
42
+ self.values
43
+ else
44
+ self.class.names.zip(attributes).map { |k, v| [k, v].join('=') }
45
+ end
46
+ end
47
+
48
+ private def inspect
49
+ "#<tuple #{self.class.name} (#{values_by_names.join(',')}>"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  module BloodContracts
2
2
  module Core
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -1,30 +1,13 @@
1
- require "dry-initializer"
2
- require_relative "./core/contract.rb"
3
- require_relative "./core/type.rb"
1
+ require_relative "./core/refined.rb"
2
+ require_relative "./core/pipe.rb"
3
+ require_relative "./core/sum.rb"
4
+ require_relative "./core/tuple.rb"
4
5
  require_relative "./core/version.rb"
5
6
 
6
- BloodContract = BloodContracts::Core::Contract
7
- BloodType = BloodContracts::Core::Type
8
7
 
9
8
  module BloodContracts
10
9
  module Core; end
11
-
12
- class ContractFailure < Core::Type
13
- def errors
14
- context[:errors].to_h
15
- end
16
-
17
- def unpack
18
- context
19
- end
20
- end
21
-
22
- class Anything < Core::Type
23
- param :data
24
- end
25
10
  end
26
11
 
27
- module BC
28
- Anything = BloodContracts::Anything
29
- ContractFailure = BloodContracts::ContractFailure
30
- end
12
+ BC = BloodContracts::Core
13
+
metadata CHANGED
@@ -1,23 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blood_contracts-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Dolganov
8
8
  autorequire:
9
9
  bindir: bin/
10
10
  cert_chain: []
11
- date: 2019-03-23 00:00:00.000000000 Z
11
+ date: 2019-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: dry-initializer
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.0'
20
- type: :runtime
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: pry
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '2.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -88,7 +88,10 @@ files:
88
88
  - lib/blood_contracts-core.rb
89
89
  - lib/blood_contracts/core.rb
90
90
  - lib/blood_contracts/core/contract.rb
91
- - lib/blood_contracts/core/type.rb
91
+ - lib/blood_contracts/core/pipe.rb
92
+ - lib/blood_contracts/core/refined.rb
93
+ - lib/blood_contracts/core/sum.rb
94
+ - lib/blood_contracts/core/tuple.rb
92
95
  - lib/blood_contracts/core/version.rb
93
96
  homepage: https://github.com/sclinede/blood_contracts-core
94
97
  licenses:
@@ -1,197 +0,0 @@
1
- module BloodContracts
2
- module Core
3
- class Type
4
- extend Dry::Initializer
5
-
6
- # TODO: share context between Refinement Types match
7
- class << self
8
- def attributes
9
- @attributes ||= []
10
- end
11
-
12
- def param(name, *)
13
- @attributes = attributes | [name.to_sym]
14
- super
15
- end
16
-
17
- def option(name, type = nil, as: name, **)
18
- @attributes = attributes | [as.to_sym]
19
- super
20
- end
21
-
22
- def single?
23
- attributes.size <= 1
24
- end
25
-
26
- attr_reader :sum
27
- def or_a(other_type)
28
- c = Class.new(Anything)
29
- c.instance_variable_set(
30
- :@sum, Set.new([self] + self.sum.to_a << other_type)
31
- )
32
- c
33
- end
34
- alias :or_an :or_a
35
- alias :| :or_a
36
-
37
- def match(*args)
38
- new(*args).match
39
- end
40
- alias :call :match
41
-
42
- def ===(object)
43
- return true if (result = super)
44
- return result unless object.class < BloodContracts::Core::Type
45
- if object.class.send(:single?)
46
- return result if object.class.sum.to_a.empty?
47
- return object.class.sum.any? { |or_type| self == or_type }
48
- end
49
-
50
- object.attributes.values.any? { |sub_type| self === sub_type }
51
- end
52
- end
53
-
54
- attr_accessor :context
55
- attr_reader :errors, :value_handler
56
- def initialize(*args, **kwargs)
57
- @errors = []
58
- @context = kwargs.delete(:context) || {}
59
- @value_handler = self.class.single? ? Single.new(self) : Tuple.new(self)
60
-
61
- super(*args, **kwargs)
62
- end
63
-
64
- def attributes
65
- @attributes ||= self.class.dry_initializer.attributes(self)
66
- end
67
-
68
- def match
69
- return @match if defined? @match
70
- return @match = yield if block_given?
71
- @match = value_handler.match
72
- end
73
- alias :call :match
74
-
75
- def valid?
76
- value_handler.valid?
77
- end
78
- def invalid?; !valid?; end
79
-
80
- def unpack
81
- value_handler.unpack
82
- end
83
-
84
- def single?
85
- Single === value_handler
86
- end
87
-
88
- def refined?(object)
89
- BloodContracts::Core::Type === object
90
- end
91
-
92
- def unpack_refined(value)
93
- refined?(value) ? value.unpack : value
94
- end
95
-
96
- protected
97
-
98
- class ValueHandler
99
- def initialize(subject)
100
- @subject = subject
101
- end
102
-
103
- def errors_by_type(matches)
104
- Hash[
105
- matches.map(&:class).zip(matches.map(&:errors))
106
- ].delete_if { |_, errors| errors.empty? }
107
- end
108
-
109
- def refined?(object)
110
- @subject.refined?(object)
111
- end
112
-
113
- def unpack_refined(value)
114
- @subject.unpack_refined(value)
115
- end
116
- end
117
-
118
- class Tuple < ValueHandler
119
- def initialize(*)
120
- super
121
- @wrapper = Struct.new(*@subject.class.attributes, keyword_init: true)
122
- end
123
-
124
- def wrap_unpacked(match)
125
- @wrapper.new(
126
- match.attributes.transform_values(&method(:unpack_refined))
127
- )
128
- end
129
-
130
- def match
131
- failed_match =
132
- @subject.attributes.values.lazy.map(&:match).find(&:invalid?)
133
-
134
- if failed_match
135
- BloodContracts::ContractFailure.new(
136
- context: { errors: errors_by_type([failed_match]) }
137
- )
138
- else
139
- @subject
140
- end
141
- end
142
-
143
- def unpack
144
- raise "This is not what you're looking for" if invalid?
145
-
146
- wrap_unpacked(@subject.match)
147
- end
148
-
149
- def invalid?
150
- BloodContracts::ContractFailure === @subject.match
151
- end
152
- def valid?; !invalid?; end
153
-
154
- def single?
155
- false
156
- end
157
- end
158
-
159
- class Single < ValueHandler
160
- def match
161
- sum = @subject.class.sum.to_a
162
- return refined?(value) ? value.match : @subject if sum.empty?
163
-
164
- or_matches = sum.map { |type| type.match(value) }
165
- if (match = or_matches.find(&:valid?))
166
- match.context.merge!(errors: errors_by_type(or_matches))
167
- match
168
- else
169
- BloodContracts::ContractFailure.new(
170
- context: { errors: errors_by_type(or_matches) }
171
- )
172
- end
173
- end
174
-
175
- def valid?
176
- @subject.errors.empty?
177
- end
178
-
179
- def unpack
180
- raise "This is not what you're looking for" unless valid?
181
-
182
- unpack_refined value
183
- end
184
-
185
- def single?
186
- true
187
- end
188
-
189
- private
190
-
191
- def value
192
- @value ||= @subject.attributes.values.first
193
- end
194
- end
195
- end
196
- end
197
- end