blood_contracts-core 0.1.0 → 0.2.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.
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