payload 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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +135 -92
- data/lib/payload/container.rb +7 -10
- data/lib/payload/decorator_chain.rb +16 -2
- data/lib/payload/definition.rb +39 -0
- data/lib/payload/definition_list.rb +11 -6
- data/lib/payload/dependency_already_defined_error.rb +5 -0
- data/lib/payload/exported_definition.rb +12 -2
- data/lib/payload/factory.rb +1 -1
- data/lib/payload/factory_resolver.rb +18 -0
- data/lib/payload/service_resolver.rb +17 -0
- data/lib/payload/undefined_definition.rb +37 -0
- data/lib/payload/version.rb +1 -1
- data/spec/payload/container_spec.rb +11 -3
- data/spec/payload/decorator_chain_spec.rb +34 -0
- data/spec/payload/definition_list_spec.rb +78 -27
- data/spec/payload/definition_spec.rb +72 -0
- data/spec/payload/exported_definition_spec.rb +25 -2
- data/spec/payload/factory_resolver_spec.rb +22 -0
- data/spec/payload/service_resolver_spec.rb +23 -0
- data/spec/payload/undefined_definition_spec.rb +65 -0
- metadata +26 -19
- data/lib/payload/factory_definition.rb +0 -24
- data/lib/payload/service_definition.rb +0 -24
- data/spec/payload/factory_definition_spec.rb +0 -49
- data/spec/payload/service_definition_spec.rb +0 -39
@@ -1,5 +1,6 @@
|
|
1
|
+
require 'payload/definition'
|
1
2
|
require 'payload/exported_definition'
|
2
|
-
require 'payload/
|
3
|
+
require 'payload/undefined_definition'
|
3
4
|
|
4
5
|
module Payload
|
5
6
|
# Immutable list of definitions.
|
@@ -12,14 +13,18 @@ module Payload
|
|
12
13
|
@definitions = definitions
|
13
14
|
end
|
14
15
|
|
15
|
-
def add(name,
|
16
|
-
|
16
|
+
def add(name, resolver)
|
17
|
+
value = find(name).set(Definition.new(resolver))
|
18
|
+
self.class.new(definitions.merge(name => value))
|
17
19
|
end
|
18
20
|
|
19
21
|
def find(name)
|
20
|
-
definitions.fetch(name)
|
21
|
-
|
22
|
-
|
22
|
+
definitions.fetch(name) { UndefinedDefinition.new(name) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def decorate(name, block)
|
26
|
+
decorated = find(name).decorate(block)
|
27
|
+
self.class.new(@definitions.merge(name => decorated))
|
23
28
|
end
|
24
29
|
|
25
30
|
def export(names)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Payload
|
2
|
-
# Decorates a base definition such as {
|
2
|
+
# Decorates a base definition such as {ServiceResolver} to provide access to
|
3
3
|
# private dependencies in the {Container} from which the definition was
|
4
4
|
# exported.
|
5
5
|
#
|
@@ -13,7 +13,17 @@ module Payload
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def resolve(container)
|
16
|
-
|
16
|
+
definition.resolve(container.import(private_definitions))
|
17
17
|
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
other.is_a?(ExportedDefinition) &&
|
21
|
+
definition == other.definition &&
|
22
|
+
private_definitions == other.private_definitions
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_reader :definition, :private_definitions
|
18
28
|
end
|
19
29
|
end
|
data/lib/payload/factory.rb
CHANGED
@@ -6,7 +6,7 @@ module Payload
|
|
6
6
|
#
|
7
7
|
# @see Container#factory Container#factory for defining and using factories.
|
8
8
|
class Factory
|
9
|
-
# Used internally by {
|
9
|
+
# Used internally by {FactoryResolver}.
|
10
10
|
#
|
11
11
|
# @api private
|
12
12
|
def initialize(container, block, decorators)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'payload/factory'
|
2
|
+
|
3
|
+
module Payload
|
4
|
+
# Encapsulates logic for resolving factory definitions.
|
5
|
+
#
|
6
|
+
# Used internally by {Container}. Use {Container#factory}.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class FactoryResolver
|
10
|
+
def initialize(block)
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve(container, decorators)
|
15
|
+
Factory.new(container, @block, decorators)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Payload
|
2
|
+
# Encapsulates logic for resolving service definitions.
|
3
|
+
#
|
4
|
+
# Used internally by {Container}. Use {Container#service}.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class ServiceResolver
|
8
|
+
def initialize(block)
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def resolve(container, decorators)
|
13
|
+
base = @block.call(container)
|
14
|
+
decorators.decorate(base, container)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'payload/decorator_chain'
|
2
|
+
require 'payload/undefined_dependency_error'
|
3
|
+
|
4
|
+
module Payload
|
5
|
+
# Returns from {DefinitionList} when attempting to find an undefined
|
6
|
+
# definition.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class UndefinedDefinition
|
10
|
+
def initialize(name, decorators = DecoratorChain.new)
|
11
|
+
@name = name
|
12
|
+
@decorators = decorators
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.is_a?(UndefinedDefinition) && name == other.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolve(container)
|
20
|
+
raise UndefinedDependencyError, "No definition for dependency: #{name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def decorate(block)
|
24
|
+
self.class.new(@name, @decorators.add(block))
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(replacement)
|
28
|
+
@decorators.inject(replacement) do |definition, decorator|
|
29
|
+
definition.decorate(decorator)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
attr_reader :name
|
36
|
+
end
|
37
|
+
end
|
data/lib/payload/version.rb
CHANGED
@@ -68,10 +68,18 @@ describe Payload::Container do
|
|
68
68
|
expect(original[:example]).to eq('expected component')
|
69
69
|
end
|
70
70
|
|
71
|
+
it 'decorates a dependency which is defined later' do
|
72
|
+
container = build_container.
|
73
|
+
decorate(:example) { |component, _| "decorated #{component}" }.
|
74
|
+
service(:example) { |_| 'expected component' }
|
75
|
+
|
76
|
+
expect(container[:example]).to eq('decorated expected component')
|
77
|
+
end
|
78
|
+
|
71
79
|
it 'raises an exception for an unknown dependency' do
|
72
80
|
container = build_container
|
73
81
|
|
74
|
-
expect { container.decorate(:
|
82
|
+
expect { container.decorate(:undefined)[:undefined] }
|
75
83
|
.to raise_error(Payload::UndefinedDependencyError)
|
76
84
|
end
|
77
85
|
end
|
@@ -122,9 +130,9 @@ describe Payload::Container do
|
|
122
130
|
describe '#import' do
|
123
131
|
it 'returns a new container with the given definitions' do
|
124
132
|
first_export =
|
125
|
-
Payload::
|
133
|
+
Payload::ServiceResolver.new(lambda { |config| 'one' })
|
126
134
|
second_export =
|
127
|
-
Payload::
|
135
|
+
Payload::ServiceResolver.new(lambda { |config| 'two' })
|
128
136
|
definitions = Payload::DefinitionList.
|
129
137
|
new.
|
130
138
|
add(:one, first_export).
|
@@ -2,6 +2,10 @@ require 'spec_helper'
|
|
2
2
|
require 'payload/decorator_chain'
|
3
3
|
|
4
4
|
describe Payload::DecoratorChain do
|
5
|
+
it 'is enumerable' do
|
6
|
+
expect(Payload::DecoratorChain.new).to be_a(Enumerable)
|
7
|
+
end
|
8
|
+
|
5
9
|
describe '#decorate' do
|
6
10
|
it 'applies a series of decorators to a component' do
|
7
11
|
chain = Payload::DecoratorChain
|
@@ -26,4 +30,34 @@ describe Payload::DecoratorChain do
|
|
26
30
|
expect(result).to eq('Decorated original')
|
27
31
|
end
|
28
32
|
end
|
33
|
+
|
34
|
+
describe '#each' do
|
35
|
+
it 'yields each decorator' do
|
36
|
+
first = double('first')
|
37
|
+
second = double('second')
|
38
|
+
chain = Payload::DecoratorChain.new.add(first).add(second)
|
39
|
+
result = []
|
40
|
+
|
41
|
+
chain.each { |yielded| result << yielded }
|
42
|
+
|
43
|
+
expect(result).to eq([first, second])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#==' do
|
48
|
+
it 'is equal with the same decorators' do
|
49
|
+
expect(Payload::DecoratorChain.new.add(:one).add(:two)).
|
50
|
+
to eq(Payload::DecoratorChain.new.add(:one).add(:two))
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'is unequal with different decorators' do
|
54
|
+
expect(Payload::DecoratorChain.new.add(:one).add(:two)).
|
55
|
+
not_to eq(Payload::DecoratorChain.new.add(:one).add(:three))
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'is unequal with another type' do
|
59
|
+
expect(Payload::DecoratorChain.new.add(:one).add(:two)).
|
60
|
+
not_to eq(:other)
|
61
|
+
end
|
62
|
+
end
|
29
63
|
end
|
@@ -4,22 +4,74 @@ require 'payload/definition_list'
|
|
4
4
|
describe Payload::DefinitionList do
|
5
5
|
describe '#add' do
|
6
6
|
it 'adds a dependency to be found later' do
|
7
|
-
|
7
|
+
resolver = double('resolver')
|
8
|
+
definition = Payload::Definition.new(resolver)
|
8
9
|
definition_list = Payload::DefinitionList.new
|
9
10
|
|
10
|
-
defined = definition_list.add(:example,
|
11
|
+
defined = definition_list.add(:example, resolver)
|
11
12
|
|
12
13
|
expect(defined.find(:example)).to eq(definition)
|
13
14
|
end
|
14
15
|
|
16
|
+
it 'does not replace an existing definition' do
|
17
|
+
definition_list = Payload::DefinitionList.new
|
18
|
+
|
19
|
+
defined = definition_list.add(:example, :original)
|
20
|
+
|
21
|
+
expect { defined.add(:example, :replacement) }.
|
22
|
+
to raise_error(Payload::DependencyAlreadyDefinedError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not mutate the list' do
|
26
|
+
resolver = double('resolver')
|
27
|
+
definition_list = Payload::DefinitionList.new
|
28
|
+
|
29
|
+
definition_list.add(:example, resolver)
|
30
|
+
|
31
|
+
expect(definition_list.find(:example)).
|
32
|
+
to eq(Payload::UndefinedDefinition.new(:example))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#decorate' do
|
37
|
+
it 'replaces a dependency with a decorated version' do
|
38
|
+
resolver = double('resolver')
|
39
|
+
decorator = double('decorator')
|
40
|
+
decorated = Payload::Definition.new(resolver).decorate(decorator)
|
41
|
+
definition_list = Payload::DefinitionList.new
|
42
|
+
|
43
|
+
defined = definition_list.
|
44
|
+
add(:example, resolver).
|
45
|
+
decorate(:example, decorator)
|
46
|
+
|
47
|
+
expect(defined.find(:example)).to eq(decorated)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'decorates an undefined dependency' do
|
51
|
+
resolver = double('resolver')
|
52
|
+
decorator = double('decorator')
|
53
|
+
decorated = Payload::Definition.new(resolver).decorate(decorator)
|
54
|
+
definition_list = Payload::DefinitionList.new
|
55
|
+
|
56
|
+
defined = definition_list.
|
57
|
+
decorate(:example, decorator).
|
58
|
+
add(:example, resolver)
|
59
|
+
|
60
|
+
expect(defined.find(:example)).to eq(decorated)
|
61
|
+
end
|
62
|
+
|
15
63
|
it 'does not mutate the list' do
|
16
|
-
|
64
|
+
resolver = double('resolver')
|
65
|
+
decorator = double('decorator')
|
66
|
+
definition = Payload::Definition.new(resolver)
|
17
67
|
definition_list = Payload::DefinitionList.new
|
18
68
|
|
19
|
-
definition_list.
|
69
|
+
defined = definition_list.
|
70
|
+
add(:example, resolver)
|
71
|
+
|
72
|
+
defined.decorate(:example, decorator)
|
20
73
|
|
21
|
-
expect
|
22
|
-
to raise_error(Payload::UndefinedDependencyError)
|
74
|
+
expect(defined.find(:example)).to eq(definition)
|
23
75
|
end
|
24
76
|
end
|
25
77
|
|
@@ -30,23 +82,21 @@ describe Payload::DefinitionList do
|
|
30
82
|
add(:one, 'first').
|
31
83
|
add(:two, 'second').
|
32
84
|
add(:three, 'third')
|
33
|
-
first_exported = double('exported_first')
|
34
|
-
allow(Payload::ExportedDefinition).
|
35
|
-
to receive(:new).
|
36
|
-
with('first', definition_list).
|
37
|
-
and_return(first_exported)
|
38
|
-
second_exported = double('exported_second')
|
39
|
-
allow(Payload::ExportedDefinition).
|
40
|
-
to receive(:new).
|
41
|
-
with('second', definition_list).
|
42
|
-
and_return(second_exported)
|
43
85
|
|
44
86
|
exported = definition_list.export([:one, :two])
|
45
87
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
88
|
+
first = Payload::ExportedDefinition.new(
|
89
|
+
Payload::Definition.new('first'),
|
90
|
+
definition_list
|
91
|
+
)
|
92
|
+
second = Payload::ExportedDefinition.new(
|
93
|
+
Payload::Definition.new('second'),
|
94
|
+
definition_list
|
95
|
+
)
|
96
|
+
third = Payload::UndefinedDefinition.new(:three)
|
97
|
+
expect(exported.find(:one)).to eq(first)
|
98
|
+
expect(exported.find(:two)).to eq(second)
|
99
|
+
expect(exported.find(:three)).to eq(third)
|
50
100
|
end
|
51
101
|
end
|
52
102
|
|
@@ -56,23 +106,24 @@ describe Payload::DefinitionList do
|
|
56
106
|
right = Payload::DefinitionList.new.add(:two, 'second')
|
57
107
|
merged = left.import(right)
|
58
108
|
|
59
|
-
expect(merged.find(:one)).to eq('first')
|
60
|
-
expect(merged.find(:two)).to eq('second')
|
109
|
+
expect(merged.find(:one)).to eq(Payload::Definition.new('first'))
|
110
|
+
expect(merged.find(:two)).to eq(Payload::Definition.new('second'))
|
61
111
|
end
|
62
112
|
end
|
63
113
|
|
64
114
|
describe '#find' do
|
65
|
-
it '
|
115
|
+
it 'returns an unknown definition' do
|
66
116
|
definition_list = Payload::DefinitionList.new
|
67
117
|
|
68
|
-
expect
|
69
|
-
to
|
118
|
+
expect(definition_list.find(:example)).
|
119
|
+
to eq(Payload::UndefinedDefinition.new(:example))
|
70
120
|
end
|
71
121
|
|
72
122
|
it 'returns an existing definition' do
|
73
|
-
|
123
|
+
resolver = double('resolver')
|
124
|
+
definition = Payload::Definition.new(resolver)
|
74
125
|
definition_list =
|
75
|
-
Payload::DefinitionList.new.add(:example,
|
126
|
+
Payload::DefinitionList.new.add(:example, resolver)
|
76
127
|
|
77
128
|
expect(definition_list.find(:example)).to eq(definition)
|
78
129
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/definition'
|
3
|
+
|
4
|
+
describe Payload::Definition do
|
5
|
+
describe '#resolve' do
|
6
|
+
it 'provides the container and decorators to its resolver' do
|
7
|
+
resolved = double('resolved')
|
8
|
+
resolver = double('resolver')
|
9
|
+
container = double('container')
|
10
|
+
decorators = double('decorators')
|
11
|
+
resolver.stub(:resolve).and_return(resolved)
|
12
|
+
definition = Payload::Definition.new(resolver, decorators)
|
13
|
+
|
14
|
+
result = definition.resolve(container)
|
15
|
+
|
16
|
+
expect(result).to eq(resolved)
|
17
|
+
expect(resolver).to have_received(:resolve).with(container, decorators)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#decorate' do
|
22
|
+
it 'returns a new decorated service' do
|
23
|
+
decorator = double('first_decorator')
|
24
|
+
resolver = double('resolver')
|
25
|
+
resolver.stub(:resolve)
|
26
|
+
container = double('container')
|
27
|
+
decorated = double('decorated')
|
28
|
+
decorators = double('decorators')
|
29
|
+
decorators.stub(:add).and_return(decorated)
|
30
|
+
Payload::DecoratorChain.stub(:new).and_return(decorators)
|
31
|
+
definition = Payload::Definition.new(resolver)
|
32
|
+
|
33
|
+
definition.
|
34
|
+
decorate(decorator).
|
35
|
+
resolve(container)
|
36
|
+
|
37
|
+
expect(decorators).to have_received(:add).with(decorator)
|
38
|
+
expect(resolver).to have_received(:resolve).with(container, decorated)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#set' do
|
43
|
+
it 'raises an already defined error' do
|
44
|
+
service = Payload::Definition.new(double('resolver'))
|
45
|
+
|
46
|
+
expect { service.set(double('replacement')) }.
|
47
|
+
to raise_error(Payload::DependencyAlreadyDefinedError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#==' do
|
52
|
+
it 'is true with the same resolver and decorators' do
|
53
|
+
expect(Payload::Definition.new(:resolver).decorate(:decorator)).
|
54
|
+
to eq(Payload::Definition.new(:resolver).decorate(:decorator))
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'is false with a different resolver' do
|
58
|
+
expect(Payload::Definition.new(:resolver).decorate(:decorator)).
|
59
|
+
not_to eq(Payload::Definition.new(:other_resolver).decorate(:decorator))
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'is false with different decorators' do
|
63
|
+
expect(Payload::Definition.new(:resolver).decorate(:decorator)).
|
64
|
+
not_to eq(Payload::Definition.new(:resolver).decorate(:other_decorator))
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'is false with a non-definition' do
|
68
|
+
expect(Payload::Definition.new(:resolver).decorate(:decorator)).
|
69
|
+
not_to eq(:other)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|