petail 0.2.0 → 0.4.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
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +41 -21
- data/lib/petail/payload.rb +14 -8
- data/lib/petail.rb +4 -2
- data/petail.gemspec +2 -1
- data.tar.gz.sig +0 -0
- metadata +16 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90ccb34a36bfc17201ce4b4881e8f37acbbc3714f076c5de0e7cf1d5ad373346
|
4
|
+
data.tar.gz: 0a2ee99b2cd3191aa9ec08473a218436bd259576f3b0e6e31c8779eddcbfe5b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b663a7f90af61259298cd14a8f23c63afbd0d0e6aab80c3c38c0242e7cc240418563021623bcf95e7af88e3368788bdad8ac30178431788f4cc3e552219815b7
|
7
|
+
data.tar.gz: 6efc822522e3e828df9c2d81953a8b2a2344e6ec9aec462ebe063cb32feffcc10552b4f874b67888d3e347875a805621df317b429d2a57227ecb55e061530611
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -55,10 +55,12 @@ The quickest way to get started is to create a new instance and then cast as JSO
|
|
55
55
|
|
56
56
|
[source,ruby]
|
57
57
|
----
|
58
|
-
payload = Petail
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
payload = Petail[
|
59
|
+
type: "https://demo.io/problem_details/timeout",
|
60
|
+
status: 413,
|
61
|
+
detail: "You've exceeded the 5MB upload limit.",
|
62
|
+
instance: "/profile/3a1bfd54-ae6c-4a61-8d0d-90c132428dc3"
|
63
|
+
]
|
62
64
|
|
63
65
|
payload.to_json
|
64
66
|
|
@@ -82,6 +84,19 @@ payload.to_xml
|
|
82
84
|
# </problem>
|
83
85
|
----
|
84
86
|
|
87
|
+
💡 You can also use `Petail.new` to create instances if you don't like `Petail.[]`, as shown above, but `.[]` is preferred.
|
88
|
+
|
89
|
+
=== Members
|
90
|
+
|
91
|
+
As briefly shown above, the minimum members (attributes) that make up problem details are:
|
92
|
+
|
93
|
+
* `type` (optional): The full (or relative) URI that links to additional documentation. Default: `"about:blank"`.
|
94
|
+
* `status` (optional): The link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status[HTTP status code] (or symbol) that must match your HTTP status code. Default: `nil`.
|
95
|
+
* `title` (optional): The HTTP status label that must match your HTTP status code label. Default: HTTP status label (dynamically computed based on code unless overwritten).
|
96
|
+
* `detail` (optional): The human readable reason for the error (should not include debugging information). Default: `nil`.
|
97
|
+
* `instance` (optional): The full (or relative) URI that represents the cause of the error. Default: `nil`.
|
98
|
+
* `extensions` (optional): A free form hash of additional details. Default: `{}`.
|
99
|
+
|
85
100
|
=== Media Types
|
86
101
|
|
87
102
|
For convenience, you can obtain the necessary media types for your HTTP headers as follows:
|
@@ -97,11 +112,11 @@ Petail.media_type_for :xml # "application/problem+xml"
|
|
97
112
|
|
98
113
|
=== Payload
|
99
114
|
|
100
|
-
You'll always get a `Petail::Payload` object answered back when using `Petail.new` for which you can cast to JSON, XML, and other types. There are few conveniences provided for you when constructing a new payload. For instance, you can also use status to set default title:
|
115
|
+
You'll always get a `Petail::Payload` object answered back when using `Petail.[]` or `Petail.new` for which you can cast to JSON, XML, and other types. There are few conveniences provided for you when constructing a new payload. For instance, you can also use status to set default title:
|
101
116
|
|
102
117
|
[source,ruby]
|
103
118
|
----
|
104
|
-
Petail
|
119
|
+
Petail[status: 413]
|
105
120
|
# #<Struct:Petail::Payload:0x0000ec80
|
106
121
|
# detail = nil,
|
107
122
|
# extensions = {},
|
@@ -116,7 +131,7 @@ Notice that standard HTTP 413 title of "Content Too Large" is provided for you b
|
|
116
131
|
|
117
132
|
[source,ruby]
|
118
133
|
----
|
119
|
-
Petail
|
134
|
+
Petail[status: :bad_request]
|
120
135
|
# #<Struct:Petail::Payload:0x0000f280
|
121
136
|
# detail = nil,
|
122
137
|
# extensions = {},
|
@@ -133,7 +148,7 @@ Due to the payload being a `Struct`, you have all of the standard methods availa
|
|
133
148
|
|
134
149
|
[source,]
|
135
150
|
----
|
136
|
-
payload = Petail
|
151
|
+
payload = Petail[status: :forbidden]
|
137
152
|
|
138
153
|
payload.add_extension(:account, "/accounts/1")
|
139
154
|
.add_extension(:balance, 50)
|
@@ -165,7 +180,7 @@ Both serialization and deserialization of JSON is supported. For example, given
|
|
165
180
|
|
166
181
|
[source,ruby]
|
167
182
|
----
|
168
|
-
payload = Petail
|
183
|
+
payload = Petail[
|
169
184
|
type: "https://test.io/problem_details/out_of_credit",
|
170
185
|
title: "You do not have enough credit.",
|
171
186
|
status: 403,
|
@@ -175,7 +190,7 @@ payload = Petail.new(
|
|
175
190
|
balance: 30,
|
176
191
|
accounts: %w[/accounts/1 /accounts/10]
|
177
192
|
}
|
178
|
-
|
193
|
+
]
|
179
194
|
----
|
180
195
|
|
181
196
|
This means you can serialize as follows:
|
@@ -183,7 +198,7 @@ This means you can serialize as follows:
|
|
183
198
|
[source,ruby]
|
184
199
|
----
|
185
200
|
payload.to_json
|
186
|
-
# {"type"
|
201
|
+
# "{\"type\":\"https://test.io/problem_details/out_of_credit\",\"title\":\"You do not have enough credit.\",\"status\":403,\"detail\":\"Your current balance is 30, but that costs 50.\",\"instance\":\"/accounts/1\",\"balance\":30,\"accounts\":[\"/accounts/1\",\"/accounts/10\"]}"
|
187
202
|
|
188
203
|
payload.to_json indent: " ", space: " ", object_nl: "\n", array_nl: "\n"
|
189
204
|
# {
|
@@ -192,13 +207,11 @@ payload.to_json indent: " ", space: " ", object_nl: "\n", array_nl: "\n"
|
|
192
207
|
# "status": 403,
|
193
208
|
# "detail": "Your current balance is 30, but that costs 50.",
|
194
209
|
# "instance": "/accounts/1",
|
195
|
-
# "
|
196
|
-
#
|
197
|
-
# "accounts"
|
198
|
-
#
|
199
|
-
#
|
200
|
-
# ]
|
201
|
-
# }
|
210
|
+
# "balance": 30,
|
211
|
+
# "accounts": [
|
212
|
+
# "/accounts/1",
|
213
|
+
# "/accounts/10"
|
214
|
+
# ]
|
202
215
|
# }
|
203
216
|
----
|
204
217
|
|
@@ -208,7 +221,7 @@ You can also deserialize by taking the result of the above and turning the raw J
|
|
208
221
|
|
209
222
|
[source,ruby]
|
210
223
|
----
|
211
|
-
Petail.from_json "{\"type\":\"https://test.io/problem_details/out_of_credit\",\"title\":\"You do not have enough credit.\",\"status\":403,\"detail\":\"Your current balance is 30, but that costs 50.\",\"instance\":\"/accounts/1\",\"
|
224
|
+
Petail.from_json "{\"type\":\"https://test.io/problem_details/out_of_credit\",\"title\":\"You do not have enough credit.\",\"status\":403,\"detail\":\"Your current balance is 30, but that costs 50.\",\"instance\":\"/accounts/1\",\"balance\":30,\"accounts\":[\"/accounts/1\",\"/accounts/10\"]}"
|
212
225
|
|
213
226
|
# #<Struct:Petail::Payload:0x00007670
|
214
227
|
# detail = "Your current balance is 30, but that costs 50.",
|
@@ -232,7 +245,7 @@ XML is supported too but isn't as robust as JSON support, at the moment. This is
|
|
232
245
|
|
233
246
|
[source,ruby]
|
234
247
|
----
|
235
|
-
payload = Petail
|
248
|
+
payload = Petail[
|
236
249
|
type: "https://test.io/problem_details/out_of_credit",
|
237
250
|
title: "You do not have enough credit.",
|
238
251
|
status: 403,
|
@@ -242,7 +255,7 @@ payload = Petail.new(
|
|
242
255
|
balance: 30,
|
243
256
|
accounts: %w[/accounts/1 /accounts/10]
|
244
257
|
}
|
245
|
-
|
258
|
+
]
|
246
259
|
----
|
247
260
|
|
248
261
|
This means you can serialize as follows:
|
@@ -349,6 +362,13 @@ To test, run:
|
|
349
362
|
bin/rake
|
350
363
|
----
|
351
364
|
|
365
|
+
== Resources
|
366
|
+
|
367
|
+
You can find additional resources here:
|
368
|
+
|
369
|
+
* link:https://www.iana.org/assignments/http-problem-types/http-problem-types.xhtml[IANA Hypertext Transfer Protocol (HTTP) Problem Types]: A registered list of problem types you can use.
|
370
|
+
* link:https://github.com/protocol-registries/http-problem-types[HTTP Problem Type Registration Requests]: Where you can register new problem types.
|
371
|
+
|
352
372
|
== link:https://alchemists.io/policies/license[License]
|
353
373
|
|
354
374
|
== link:https://alchemists.io/policies/security[Security]
|
data/lib/petail/payload.rb
CHANGED
@@ -5,8 +5,10 @@ require "rack/utils"
|
|
5
5
|
require "rexml"
|
6
6
|
|
7
7
|
module Petail
|
8
|
+
PRIMARY_KEYS = %i[type title status detail instance].freeze
|
9
|
+
|
8
10
|
# Models the problem details response payload.
|
9
|
-
Payload = Struct.new
|
11
|
+
Payload = Struct.new(*PRIMARY_KEYS, :extensions) do
|
10
12
|
def self.for(**attributes)
|
11
13
|
status = attributes.delete(:status).then { Rack::Utils.status_code it if it }
|
12
14
|
title = attributes.delete(:title).then { it || Rack::Utils::HTTP_STATUS_CODES[status] }
|
@@ -14,18 +16,23 @@ module Petail
|
|
14
16
|
new title:, status:, **attributes
|
15
17
|
end
|
16
18
|
|
17
|
-
def self.from_json
|
19
|
+
def self.from_json body
|
20
|
+
attributes = JSON body, symbolize_names: true
|
21
|
+
extensions = attributes.reject { |key| PRIMARY_KEYS.include? key }
|
22
|
+
|
23
|
+
self.for(**attributes.slice(*PRIMARY_KEYS), extensions:)
|
24
|
+
end
|
18
25
|
|
19
26
|
# :reek:TooManyStatements
|
20
27
|
def self.from_xml body, deserializer: XML::Deserializer
|
21
28
|
elements = REXML::Document.new(body).root.elements
|
22
29
|
|
23
30
|
attributes = elements.each_with_object({extensions: {}}) do |element, collection|
|
24
|
-
name = element.name
|
31
|
+
name = element.name.to_sym
|
25
32
|
text = element.text
|
26
33
|
|
27
34
|
case name
|
28
|
-
when
|
35
|
+
when *PRIMARY_KEYS then collection[name] = text
|
29
36
|
else collection[:extensions].merge! deserializer.call(element)
|
30
37
|
end
|
31
38
|
end
|
@@ -48,11 +55,12 @@ module Petail
|
|
48
55
|
|
49
56
|
def extension?(name) = extensions.key? name
|
50
57
|
|
51
|
-
def to_h =
|
58
|
+
def to_h = {type:, title:, status:, detail:, instance:, **extensions}.compact
|
52
59
|
|
53
60
|
def to_json(*) = to_h.to_json(*)
|
54
61
|
|
55
62
|
# :reek:TooManyStatements
|
63
|
+
# :reek:FeatureEnvy
|
56
64
|
def to_xml(serializer: XML::Serializer, **options)
|
57
65
|
document = REXML::Document.new
|
58
66
|
document.add REXML::XMLDecl.new("1.0", "UTF-8")
|
@@ -60,9 +68,7 @@ module Petail
|
|
60
68
|
problem = REXML::Element.new("problem").add_namespace("urn:ietf:rfc:7807")
|
61
69
|
document.add problem
|
62
70
|
|
63
|
-
|
64
|
-
attributes.merge! attributes.delete :extensions if extensions.any?
|
65
|
-
attributes.each { |name, value| serializer.call name, value, problem }
|
71
|
+
to_h.each { |name, value| serializer.call name, value, problem }
|
66
72
|
|
67
73
|
"".dup.tap { document.write(**options, output: it) }
|
68
74
|
end
|
data/lib/petail.rb
CHANGED
@@ -10,6 +10,10 @@ module Petail
|
|
10
10
|
MEDIA_TYPE_XML = "application/problem+xml"
|
11
11
|
TYPES = %i[json xml].freeze
|
12
12
|
|
13
|
+
def self.[](**) = Payload.for(**)
|
14
|
+
|
15
|
+
def self.new(**) = Payload.for(**)
|
16
|
+
|
13
17
|
def self.from_json(...) = Payload.from_json(...)
|
14
18
|
|
15
19
|
def self.from_xml(...) = Payload.from_xml(...)
|
@@ -17,6 +21,4 @@ module Petail
|
|
17
21
|
def self.media_type_for key, types: TYPES
|
18
22
|
types.include?(key) ? const_get("MEDIA_TYPE_#{key.upcase}") : ""
|
19
23
|
end
|
20
|
-
|
21
|
-
def self.new(**) = Payload.for(**)
|
22
24
|
end
|
data/petail.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "petail"
|
5
|
-
spec.version = "0.
|
5
|
+
spec.version = "0.4.0"
|
6
6
|
spec.authors = ["Brooke Kuhlmann"]
|
7
7
|
spec.email = ["brooke@alchemists.io"]
|
8
8
|
spec.homepage = "https://alchemists.io/projects/petail"
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.required_ruby_version = "~> 3.4"
|
26
26
|
spec.add_dependency "rack", ">= 2.2", "< 4.0"
|
27
|
+
spec.add_dependency "rexml", "~> 3.4"
|
27
28
|
|
28
29
|
spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
|
29
30
|
spec.files = Dir["*.gemspec", "lib/**/*"]
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: petail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brooke Kuhlmann
|
@@ -55,6 +55,20 @@ dependencies:
|
|
55
55
|
- - "<"
|
56
56
|
- !ruby/object:Gem::Version
|
57
57
|
version: '4.0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rexml
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - "~>"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '3.4'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.4'
|
58
72
|
email:
|
59
73
|
- brooke@alchemists.io
|
60
74
|
executables: []
|
@@ -95,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
requirements: []
|
98
|
-
rubygems_version: 3.6.
|
112
|
+
rubygems_version: 3.6.9
|
99
113
|
specification_version: 4
|
100
114
|
summary: A RFC 9457 Problem Details for HTTP APIs implementation.
|
101
115
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|