clean-architecture 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 +4 -4
- data/.reek.yml +32 -0
- data/.rubocop.yml +3 -11
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +33 -9
- data/README.md +94 -4
- data/clean-architecture.gemspec +2 -0
- data/lib/clean_architecture/all.rb +2 -0
- data/lib/clean_architecture/entities/all.rb +1 -0
- data/lib/clean_architecture/entities/failure_details.rb +21 -0
- data/lib/clean_architecture/entities/use_case_history_entry.rb +3 -2
- data/lib/clean_architecture/matchers/all.rb +5 -0
- data/lib/clean_architecture/matchers/use_case_result.rb +45 -0
- data/lib/clean_architecture/queries/all.rb +1 -0
- data/lib/clean_architecture/queries/http_failure_code.rb +30 -0
- data/lib/clean_architecture/queries/http_success_code.rb +2 -1
- data/lib/clean_architecture/serializers/html_response_from_result.rb +17 -6
- data/lib/clean_architecture/serializers/json_response_from_result.rb +10 -22
- data/lib/clean_architecture/strategies/actor_gets_authorized_then_does_work.rb +8 -1
- data/lib/clean_architecture/types.rb +7 -0
- data/lib/clean_architecture/version.rb +1 -1
- metadata +36 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c49173a8914ea9260202535efe792a4a678619d
|
|
4
|
+
data.tar.gz: 508d35b34541f7b631cf1ea8fd994b4b20e08927
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9cf655b8ae3fddb3071fb12b817b4b3e08863a34e0007a06d15c814c3cbde3d43057f62dca01ade1bfe02aed5634850e5b2852d6c9647a83055f598494cb6ec4
|
|
7
|
+
data.tar.gz: b92ebb0350cd0d79ed012c0318560bfef0148fa5669a6e4837f1c7f48a87611e44dafea1b6cfa75dc228946e28c34e42b44572f3a4c9c238b9e626d372c3203f
|
data/.reek.yml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
detectors:
|
|
3
|
+
IrresponsibleModule:
|
|
4
|
+
enabled: false
|
|
5
|
+
TooManyStatements:
|
|
6
|
+
enabled: true
|
|
7
|
+
max_statements: 10
|
|
8
|
+
NilCheck:
|
|
9
|
+
enabled: false
|
|
10
|
+
directories:
|
|
11
|
+
app/controllers:
|
|
12
|
+
NestedIterators:
|
|
13
|
+
max_allowed_nesting: 2
|
|
14
|
+
UnusedPrivateMethod:
|
|
15
|
+
enabled: false
|
|
16
|
+
app/helpers:
|
|
17
|
+
UtilityFunction:
|
|
18
|
+
enabled: false
|
|
19
|
+
exclude_paths:
|
|
20
|
+
- app/assets
|
|
21
|
+
- bin
|
|
22
|
+
- client/node_modules
|
|
23
|
+
- config
|
|
24
|
+
- coverage
|
|
25
|
+
- data
|
|
26
|
+
- db
|
|
27
|
+
- dw
|
|
28
|
+
- log
|
|
29
|
+
- phrase
|
|
30
|
+
- public
|
|
31
|
+
- tmp
|
|
32
|
+
- vendor
|
data/.rubocop.yml
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
Documentation:
|
|
3
3
|
Enabled: false
|
|
4
|
-
Rails:
|
|
5
|
-
Enabled: true
|
|
6
4
|
AllCops:
|
|
7
5
|
Include:
|
|
8
|
-
- app/**/*.rb
|
|
9
6
|
- lib/**/*.rb
|
|
10
7
|
- spec/**/*.rb
|
|
11
8
|
Exclude:
|
|
12
|
-
- app/assets/**/*
|
|
13
9
|
- bin/**/*
|
|
14
|
-
- client/node_modules/**/*
|
|
15
10
|
- config/**/*
|
|
16
11
|
- coverage/**/*
|
|
17
|
-
- data/**/*
|
|
18
|
-
- db/**/*
|
|
19
|
-
- db_*/**/*
|
|
20
|
-
- dw/**/*
|
|
21
12
|
- log/**/*
|
|
22
|
-
- phrase/**/*
|
|
23
13
|
- public/**/*
|
|
24
14
|
- tmp/**/*
|
|
25
15
|
- vendor/**/*
|
|
26
|
-
TargetRubyVersion: 2.
|
|
16
|
+
TargetRubyVersion: 2.4
|
|
27
17
|
Metrics/LineLength:
|
|
28
18
|
Max: 100
|
|
29
19
|
Layout/MultilineMethodCallIndentation:
|
|
@@ -42,6 +32,8 @@ RSpec/NestedGroups:
|
|
|
42
32
|
Max: 10
|
|
43
33
|
RSpec/MessageExpectation:
|
|
44
34
|
Enabled: false
|
|
35
|
+
RSpec/MissingExampleGroupArgument:
|
|
36
|
+
Enabled: false
|
|
45
37
|
require:
|
|
46
38
|
- rubocop-rspec
|
|
47
39
|
- test_prof/rubocop
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
clean-architecture (0.
|
|
4
|
+
clean-architecture (0.2.0)
|
|
5
5
|
dry-matcher (~> 0.0)
|
|
6
6
|
dry-monads (~> 1.0)
|
|
7
|
+
dry-struct (~> 0.0)
|
|
8
|
+
dry-types (~> 0.0)
|
|
7
9
|
duckface-interfaces (~> 0.0)
|
|
8
10
|
|
|
9
11
|
GEM
|
|
@@ -25,14 +27,36 @@ GEM
|
|
|
25
27
|
thread_safe (~> 0.3, >= 0.3.1)
|
|
26
28
|
diff-lcs (1.3)
|
|
27
29
|
docile (1.3.1)
|
|
30
|
+
dry-configurable (0.7.0)
|
|
31
|
+
concurrent-ruby (~> 1.0)
|
|
32
|
+
dry-container (0.6.0)
|
|
33
|
+
concurrent-ruby (~> 1.0)
|
|
34
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
|
28
35
|
dry-core (0.4.7)
|
|
29
36
|
concurrent-ruby (~> 1.0)
|
|
30
37
|
dry-equalizer (0.2.1)
|
|
38
|
+
dry-inflector (0.1.2)
|
|
39
|
+
dry-logic (0.4.2)
|
|
40
|
+
dry-container (~> 0.2, >= 0.2.6)
|
|
41
|
+
dry-core (~> 0.2)
|
|
42
|
+
dry-equalizer (~> 0.2)
|
|
31
43
|
dry-matcher (0.7.0)
|
|
32
|
-
dry-monads (1.0
|
|
44
|
+
dry-monads (1.1.0)
|
|
33
45
|
concurrent-ruby (~> 1.0)
|
|
34
46
|
dry-core (~> 0.4, >= 0.4.4)
|
|
35
47
|
dry-equalizer
|
|
48
|
+
dry-struct (0.5.1)
|
|
49
|
+
dry-core (~> 0.4, >= 0.4.3)
|
|
50
|
+
dry-equalizer (~> 0.2)
|
|
51
|
+
dry-types (~> 0.13)
|
|
52
|
+
ice_nine (~> 0.11)
|
|
53
|
+
dry-types (0.13.2)
|
|
54
|
+
concurrent-ruby (~> 1.0)
|
|
55
|
+
dry-container (~> 0.3)
|
|
56
|
+
dry-core (~> 0.4, >= 0.4.4)
|
|
57
|
+
dry-equalizer (~> 0.2)
|
|
58
|
+
dry-inflector (~> 0.1, >= 0.1.2)
|
|
59
|
+
dry-logic (~> 0.4, >= 0.4.2)
|
|
36
60
|
duckface-interfaces (0.0.2)
|
|
37
61
|
em-websocket (0.5.1)
|
|
38
62
|
eventmachine (>= 0.12.9)
|
|
@@ -92,7 +116,7 @@ GEM
|
|
|
92
116
|
rb-inotify (0.9.10)
|
|
93
117
|
ffi (>= 0.5.0, < 2)
|
|
94
118
|
rb-readline (0.5.5)
|
|
95
|
-
reek (5.0
|
|
119
|
+
reek (5.2.0)
|
|
96
120
|
codeclimate-engine-rb (~> 0.4.0)
|
|
97
121
|
kwalify (~> 0.7.0)
|
|
98
122
|
parser (>= 2.5.0.0, < 2.6, != 2.5.1.1)
|
|
@@ -103,14 +127,14 @@ GEM
|
|
|
103
127
|
rspec-mocks (~> 3.8.0)
|
|
104
128
|
rspec-core (3.8.0)
|
|
105
129
|
rspec-support (~> 3.8.0)
|
|
106
|
-
rspec-expectations (3.8.
|
|
130
|
+
rspec-expectations (3.8.2)
|
|
107
131
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
108
132
|
rspec-support (~> 3.8.0)
|
|
109
133
|
rspec-mocks (3.8.0)
|
|
110
134
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
111
135
|
rspec-support (~> 3.8.0)
|
|
112
136
|
rspec-support (3.8.0)
|
|
113
|
-
rubocop (0.
|
|
137
|
+
rubocop (0.59.2)
|
|
114
138
|
jaro_winkler (~> 1.5.1)
|
|
115
139
|
parallel (~> 1.10)
|
|
116
140
|
parser (>= 2.5, != 2.5.1.1)
|
|
@@ -118,7 +142,7 @@ GEM
|
|
|
118
142
|
rainbow (>= 2.2.2, < 4.0)
|
|
119
143
|
ruby-progressbar (~> 1.7)
|
|
120
144
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
121
|
-
rubocop-rspec (1.
|
|
145
|
+
rubocop-rspec (1.30.0)
|
|
122
146
|
rubocop (>= 0.58.0)
|
|
123
147
|
ruby-progressbar (1.10.0)
|
|
124
148
|
ruby_dep (1.5.0)
|
|
@@ -143,12 +167,12 @@ PLATFORMS
|
|
|
143
167
|
ruby
|
|
144
168
|
|
|
145
169
|
DEPENDENCIES
|
|
146
|
-
bundler (
|
|
170
|
+
bundler (~> 1.13)
|
|
147
171
|
clean-architecture!
|
|
148
172
|
guard-livereload
|
|
149
173
|
guard-rspec
|
|
150
174
|
pry-byebug
|
|
151
|
-
rake (
|
|
175
|
+
rake (~> 12.0)
|
|
152
176
|
rb-fsevent
|
|
153
177
|
rb-readline
|
|
154
178
|
reek
|
|
@@ -160,4 +184,4 @@ DEPENDENCIES
|
|
|
160
184
|
timecop
|
|
161
185
|
|
|
162
186
|
BUNDLED WITH
|
|
163
|
-
1.16.
|
|
187
|
+
1.16.4
|
data/README.md
CHANGED
|
@@ -23,10 +23,11 @@ and decisions about I/O can be deferred until the last possible moment. It relie
|
|
|
23
23
|
[duckface-interfaces](https://github.com/samuelgiles/duckface) gem to enforce interface
|
|
24
24
|
implementation.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
## Clean architecture principles
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
### Screaming architecture - use cases as an organisational principle
|
|
29
|
+
|
|
30
|
+
Uncle Bob suggests that your source code organisation should allow developers to easily find a listing of all use cases your application provides. Here's an example of how this might look in a
|
|
30
31
|
Rails application.
|
|
31
32
|
|
|
32
33
|
```
|
|
@@ -38,7 +39,96 @@ Rails application.
|
|
|
38
39
|
- ...
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
Note that the use case name contains:
|
|
43
|
+
|
|
44
|
+
- the user role
|
|
45
|
+
- the action
|
|
46
|
+
- the (sometimes implied) subject
|
|
47
|
+
|
|
48
|
+
### Design principles
|
|
49
|
+
|
|
50
|
+
#### SRP - The Single Responsibility principle
|
|
51
|
+
|
|
52
|
+
> A function should do one, and only one, thing
|
|
53
|
+
|
|
54
|
+
We satisfy the SRP by following these rules:
|
|
55
|
+
|
|
56
|
+
- An **adapter** is solely responsible for presenting the properties of a business object, or a small number of business objects, in a known interface
|
|
57
|
+
- A **command** is solely responsible for completing an atomic I/O operation
|
|
58
|
+
- An **entity** is solely responsible for representing, in memory, a business object whos properties do not come from a single source
|
|
59
|
+
- An **interface** is a module that represents a contract between two classes
|
|
60
|
+
- A **serializer** is solely responsible for taking a business object and turning it into a representation made up of purely primitive values
|
|
61
|
+
- A **strategy** is an algorithm used by commands to compose atomic I/O operations
|
|
62
|
+
- A **use case** is solely responsible for checking whether an actor has permissions to perform a command, and executing that command if so
|
|
63
|
+
- A **validator** is solely responsible for validating a business object and returning a validation result
|
|
64
|
+
|
|
65
|
+
#### OCP - The Open/Closed Principle, LSP - The Liskov Substitution Principle and DIP - The Dependency Inversion Principle
|
|
66
|
+
|
|
67
|
+
> A software artefact should be open for extension but closed for modification
|
|
68
|
+
|
|
69
|
+
> A caller should not have to know the type of an object to interact with it
|
|
70
|
+
|
|
71
|
+
> Always depend on or derive from a stable abstraction, rather than a volatile concrete class
|
|
72
|
+
|
|
73
|
+
We satisfy the OCP, LSP & DIP by following these rules:
|
|
74
|
+
|
|
75
|
+
- We create a clean boundary between our business logic, our persistence layer and our application-specific classes using interfaces
|
|
76
|
+
- We use interfaces wherever possible, allowing concrete implementations of those interfaces to be extended without breaking the contract
|
|
77
|
+
- We write unit tests against interfaces, never against concrete implementations (unless interfaces don't exist)
|
|
78
|
+
|
|
79
|
+
#### ISP - The Interface Segregation Principle
|
|
80
|
+
|
|
81
|
+
> Where some actors only use a subset of methods available from an interface, the interface should be split into sub-interfaces supporting each type of caller
|
|
82
|
+
|
|
83
|
+
We satisfy the ISP by following these rules:
|
|
84
|
+
|
|
85
|
+
- Each functional area of our code is split into folders (under `lib` in Rails projects)
|
|
86
|
+
- Each functional area defines its own interfaces
|
|
87
|
+
- Interfaces are not shared between functional areas
|
|
88
|
+
|
|
89
|
+
### Component cohesion
|
|
90
|
+
|
|
91
|
+
#### REP - The Reuse/Release Equivalence Principle, CCP - The Common Closure Principle & CRP - The Common Reuse Principle
|
|
92
|
+
|
|
93
|
+
> Classes and modules that are grouped together into a component should be releasable together
|
|
94
|
+
|
|
95
|
+
> Gather into components those changes the change for the same reasons and at the same times.
|
|
96
|
+
|
|
97
|
+
> Classes and modules that tend to be reused together should be placed in the same component
|
|
98
|
+
|
|
99
|
+
We satisfy the REP, CCP and CRP by:
|
|
100
|
+
|
|
101
|
+
- Having team discussions whenever we make decisions about what a new functional area should be called and what it should contain
|
|
102
|
+
- Ensuring that none of our functional areas make direct reference back to the parent application
|
|
103
|
+
- Splitting functional areas out into gems when those functional areas change at a different rate than the rest of the codebase
|
|
104
|
+
- Splitting functional areas out into standalone applications when it makes sense to do so
|
|
105
|
+
|
|
106
|
+
### Component coupling
|
|
107
|
+
|
|
108
|
+
#### ADP - The Acyclic Dependencies Principle
|
|
109
|
+
|
|
110
|
+
> Don't create circular dependencies
|
|
111
|
+
|
|
112
|
+
I don't think I need to explain this. Just don't do it. I like explicitly including dependencies using `require` because it actually prevents you from doing this. Rails, in so many ways, makes one lazy.
|
|
113
|
+
|
|
114
|
+
#### SDP - The Stable Dependencies Principle
|
|
115
|
+
|
|
116
|
+
> A component always have less things depending on it than it depends on
|
|
117
|
+
|
|
118
|
+
We satisfy the SDP by:
|
|
119
|
+
|
|
120
|
+
- Putting sensible abstractions in place that adhere to the Single Responsibility principle
|
|
121
|
+
- Not sharing abstractions and entities between multiple functional areas
|
|
122
|
+
|
|
123
|
+
#### SAP - The Stable Abstractions Principle
|
|
124
|
+
|
|
125
|
+
> A component should be as abstract as it is stable
|
|
126
|
+
|
|
127
|
+
We satisfy the SAP by:
|
|
128
|
+
|
|
129
|
+
- Thinking hard about the methods and parameters we specify in our interfaces. Are they solving for a general problem? Are we likely to have to change them when requirements change, and how we can avoid that?
|
|
130
|
+
|
|
131
|
+
## Practical suggestions for implementation
|
|
42
132
|
|
|
43
133
|
* The code that manages your inputs (e.g. a Rails controller) instantiates a persistence layer
|
|
44
134
|
object
|
data/clean-architecture.gemspec
CHANGED
|
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
|
|
22
22
|
spec.add_dependency 'dry-matcher', '~> 0.0'
|
|
23
23
|
spec.add_dependency 'dry-monads', '~> 1.0'
|
|
24
|
+
spec.add_dependency 'dry-struct', '~> 0.0'
|
|
25
|
+
spec.add_dependency 'dry-types', '~> 0.0'
|
|
24
26
|
spec.add_dependency 'duckface-interfaces', '~> 0.0'
|
|
25
27
|
|
|
26
28
|
spec.add_development_dependency 'bundler', '~> 1.13'
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
require 'clean_architecture/adapters/all'
|
|
6
6
|
require 'clean_architecture/entities/all'
|
|
7
7
|
require 'clean_architecture/interfaces/all'
|
|
8
|
+
require 'clean_architecture/matchers/all'
|
|
8
9
|
require 'clean_architecture/queries/all'
|
|
9
10
|
require 'clean_architecture/serializers/all'
|
|
10
11
|
require 'clean_architecture/strategies/all'
|
|
11
12
|
|
|
13
|
+
require 'clean_architecture/types'
|
|
12
14
|
require 'clean_architecture/version'
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
|
|
4
4
|
|
|
5
|
+
require 'clean_architecture/entities/failure_details'
|
|
5
6
|
require 'clean_architecture/entities/targeted_parameters'
|
|
6
7
|
require 'clean_architecture/entities/untargeted_parameters'
|
|
7
8
|
require 'clean_architecture/entities/use_case_history_entry'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'clean_architecture/types'
|
|
4
|
+
require 'dry/struct'
|
|
5
|
+
|
|
6
|
+
module CleanArchitecture
|
|
7
|
+
module Entities
|
|
8
|
+
class FailureDetails < Dry::Struct
|
|
9
|
+
FailureTypes = Types::Strict::String.enum(
|
|
10
|
+
'error',
|
|
11
|
+
'expectation_failed',
|
|
12
|
+
'not_found',
|
|
13
|
+
'unauthorized'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
attribute :type, FailureTypes
|
|
17
|
+
attribute :message, Types::Strict::String
|
|
18
|
+
attribute :other_properties, Types::Strict::Hash
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,6 +4,7 @@ require 'dry-monads'
|
|
|
4
4
|
require 'dry/matcher/result_matcher'
|
|
5
5
|
require 'duckface'
|
|
6
6
|
require 'clean_architecture/interfaces/use_case_history_entry'
|
|
7
|
+
require 'clean_architecture/matchers/use_case_result'
|
|
7
8
|
|
|
8
9
|
module CleanArchitecture
|
|
9
10
|
module Entities
|
|
@@ -23,9 +24,9 @@ module CleanArchitecture
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def failure_messages
|
|
26
|
-
|
|
27
|
+
Matchers::UseCaseResult.call(@use_case_result) do |matcher|
|
|
27
28
|
matcher.success { nil }
|
|
28
|
-
matcher.failure
|
|
29
|
+
matcher.failure(&:message)
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-matcher'
|
|
4
|
+
|
|
5
|
+
module CleanArchitecture
|
|
6
|
+
module Matchers
|
|
7
|
+
class UseCaseResult
|
|
8
|
+
def self.call(result, &block)
|
|
9
|
+
new.matcher.call(result, &block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def matcher
|
|
13
|
+
Dry::Matcher.new(success: success_case, failure: failure_case)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def success_case
|
|
19
|
+
Dry::Matcher::Case.new(
|
|
20
|
+
match: ->(value) { value.is_a?(Dry::Monads::Success) },
|
|
21
|
+
resolve: ->(value) { value.value! }
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def failure_case
|
|
26
|
+
Dry::Matcher::Case.new(
|
|
27
|
+
match: ->(value) { value.is_a?(Dry::Monads::Failure) },
|
|
28
|
+
resolve: ->(value) { resolve_failure_value(value) }
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def resolve_failure_value(value)
|
|
33
|
+
failure = value.failure
|
|
34
|
+
case failure
|
|
35
|
+
when String
|
|
36
|
+
Entities::FailureDetails.new(message: failure, other_properties: {}, type: 'error')
|
|
37
|
+
when Entities::FailureDetails
|
|
38
|
+
failure
|
|
39
|
+
else
|
|
40
|
+
raise 'Unexpected failure value - must be String or Entities::FailureDetails'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CleanArchitecture
|
|
4
|
+
module Queries
|
|
5
|
+
class HttpFailureCode
|
|
6
|
+
def initialize(failure_details_type)
|
|
7
|
+
@failure_details_type = failure_details_type
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_sym
|
|
11
|
+
code = FAILURE_DETAILS_TYPE_TO_STATUS_CODE[@failure_details_type.to_s.downcase]
|
|
12
|
+
if code.nil?
|
|
13
|
+
raise NotImplementedError,
|
|
14
|
+
"cannot determine failure code for failure details type #{@failure_details_type}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
code
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
FAILURE_DETAILS_TYPE_TO_STATUS_CODE = {
|
|
23
|
+
'error' => :internal_server_error,
|
|
24
|
+
'expectation_failed' => :expectation_failed,
|
|
25
|
+
'not_found' => :not_found,
|
|
26
|
+
'unauthorized' => :unauthorized
|
|
27
|
+
}.freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -8,10 +8,11 @@ module CleanArchitecture
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def to_sym
|
|
11
|
-
code = HTTP_METHOD_TO_SUCCESS_CODE[@http_method]
|
|
11
|
+
code = HTTP_METHOD_TO_SUCCESS_CODE[@http_method.to_s.upcase]
|
|
12
12
|
if code.nil?
|
|
13
13
|
raise NotImplementedError, "cannot determine success code for HTTP method #{@http_method}"
|
|
14
14
|
end
|
|
15
|
+
|
|
15
16
|
code
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'clean_architecture/entities/failure_details'
|
|
4
|
+
require 'clean_architecture/matchers/use_case_result'
|
|
4
5
|
require 'clean_architecture/queries/http_success_code'
|
|
6
|
+
require 'clean_architecture/queries/http_failure_code'
|
|
5
7
|
|
|
6
8
|
module CleanArchitecture
|
|
7
9
|
module Serializers
|
|
@@ -12,13 +14,22 @@ module CleanArchitecture
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def to_h
|
|
15
|
-
|
|
16
|
-
matcher.success
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
matcher.failure { |error_message| { status: :error, error: error_message } }
|
|
17
|
+
Matchers::UseCaseResult.call(@result) do |matcher|
|
|
18
|
+
matcher.success { |data| success_html_response(data) }
|
|
19
|
+
matcher.failure { |failure_details| failure_html_response(failure_details) }
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def success_html_response(data)
|
|
26
|
+
{ status: Queries::HttpSuccessCode.new(@http_method).to_sym, data: data }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def failure_html_response(failure_details)
|
|
30
|
+
status = Queries::HttpFailureCode.new(failure_details.type).to_sym
|
|
31
|
+
{ status: status, error: failure_details.message }
|
|
32
|
+
end
|
|
22
33
|
end
|
|
23
34
|
end
|
|
24
35
|
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
3
|
+
require 'clean_architecture/entities/failure_details'
|
|
4
|
+
require 'clean_architecture/matchers/use_case_result'
|
|
5
|
+
require 'clean_architecture/queries/http_failure_code'
|
|
5
6
|
require 'clean_architecture/queries/http_success_code'
|
|
6
7
|
|
|
7
8
|
module CleanArchitecture
|
|
@@ -23,22 +24,20 @@ module CleanArchitecture
|
|
|
23
24
|
private
|
|
24
25
|
|
|
25
26
|
def http_status_code
|
|
26
|
-
|
|
27
|
+
Matchers::UseCaseResult.call(@result) do |matcher|
|
|
27
28
|
matcher.success { Queries::HttpSuccessCode.new(@http_method).to_sym }
|
|
28
|
-
matcher.failure do
|
|
29
|
-
|
|
30
|
-
:unauthorized
|
|
31
|
-
else
|
|
32
|
-
:expectation_failed
|
|
33
|
-
end
|
|
29
|
+
matcher.failure do |failure_value|
|
|
30
|
+
Queries::HttpFailureCode.new(failure_value.type).to_sym
|
|
34
31
|
end
|
|
35
32
|
end
|
|
36
33
|
end
|
|
37
34
|
|
|
38
35
|
def json
|
|
39
|
-
|
|
36
|
+
Matchers::UseCaseResult.call(@result) do |matcher|
|
|
40
37
|
matcher.success { success_payload }
|
|
41
|
-
matcher.failure
|
|
38
|
+
matcher.failure do |failure_details|
|
|
39
|
+
{ jsonapi: json_api_version_hash, errors: [failure_details.message] }
|
|
40
|
+
end
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
|
|
@@ -49,20 +48,9 @@ module CleanArchitecture
|
|
|
49
48
|
}
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def error_payload(failure_message)
|
|
53
|
-
{
|
|
54
|
-
jsonapi: json_api_version_hash,
|
|
55
|
-
errors: [failure_message]
|
|
56
|
-
}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
51
|
def json_api_version_hash
|
|
60
52
|
{ version: @success_payload.version }
|
|
61
53
|
end
|
|
62
|
-
|
|
63
|
-
def result_is_authorization_failure?
|
|
64
|
-
!@result.failure.index('Unauthorized: ').nil?
|
|
65
|
-
end
|
|
66
54
|
end
|
|
67
55
|
end
|
|
68
56
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'dry/monads/all'
|
|
4
|
+
require 'clean_architecture/entities/failure_details'
|
|
4
5
|
require 'clean_architecture/interfaces/strategy'
|
|
5
6
|
|
|
6
7
|
module CleanArchitecture
|
|
@@ -17,12 +18,18 @@ module CleanArchitecture
|
|
|
17
18
|
|
|
18
19
|
def_delegator :@sub_strategy, :parameters
|
|
19
20
|
|
|
21
|
+
UNAUTHORIZED_FAILURE_DETAILS = Entities::FailureDetails.new(
|
|
22
|
+
message: 'Unauthorized',
|
|
23
|
+
other_properties: {},
|
|
24
|
+
type: 'unauthorized'
|
|
25
|
+
)
|
|
26
|
+
|
|
20
27
|
def result
|
|
21
28
|
@result ||= begin
|
|
22
29
|
if @authorization_check.authorized?
|
|
23
30
|
@sub_strategy.result
|
|
24
31
|
else
|
|
25
|
-
Dry::Monads::Failure(
|
|
32
|
+
Dry::Monads::Failure(UNAUTHORIZED_FAILURE_DETAILS)
|
|
26
33
|
end
|
|
27
34
|
end
|
|
28
35
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clean-architecture
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bellroy Tech Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dry-matcher
|
|
@@ -38,6 +38,34 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: dry-struct
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: dry-types
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.0'
|
|
41
69
|
- !ruby/object:Gem::Dependency
|
|
42
70
|
name: duckface-interfaces
|
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -104,6 +132,7 @@ files:
|
|
|
104
132
|
- ".gitignore"
|
|
105
133
|
- ".overcommit.yml"
|
|
106
134
|
- ".reek"
|
|
135
|
+
- ".reek.yml"
|
|
107
136
|
- ".rspec"
|
|
108
137
|
- ".rubocop.yml"
|
|
109
138
|
- ".ruby-version"
|
|
@@ -120,6 +149,7 @@ files:
|
|
|
120
149
|
- lib/clean_architecture/adapters/attribute_hash_base.rb
|
|
121
150
|
- lib/clean_architecture/all.rb
|
|
122
151
|
- lib/clean_architecture/entities/all.rb
|
|
152
|
+
- lib/clean_architecture/entities/failure_details.rb
|
|
123
153
|
- lib/clean_architecture/entities/targeted_parameters.rb
|
|
124
154
|
- lib/clean_architecture/entities/untargeted_parameters.rb
|
|
125
155
|
- lib/clean_architecture/entities/use_case_history_entry.rb
|
|
@@ -137,7 +167,10 @@ files:
|
|
|
137
167
|
- lib/clean_architecture/interfaces/use_case_actor.rb
|
|
138
168
|
- lib/clean_architecture/interfaces/use_case_history_entry.rb
|
|
139
169
|
- lib/clean_architecture/interfaces/use_case_target.rb
|
|
170
|
+
- lib/clean_architecture/matchers/all.rb
|
|
171
|
+
- lib/clean_architecture/matchers/use_case_result.rb
|
|
140
172
|
- lib/clean_architecture/queries/all.rb
|
|
173
|
+
- lib/clean_architecture/queries/http_failure_code.rb
|
|
141
174
|
- lib/clean_architecture/queries/http_success_code.rb
|
|
142
175
|
- lib/clean_architecture/serializers/all.rb
|
|
143
176
|
- lib/clean_architecture/serializers/html_response_from_result.rb
|
|
@@ -146,6 +179,7 @@ files:
|
|
|
146
179
|
- lib/clean_architecture/strategies/actor_gets_authorized_then_does_work.rb
|
|
147
180
|
- lib/clean_architecture/strategies/all.rb
|
|
148
181
|
- lib/clean_architecture/strategies/with_audit_trail.rb
|
|
182
|
+
- lib/clean_architecture/types.rb
|
|
149
183
|
- lib/clean_architecture/version.rb
|
|
150
184
|
homepage: https://bellroy.com
|
|
151
185
|
licenses: []
|