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.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -8
- data/README.md +164 -3
- data/lib/pragma/operation.rb +14 -4
- data/lib/pragma/operation/base.rb +2 -247
- data/lib/pragma/operation/error.rb +27 -0
- data/lib/pragma/operation/response.rb +102 -0
- data/lib/pragma/operation/response/bad_request.rb +16 -0
- data/lib/pragma/operation/response/created.rb +13 -0
- data/lib/pragma/operation/response/forbidden.rb +19 -0
- data/lib/pragma/operation/response/no_content.rb +13 -0
- data/lib/pragma/operation/response/not_found.rb +19 -0
- data/lib/pragma/operation/response/ok.rb +13 -0
- data/lib/pragma/operation/response/unprocessable_entity.rb +23 -0
- data/lib/pragma/operation/version.rb +2 -1
- data/pragma-operation.gemspec +16 -19
- metadata +15 -55
- data/doc/01-basic-usage.md +0 -264
- data/doc/02-contracts.md +0 -154
- data/doc/03-policies.md +0 -179
- data/doc/04-decorators.md +0 -50
- data/lib/pragma/operation/authorization.rb +0 -130
- data/lib/pragma/operation/decoration.rb +0 -76
- data/lib/pragma/operation/validation.rb +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b48bd839081e93bf86ae68587a35c6f3ad07fc4d
|
4
|
+
data.tar.gz: a0ce83bc5177c74503d82b744126f0f02931093c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cde98287cc82bc74923286973dc18cf83c04847943bc5bbc86652ac59b7391272b2b41a336cfbc47f2da9f3cc2dbf3c0f381a0dde76040a5a00d36d87848ca5d
|
7
|
+
data.tar.gz: 9d4179add815dcb6c5732c8c8501ba407298d97a1ac78c749e1499e85dede8a6b2d146c6e48e6db890df171f426f3a78e85469f2eed9aafe22f52b085245b015
|
data/.rubocop.yml
CHANGED
@@ -15,6 +15,7 @@ AllCops:
|
|
15
15
|
- 'config/**/*'
|
16
16
|
- '**/Rakefile'
|
17
17
|
- '**/Gemfile'
|
18
|
+
- 'pragma-operation.gemspec'
|
18
19
|
|
19
20
|
RSpec/DescribeClass:
|
20
21
|
Exclude:
|
@@ -24,26 +25,26 @@ Style/BlockDelimiters:
|
|
24
25
|
Exclude:
|
25
26
|
- 'spec/**/*'
|
26
27
|
|
27
|
-
|
28
|
+
Layout/AlignParameters:
|
28
29
|
EnforcedStyle: with_fixed_indentation
|
29
30
|
|
30
|
-
|
31
|
+
Layout/ClosingParenthesisIndentation:
|
31
32
|
Enabled: false
|
32
33
|
|
33
34
|
Metrics/LineLength:
|
34
35
|
Max: 100
|
35
36
|
AllowURI: true
|
36
37
|
|
37
|
-
|
38
|
+
Layout/FirstParameterIndentation:
|
38
39
|
Enabled: false
|
39
40
|
|
40
|
-
|
41
|
+
Layout/MultilineMethodCallIndentation:
|
41
42
|
EnforcedStyle: indented
|
42
43
|
|
43
|
-
|
44
|
+
Layout/IndentArray:
|
44
45
|
EnforcedStyle: consistent
|
45
46
|
|
46
|
-
|
47
|
+
Layout/IndentHash:
|
47
48
|
EnforcedStyle: consistent
|
48
49
|
|
49
50
|
Style/SignalException:
|
@@ -68,7 +69,7 @@ RSpec/NamedSubject:
|
|
68
69
|
RSpec/ExampleLength:
|
69
70
|
Enabled: false
|
70
71
|
|
71
|
-
|
72
|
+
Layout/MultilineMethodCallBraceLayout:
|
72
73
|
Enabled: false
|
73
74
|
|
74
75
|
Metrics/MethodLength:
|
@@ -86,7 +87,7 @@ Metrics/CyclomaticComplexity:
|
|
86
87
|
Metrics/ClassLength:
|
87
88
|
Enabled: false
|
88
89
|
|
89
|
-
|
90
|
+
Layout/CaseIndentation:
|
90
91
|
EnforcedStyle: end
|
91
92
|
IndentOneStep: false
|
92
93
|
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
Operations encapsulate the business logic of your JSON API.
|
9
9
|
|
10
|
-
They are built on top of the
|
10
|
+
They are built on top of the [Trailblazer::Operation](https://github.com/trailblazer/trailblazer-operation) gem.
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -31,8 +31,169 @@ $ gem install pragma-operation
|
|
31
31
|
|
32
32
|
## Usage
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
Let's build your first operation!
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
module API
|
38
|
+
module V1
|
39
|
+
module Article
|
40
|
+
class Show < Pragma::Operation::Base
|
41
|
+
step :find!
|
42
|
+
failure :handle_not_found!, fail_fast: true
|
43
|
+
step :authorize!
|
44
|
+
failure :handle_unauthorized!
|
45
|
+
step :respond!
|
46
|
+
|
47
|
+
def find!(params:, **options)
|
48
|
+
options['model'] = ::Article.find_by(id: params[:id])
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_not_found!(options)
|
52
|
+
options['result.response'] = Pragma::Operation::Response::NotFound.new
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def authorize!(options)
|
57
|
+
options['result.authorization'] = options['model'].published? ||
|
58
|
+
options['model'].author == options['current_user']
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_unauthorized!(options)
|
62
|
+
options['result.response'] = Pragma::Operation::Response::Forbidden.new(
|
63
|
+
entity: Error.new(
|
64
|
+
error_type: :forbidden,
|
65
|
+
error_message: 'You can only access an article if published or authored by you.'
|
66
|
+
)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond!(options)
|
71
|
+
options['result.response'] = Pragma::Operation::Response::Ok.new(
|
72
|
+
entity: options['model'].as_json
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
Yes, I know. This does not make any sense yet. Before continuing, I encourage you to read (and
|
82
|
+
understand!) the documentation of [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/index.html).
|
83
|
+
Pragma::Operation is simply an extension of its TRB counterpart. For the rest of this guide, we will
|
84
|
+
assume you have a good understanding of TRB concepts like flow control and macros.
|
85
|
+
|
86
|
+
### Response basics
|
87
|
+
|
88
|
+
The only requirement for a Pragma operation is that it sets a `result.response` key in the options
|
89
|
+
hash by the end of its execution. This is a `Pragma::Operation::Response` object that will be used
|
90
|
+
by [pragma-rails](https://github.com/pragmarb/pragma-rails) or another integration to respond with
|
91
|
+
the proper HTTP information.
|
92
|
+
|
93
|
+
Responses have, just as you'd expect, a status, headers and body. You can manipulate them by using
|
94
|
+
the `status`, `headers` and `entity` parameters of the initializer:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
response = Pragma::Operation::Response.new(
|
98
|
+
status: 201,
|
99
|
+
headers: {
|
100
|
+
'X-Api-Custom' => 'Value'
|
101
|
+
},
|
102
|
+
entity: my_model
|
103
|
+
)
|
104
|
+
```
|
105
|
+
|
106
|
+
You can also set these properties through their accessors after instantiating the response:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
# You can set the status as a symbol:
|
110
|
+
response.status = :created
|
111
|
+
|
112
|
+
# You can set it as an HTTP status code:
|
113
|
+
response.status = 201
|
114
|
+
|
115
|
+
# You can manipulate headers:
|
116
|
+
response.headers['X-Api-Custom'] = 'Value'
|
117
|
+
|
118
|
+
# You can manipulate the entity:
|
119
|
+
response.entity = my_model
|
120
|
+
|
121
|
+
# The entity can be any object responding to #to_json:
|
122
|
+
response.entity = {
|
123
|
+
foo: :bar
|
124
|
+
}
|
125
|
+
```
|
126
|
+
|
127
|
+
### Decorating entities
|
128
|
+
|
129
|
+
The response class also has support for Pragma [decorators](https://github.com/pragmarb/pragma-decorator).
|
130
|
+
|
131
|
+
If you use decorators, you can set a decorator as the entity or you can use the `#decorate_with`
|
132
|
+
convenience method to decorate the existing entity:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
response.entity = ArticleDecorator.new(article)
|
136
|
+
|
137
|
+
# This is equivalent to the above:
|
138
|
+
response.entity = article
|
139
|
+
response.decorate_with(ArticleDecorator) # returns the response itself for chaining
|
140
|
+
```
|
141
|
+
|
142
|
+
### Errors
|
143
|
+
|
144
|
+
Pragma::Operation ships with an `Error` data structure that's simply the recommended way to present
|
145
|
+
your errors. You can build your custom error by creating a new instance of it and specify a
|
146
|
+
machine-readable error type and a human-readable error message:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
error = Pragma::Operation::Error.new(
|
150
|
+
error_type: :invalid_date,
|
151
|
+
error_message: 'You have specified an invalid date in your request.'
|
152
|
+
)
|
153
|
+
|
154
|
+
error.as_json # => {:error_type=>:invalid_date, :error_message=>"You have specified an invalid date in your request.", :meta=>{}}
|
155
|
+
error.to_json # => {"error_type":"invalid_date","error_message":"You have specified an invalid date in your request.","meta":{}}
|
156
|
+
```
|
157
|
+
|
158
|
+
Do you see that `meta` property in the JSON representation of the error? You can use it to include
|
159
|
+
additional metadata about the error. This is especially useful, for instance, with validation errors
|
160
|
+
as you can include the exact fields and validation messages (which is exactly what Pragma does by
|
161
|
+
default, by the way):
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
error = Pragma::Operation::Error.new(
|
165
|
+
error_type: :invalid_date,
|
166
|
+
error_message: 'You have specified an invalid date in your request.',
|
167
|
+
meta: {
|
168
|
+
expected_format: 'YYYY-MM-DD'
|
169
|
+
}
|
170
|
+
)
|
171
|
+
|
172
|
+
error.as_json # => {:error_type=>:invalid_date, :error_message=>"You have specified an invalid date in your request.", :meta=>{:expected_format=>"YYYY-MM-DD"}}
|
173
|
+
error.to_json # => {"error_type":"invalid_date","error_message":"You have specified an invalid date in your request.","meta":{"expected_format":"YYYY-MM-DD"}}
|
174
|
+
```
|
175
|
+
|
176
|
+
If you don't want to go with this format, you are free to implement your own error class, but it is
|
177
|
+
not recommended, as the [built-in macros](https://github.com/pragmarb/pragma/tree/master/lib/pragma/operation/macro)
|
178
|
+
will use `Pragma::Operation::Error`.
|
179
|
+
|
180
|
+
### Built-in responses
|
181
|
+
|
182
|
+
Last but not least, as you have seen in the example operation, Pragma provides some
|
183
|
+
[built-in responses](https://github.com/pragmarb/pragma-operation/tree/master/lib/pragma/operation/response)
|
184
|
+
for common status codes and bodies. Some of these only have a status code while others (the error
|
185
|
+
responses) also have a default entity attached to them. For instance, you can use `Pragma::Operation::Response::Forbidden`
|
186
|
+
without specifying your own error type and message:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
response = Pragma::Operation::Response::Forbidden.new
|
190
|
+
|
191
|
+
response.status # => 403
|
192
|
+
response.entity.to_json # => {"error_type":"forbidden","error_message":"You are not authorized to access the requested resource.","meta":{}}
|
193
|
+
```
|
194
|
+
|
195
|
+
The built-in responses are not meant to be comprehensive and you will most likely have to implement
|
196
|
+
your own. If you write some that you think could be useful, feel free to open a PR!
|
36
197
|
|
37
198
|
## Contributing
|
38
199
|
|
data/lib/pragma/operation.rb
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'trailblazer/operation'
|
3
6
|
|
4
7
|
require 'pragma/operation/version'
|
5
|
-
require 'pragma/operation/authorization'
|
6
|
-
require 'pragma/operation/validation'
|
7
|
-
require 'pragma/operation/decoration'
|
8
8
|
require 'pragma/operation/base'
|
9
|
+
require 'pragma/operation/error'
|
10
|
+
|
11
|
+
require 'pragma/operation/response'
|
12
|
+
require 'pragma/operation/response/bad_request'
|
13
|
+
require 'pragma/operation/response/not_found'
|
14
|
+
require 'pragma/operation/response/forbidden'
|
15
|
+
require 'pragma/operation/response/unprocessable_entity'
|
16
|
+
require 'pragma/operation/response/created'
|
17
|
+
require 'pragma/operation/response/ok'
|
18
|
+
require 'pragma/operation/response/no_content'
|
9
19
|
|
10
20
|
module Pragma
|
11
21
|
# Operations provide business logic encapsulation for your JSON API.
|
@@ -1,86 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Pragma
|
3
4
|
module Operation
|
4
5
|
# This is the base class all your operations should extend.
|
5
6
|
#
|
6
7
|
# @author Alessandro Desantis
|
7
|
-
|
8
|
-
# @abstract Subclass and override {#call} to implement an operation.
|
9
|
-
class Base
|
10
|
-
include Interactor
|
11
|
-
|
12
|
-
include Authorization
|
13
|
-
include Validation
|
14
|
-
include Decoration
|
15
|
-
|
16
|
-
STATUSES = {
|
17
|
-
200 => :ok,
|
18
|
-
201 => :created,
|
19
|
-
202 => :accepted,
|
20
|
-
203 => :non_authoritative_information,
|
21
|
-
204 => :no_content,
|
22
|
-
205 => :reset_content,
|
23
|
-
206 => :partial_content,
|
24
|
-
207 => :multi_status,
|
25
|
-
208 => :already_reported,
|
26
|
-
300 => :multiple_choices,
|
27
|
-
301 => :moved_permanently,
|
28
|
-
302 => :found,
|
29
|
-
303 => :see_other,
|
30
|
-
304 => :not_modified,
|
31
|
-
305 => :use_proxy,
|
32
|
-
307 => :temporary_redirect,
|
33
|
-
400 => :bad_request,
|
34
|
-
401 => :unauthorized,
|
35
|
-
402 => :payment_required,
|
36
|
-
403 => :forbidden,
|
37
|
-
404 => :not_found,
|
38
|
-
405 => :method_not_allowed,
|
39
|
-
406 => :not_acceptable,
|
40
|
-
407 => :proxy_authentication_required,
|
41
|
-
408 => :request_timeout,
|
42
|
-
409 => :conflict,
|
43
|
-
410 => :gone,
|
44
|
-
411 => :length_required,
|
45
|
-
412 => :precondition_failed,
|
46
|
-
413 => :request_entity_too_large,
|
47
|
-
414 => :request_uri_too_large,
|
48
|
-
415 => :unsupported_media_type,
|
49
|
-
416 => :request_range_not_satisfiable,
|
50
|
-
417 => :expectation_failed,
|
51
|
-
418 => :im_a_teapot,
|
52
|
-
422 => :unprocessable_entity,
|
53
|
-
423 => :locked,
|
54
|
-
424 => :failed_dependency,
|
55
|
-
425 => :unordered_collection,
|
56
|
-
426 => :upgrade_required,
|
57
|
-
428 => :precondition_required,
|
58
|
-
429 => :too_many_requests,
|
59
|
-
431 => :request_header_fields_too_large,
|
60
|
-
449 => :retry_with,
|
61
|
-
500 => :internal_server_error,
|
62
|
-
501 => :not_implemented,
|
63
|
-
502 => :bad_gateway,
|
64
|
-
503 => :service_unavailable,
|
65
|
-
504 => :gateway_timeout,
|
66
|
-
505 => :http_version_not_supported,
|
67
|
-
506 => :variant_also_negotiates,
|
68
|
-
507 => :insufficient_storage,
|
69
|
-
509 => :bandwidth_limit_exceeded,
|
70
|
-
510 => :not_extended,
|
71
|
-
511 => :network_authentication_required
|
72
|
-
}.freeze
|
73
|
-
|
8
|
+
class Base < Trailblazer::Operation
|
74
9
|
class << self
|
75
|
-
def inherited(child)
|
76
|
-
child.class_eval do
|
77
|
-
before :setup_context
|
78
|
-
around :handle_halt
|
79
|
-
after :mark_result, :consolidate_status, :validate_status, :set_default_status
|
80
|
-
after :build_links
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
10
|
# Returns the name of this operation.
|
85
11
|
#
|
86
12
|
# For instance, if the operation is called +API::V1::Post::Operation::Create+, returns
|
@@ -96,177 +22,6 @@ module Pragma
|
|
96
22
|
.to_sym
|
97
23
|
end
|
98
24
|
end
|
99
|
-
|
100
|
-
# Runs the operation.
|
101
|
-
def call
|
102
|
-
fail NotImplementedError
|
103
|
-
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
|
107
|
-
# Returns the params this operation is being run with.
|
108
|
-
#
|
109
|
-
# This is just a shortcut for +context.params+.
|
110
|
-
#
|
111
|
-
# @return [Hash]
|
112
|
-
def params
|
113
|
-
context.params
|
114
|
-
end
|
115
|
-
|
116
|
-
# Sets the status and resource to respond with.
|
117
|
-
#
|
118
|
-
# You can achieve the same result by setting +context.status+, +context.headers+ and
|
119
|
-
# +context.resource+ wherever you want in {#call}.
|
120
|
-
#
|
121
|
-
# Note that calling this method doesn't halt the execution of the operation and that this
|
122
|
-
# method can be called multiple times, overriding the previous context.
|
123
|
-
#
|
124
|
-
# @param status [Integer|Symbol] an HTTP status code
|
125
|
-
# @param resource [Object] an object responding to +#to_json+
|
126
|
-
# @param headers [Hash] HTTP headers
|
127
|
-
# @param links [Hash] links to use for building the +Link+ header
|
128
|
-
def respond_with(status: nil, resource: nil, headers: nil, links: nil)
|
129
|
-
context.status = status if status
|
130
|
-
context.resource = resource if resource
|
131
|
-
context.headers = headers if headers
|
132
|
-
context.links = links if links
|
133
|
-
end
|
134
|
-
|
135
|
-
# Same as {#respond_with}, but also halts the execution of the operation.
|
136
|
-
#
|
137
|
-
# @see #respond_with
|
138
|
-
def respond_with!(*args)
|
139
|
-
respond_with(*args)
|
140
|
-
fail Halt
|
141
|
-
end
|
142
|
-
|
143
|
-
# Sets the status to respond with.
|
144
|
-
#
|
145
|
-
# You can achieve the same result by setting +context.status+ wherever you want in {#call}.
|
146
|
-
#
|
147
|
-
# Note that calling this method doesn't halt the execution of the operation and that this
|
148
|
-
# method can be called multiple times, overriding the previous context.
|
149
|
-
#
|
150
|
-
# @param status [Integer|Symbol] an HTTP status code
|
151
|
-
def head(status)
|
152
|
-
context.status = status
|
153
|
-
end
|
154
|
-
|
155
|
-
# Same as {#head}, but also halts the execution of the operation.
|
156
|
-
#
|
157
|
-
# @param status [Integer|Symbol] an HTTP status code
|
158
|
-
#
|
159
|
-
# @see #head
|
160
|
-
def head!(status)
|
161
|
-
head status
|
162
|
-
fail Halt
|
163
|
-
end
|
164
|
-
|
165
|
-
# Returns the current user.
|
166
|
-
#
|
167
|
-
# This is just a shortcut for +context.current_user+.
|
168
|
-
#
|
169
|
-
# @return [Object]
|
170
|
-
def current_user
|
171
|
-
context.current_user
|
172
|
-
end
|
173
|
-
|
174
|
-
# Returns the headers we are responding with.
|
175
|
-
#
|
176
|
-
# This is just a shortcut for +context.headers+.
|
177
|
-
#
|
178
|
-
# @return [Hash]
|
179
|
-
def headers
|
180
|
-
context.headers
|
181
|
-
end
|
182
|
-
|
183
|
-
# Returns the links we are responding with.
|
184
|
-
#
|
185
|
-
# This is just a shotcut for +context.links+.
|
186
|
-
#
|
187
|
-
# @return [Hash]
|
188
|
-
def links
|
189
|
-
context.links
|
190
|
-
end
|
191
|
-
|
192
|
-
private
|
193
|
-
|
194
|
-
def with_hooks
|
195
|
-
# This overrides the default behavior, which is not to run after hooks if an exception is
|
196
|
-
# raised either in +#call+ or one of the before hooks. See:
|
197
|
-
# https://github.com/collectiveidea/interactor/blob/master/lib/interactor/hooks.rb#L210)
|
198
|
-
run_around_hooks do
|
199
|
-
begin
|
200
|
-
run_before_hooks
|
201
|
-
yield
|
202
|
-
ensure
|
203
|
-
run_after_hooks
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def setup_context
|
209
|
-
context.params ||= {}
|
210
|
-
context.headers = {}
|
211
|
-
context.links = {}
|
212
|
-
end
|
213
|
-
|
214
|
-
def handle_halt(interactor)
|
215
|
-
interactor.call
|
216
|
-
rescue Halt # rubocop:disable Lint/HandleExceptions
|
217
|
-
end
|
218
|
-
|
219
|
-
def set_default_status
|
220
|
-
return if context.status
|
221
|
-
context.status = context.resource ? :ok : :no_content
|
222
|
-
end
|
223
|
-
|
224
|
-
def validate_status
|
225
|
-
if context.status.is_a?(Integer)
|
226
|
-
fail InvalidStatusError, context.status unless STATUSES.key?(context.status)
|
227
|
-
else
|
228
|
-
fail InvalidStatusError, context.status unless STATUSES.invert.key?(context.status.to_sym)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def consolidate_status
|
233
|
-
context.status = if context.status.is_a?(Integer)
|
234
|
-
STATUSES[context.status]
|
235
|
-
else
|
236
|
-
context.status.to_sym
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def mark_result
|
241
|
-
return if /\A(2|3)\d{2}\z/ =~ STATUSES.invert[context.status].to_s
|
242
|
-
context.fail! pragma_operation_failure: true
|
243
|
-
end
|
244
|
-
|
245
|
-
def build_links
|
246
|
-
headers.delete('Link')
|
247
|
-
|
248
|
-
link_header = context.links.each_pair.select do |_relation, url|
|
249
|
-
url && !url.empty?
|
250
|
-
end.map do |relation, url|
|
251
|
-
%(<#{url}>; rel="#{relation}")
|
252
|
-
end.join(",\n ")
|
253
|
-
|
254
|
-
headers['Link'] = link_header unless link_header.empty?
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
Halt = Class.new(StandardError)
|
259
|
-
|
260
|
-
# This error is raised when an invalid status is set for an operation.
|
261
|
-
#
|
262
|
-
# @author Alessandro Desantis
|
263
|
-
class InvalidStatusError < StandardError
|
264
|
-
# Initializes the error.
|
265
|
-
#
|
266
|
-
# @param [Integer|Symbol] an invalid HTTP status code
|
267
|
-
def initialize(status)
|
268
|
-
super "'#{status}' is not a valid HTTP status code."
|
269
|
-
end
|
270
25
|
end
|
271
26
|
end
|
272
27
|
end
|