pragma-operation 1.6.3 → 2.0.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.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Error
6
+ attr_reader :error_type, :error_message, :meta
7
+
8
+ def initialize(error_type:, error_message:, meta: {})
9
+ @error_type = error_type
10
+ @error_message = error_message
11
+ @meta = meta
12
+ end
13
+
14
+ def as_json
15
+ {
16
+ error_type: error_type,
17
+ error_message: error_message,
18
+ meta: meta
19
+ }
20
+ end
21
+
22
+ def to_json
23
+ JSON.dump as_json
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ STATUSES = {
7
+ 100 => :continue,
8
+ 101 => :switching_protocols,
9
+ 102 => :processing,
10
+ 200 => :ok,
11
+ 201 => :created,
12
+ 202 => :accepted,
13
+ 203 => :non_authoritative_information,
14
+ 204 => :no_content,
15
+ 205 => :reset_content,
16
+ 206 => :partial_content,
17
+ 207 => :multi_status,
18
+ 208 => :already_reported,
19
+ 300 => :multiple_choices,
20
+ 301 => :moved_permanently,
21
+ 302 => :found,
22
+ 303 => :see_other,
23
+ 304 => :not_modified,
24
+ 305 => :use_proxy,
25
+ 307 => :temporary_redirect,
26
+ 400 => :bad_request,
27
+ 401 => :unauthorized,
28
+ 402 => :payment_required,
29
+ 403 => :forbidden,
30
+ 404 => :not_found,
31
+ 405 => :method_not_allowed,
32
+ 406 => :not_acceptable,
33
+ 407 => :proxy_authentication_required,
34
+ 408 => :request_timeout,
35
+ 409 => :conflict,
36
+ 410 => :gone,
37
+ 411 => :length_required,
38
+ 412 => :precondition_failed,
39
+ 413 => :request_entity_too_large,
40
+ 414 => :request_uri_too_large,
41
+ 415 => :unsupported_media_type,
42
+ 416 => :request_range_not_satisfiable,
43
+ 417 => :expectation_failed,
44
+ 418 => :im_a_teapot,
45
+ 422 => :unprocessable_entity,
46
+ 423 => :locked,
47
+ 424 => :failed_dependency,
48
+ 425 => :unordered_collection,
49
+ 426 => :upgrade_required,
50
+ 428 => :precondition_required,
51
+ 429 => :too_many_requests,
52
+ 431 => :request_header_fields_too_large,
53
+ 449 => :retry_with,
54
+ 500 => :internal_server_error,
55
+ 501 => :not_implemented,
56
+ 502 => :bad_gateway,
57
+ 503 => :service_unavailable,
58
+ 504 => :gateway_timeout,
59
+ 505 => :http_version_not_supported,
60
+ 506 => :variant_also_negotiates,
61
+ 507 => :insufficient_storage,
62
+ 509 => :bandwidth_limit_exceeded,
63
+ 510 => :not_extended,
64
+ 511 => :network_authentication_required
65
+ }.freeze
66
+
67
+ attr_accessor :entity, :headers
68
+ attr_reader :status
69
+
70
+ def initialize(status: 200, entity: nil, headers: {})
71
+ self.status = status
72
+ self.entity = entity
73
+ self.headers = headers
74
+ end
75
+
76
+ def success?
77
+ %w[1 2 3].include?(@status.to_s[0])
78
+ end
79
+
80
+ def failure?
81
+ !success?
82
+ end
83
+
84
+ def status=(v)
85
+ case v
86
+ when Integer
87
+ fail ArgumentError, "#{v} is not a valid status code" unless STATUSES[v]
88
+ @status = v
89
+ when Symbol, String
90
+ fail ArgumentError, "#{v} is not a valid status phrase" unless STATUSES.invert[v.to_sym]
91
+ @status = STATUSES.invert[v.to_sym]
92
+ end
93
+ end
94
+
95
+ def decorate_with(decorator)
96
+ tap do
97
+ self.entity = decorator.represent(entity)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class BadRequest < Response
7
+ def initialize(
8
+ entity: Error.new(error_type: :bad_request, error_message: 'This request is malformed.'),
9
+ headers: {}
10
+ )
11
+ super(status: 400, entity: entity, headers: headers)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class Created < Response
7
+ def initialize(entity: nil, headers: {})
8
+ super(status: 201, entity: entity, headers: headers)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class Forbidden < Response
7
+ def initialize(
8
+ entity: Error.new(
9
+ error_type: :forbidden,
10
+ error_message: 'You are not authorized to access the requested resource.'
11
+ ),
12
+ headers: {}
13
+ )
14
+ super(status: 403, entity: entity, headers: headers)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class NoContent < Response
7
+ def initialize(headers: {})
8
+ super(status: 204, entity: nil, headers: headers)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class NotFound < Response
7
+ def initialize(
8
+ entity: Error.new(
9
+ error_type: :not_found,
10
+ error_message: 'The requested resource could not be found.'
11
+ ),
12
+ headers: {}
13
+ )
14
+ super(status: 404, entity: entity, headers: headers)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class Ok < Response
7
+ def initialize(entity: nil, headers: {})
8
+ super(status: 200, entity: entity, headers: headers)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Operation
5
+ class Response
6
+ class UnprocessableEntity < Response
7
+ def initialize(entity: nil, headers: {}, errors: nil)
8
+ fail ArgumentError, 'You cannot provide both :entity and :errors!' if entity && errors
9
+
10
+ entity ||= Error.new(
11
+ error_type: :unprocessable_entity,
12
+ error_message: 'The provided resource is in an unexpected format.',
13
+ meta: {
14
+ errors: errors || {}
15
+ }
16
+ )
17
+
18
+ super(status: 422, entity: entity, headers: headers)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Pragma
3
4
  module Operation
4
- VERSION = '1.6.3'
5
+ VERSION = '2.0.0'
5
6
  end
6
7
  end
@@ -1,35 +1,32 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'pragma/operation/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "pragma-operation"
8
+ spec.name = 'pragma-operation'
8
9
  spec.version = Pragma::Operation::VERSION
9
- spec.authors = ["Alessandro Desantis"]
10
- spec.email = ["desa.alessandro@gmail.com"]
10
+ spec.authors = ['Alessandro Desantis']
11
+ spec.email = ['desa.alessandro@gmail.com']
11
12
 
12
13
  spec.summary = 'Business logic encapsulation for your JSON API.'
13
- spec.homepage = "https://github.com/pragmarb/pragma-operation"
14
- spec.license = "MIT"
14
+ spec.homepage = 'https://github.com/pragmarb/pragma-operation'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
18
  f.match(%r{^(test|spec|features)/})
18
19
  end
19
- spec.bindir = "exe"
20
+ spec.bindir = 'exe'
20
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
22
-
23
- spec.add_dependency 'interactor', '~> 3.1.0'
22
+ spec.require_paths = ['lib']
24
23
 
25
- spec.add_development_dependency 'pragma-policy', '~> 0.1.0'
26
- spec.add_development_dependency 'pragma-contract', '~> 0.1.0'
27
- spec.add_development_dependency 'pragma-decorator', '~> 0.1.0'
24
+ spec.add_dependency 'trailblazer-operation', '~> 0.0'
28
25
 
29
- spec.add_development_dependency "bundler"
30
- spec.add_development_dependency "rake"
31
- spec.add_development_dependency "rspec"
32
- spec.add_development_dependency "rubocop"
33
- spec.add_development_dependency "rubocop-rspec"
34
- spec.add_development_dependency "coveralls"
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency 'rubocop'
30
+ spec.add_development_dependency 'rubocop-rspec'
31
+ spec.add_development_dependency 'coveralls'
35
32
  end
metadata CHANGED
@@ -1,71 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pragma-operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessandro Desantis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-27 00:00:00.000000000 Z
11
+ date: 2017-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: interactor
14
+ name: trailblazer-operation
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.1.0
19
+ version: '0.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 3.1.0
27
- - !ruby/object:Gem::Dependency
28
- name: pragma-policy
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.1.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.1.0
41
- - !ruby/object:Gem::Dependency
42
- name: pragma-contract
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.1.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.1.0
55
- - !ruby/object:Gem::Dependency
56
- name: pragma-decorator
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.1.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.1.0
26
+ version: '0.0'
69
27
  - !ruby/object:Gem::Dependency
70
28
  name: bundler
71
29
  requirement: !ruby/object:Gem::Requirement
@@ -167,15 +125,17 @@ files:
167
125
  - Rakefile
168
126
  - bin/console
169
127
  - bin/setup
170
- - doc/01-basic-usage.md
171
- - doc/02-contracts.md
172
- - doc/03-policies.md
173
- - doc/04-decorators.md
174
128
  - lib/pragma/operation.rb
175
- - lib/pragma/operation/authorization.rb
176
129
  - lib/pragma/operation/base.rb
177
- - lib/pragma/operation/decoration.rb
178
- - lib/pragma/operation/validation.rb
130
+ - lib/pragma/operation/error.rb
131
+ - lib/pragma/operation/response.rb
132
+ - lib/pragma/operation/response/bad_request.rb
133
+ - lib/pragma/operation/response/created.rb
134
+ - lib/pragma/operation/response/forbidden.rb
135
+ - lib/pragma/operation/response/no_content.rb
136
+ - lib/pragma/operation/response/not_found.rb
137
+ - lib/pragma/operation/response/ok.rb
138
+ - lib/pragma/operation/response/unprocessable_entity.rb
179
139
  - lib/pragma/operation/version.rb
180
140
  - pragma-operation.gemspec
181
141
  homepage: https://github.com/pragmarb/pragma-operation
@@ -198,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
158
  version: '0'
199
159
  requirements: []
200
160
  rubyforge_project:
201
- rubygems_version: 2.6.8
161
+ rubygems_version: 2.6.13
202
162
  signing_key:
203
163
  specification_version: 4
204
164
  summary: Business logic encapsulation for your JSON API.
@@ -1,264 +0,0 @@
1
- # Basic usage
2
-
3
- Here's the simplest operation you can write:
4
-
5
- ```ruby
6
- module API
7
- module V1
8
- module Ping
9
- module Operation
10
- class Create < Pragma::Operation::Base
11
- def call
12
- # The `status` parameter is optional (the default is `:ok`).
13
- respond_with(
14
- status: :ok,
15
- resource: { pong: params[:pong] }
16
- )
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
23
- ```
24
-
25
- Here's how you use it:
26
-
27
- ```ruby
28
- result = API::V1::Ping::Operation::Create.call(params: { pong: 'HELLO' })
29
-
30
- result.status # => :ok
31
- result.resource # => { pong: 'HELLO' }
32
- ```
33
-
34
- As you can see, an operation takes parameters as input and responds with:
35
-
36
- - an HTTP status code;
37
- - (optional) a resource (i.e. an object implementing `#to_json`).
38
-
39
- If you don't want to return a resource, you can use the `#head` shortcut:
40
-
41
- ```ruby
42
- module API
43
- module V1
44
- module Ping
45
- module Operation
46
- class Create < Pragma::Operation::Base
47
- def call
48
- head :no_content
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- ```
56
-
57
- Since Pragma::Operation is built on top of [Interactor](https://github.com/collectiveidea/interactor),
58
- you should consult its documentation for the basic usage of operations; the rest of this section
59
- only covers the features provided specifically by Pragma::Operation.
60
-
61
- ## Headers
62
-
63
- You can attach headers to your response by manipulating the `headers` hash:
64
-
65
- ```ruby
66
- module API
67
- module V1
68
- module Post
69
- module Operation
70
- class Create < Pragma::Operation::Base
71
- def call
72
- post = ::Post.new(params)
73
- post.save!
74
-
75
- headers['X-Post-Id'] = post.id
76
-
77
- respond_with status: :created, resource: post
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
- ```
85
-
86
- You can also set headers when calling `#respond_with`:
87
-
88
- ```ruby
89
- module API
90
- module V1
91
- module Post
92
- module Operation
93
- class Create < Pragma::Operation::Base
94
- def call
95
- post = ::Post.new(params)
96
- post.save!
97
-
98
- respond_with status: :created, resource: post, headers: {
99
- 'X-Post-Id' => post.id
100
- }
101
- end
102
- end
103
- end
104
- end
105
- end
106
- end
107
- ```
108
-
109
- ## HATEOAS
110
-
111
- Pragma::Operation supports HATEOAS by allowing you to specify a list of links to use for building
112
- the `Link` header. You can set the links by manipulating the `links` hash.
113
-
114
- For instance, here's how you could link to a post's comments and author:
115
-
116
- ```ruby
117
- module API
118
- module V1
119
- module Post
120
- module Operation
121
- class Show < Pragma::Operation::Base
122
- def call
123
- post = ::Post.find(params[:id])
124
-
125
- links['comments'] = "/posts/#{post.id}/comments"
126
- links['author'] = "/users/#{post.author.id}"
127
-
128
- respond_with resource: post
129
- end
130
- end
131
- end
132
- end
133
- end
134
- end
135
- ```
136
-
137
- You can also set the links when calling `#respond_with`:
138
-
139
- ```ruby
140
- module API
141
- module V1
142
- module Post
143
- module Operation
144
- class Show < Pragma::Operation::Base
145
- def call
146
- post = ::Post.find(params[:id])
147
-
148
- respond_with resource: post, links: {
149
- comments: "/posts/#{post.id}/comments",
150
- author: "/users/#{post.author.id}"
151
- }
152
- end
153
- end
154
- end
155
- end
156
- end
157
- end
158
- ```
159
-
160
- This will build the `Link` header accordingly:
161
-
162
- ```ruby
163
- result = API::V1::Post::Operation::Show.call(params: { id: 1 })
164
-
165
- result.status # => :ok
166
- result.headers
167
- # => {
168
- # 'Link' => '</posts/1/comments>; rel="comments",
169
- # </users/49>; rel="author"'
170
- # }
171
- ```
172
-
173
- **Note: Do not set the `Link` header manually, as it will be replaced when building links from the
174
- `links` hash.**
175
-
176
- ## Handling errors
177
-
178
- You can use the `#success?` and `#failure?` method to check whether an operation was successful. An
179
- operation is considered successful when it returns a 2xx or 3xx status code:
180
-
181
- ```ruby
182
- module API
183
- module V1
184
- module Ping
185
- module Operation
186
- class Create < Pragma::Operation::Base
187
- def call
188
- if params[:pong].blank?
189
- return respond_with(
190
- status: :unprocessable_entity,
191
- resource: {
192
- error_type: :missing_pong,
193
- error_message: "You must provide a 'pong' parameter."
194
- }
195
- )
196
- end
197
-
198
- respond_with status: :ok, resource: { pong: params[:pong] }
199
- end
200
- end
201
- end
202
- end
203
- end
204
- end
205
- ```
206
-
207
- Once more, here's an example usage of the above operation:
208
-
209
- ```ruby
210
- result1 = API::V1::Ping::Operation::Create.call(params: { pong: '' })
211
- result1.success? # => false
212
-
213
- result2 = API::V1::Ping::Operation::Create.call(params: { pong: 'HELLO' })
214
- result2.success? # => true
215
- ```
216
-
217
- ## Halting the execution
218
-
219
- Both `#respond_with` and `#head` provide bang counterparts that halt the execution of the operation.
220
- They are useful, for instance, in before callbacks.
221
-
222
- The above operation can be rewritten like this:
223
-
224
- ```ruby
225
- module API
226
- module V1
227
- module Ping
228
- module Operation
229
- class Create < Pragma::Operation::Base
230
- before :validate_params
231
-
232
- def call
233
- respond_with status: :ok, resource: { pong: params[:pong] }
234
- end
235
-
236
- private
237
-
238
- def validate_params
239
- if params[:pong].blank?
240
- respond_with!(
241
- status: :unprocessable_entity,
242
- resource: {
243
- error_type: :missing_pong,
244
- error_message: "You must provide a 'pong' parameter."
245
- }
246
- )
247
- end
248
- end
249
- end
250
- end
251
- end
252
- end
253
- end
254
- ```
255
-
256
- The result is identical:
257
-
258
- ```ruby
259
- result1 = API::V1::Ping::Operation::Create.call(params: { pong: '' })
260
- result1.success? # => false
261
-
262
- result2 = API::V1::Ping::Operation::Create.call(params: { pong: 'HELLO' })
263
- result2.success? # => true
264
- ```