problem 0.1.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 +7 -0
- data/CHANGELOG.md +13 -0
- data/DESIGN.md +274 -0
- data/LICENSE.txt +21 -0
- data/README.md +328 -0
- data/lib/problem/detailable.rb +139 -0
- data/lib/problem/details.rb +46 -0
- data/lib/problem/document.rb +52 -0
- data/lib/problem/exceptions_app.rb +78 -0
- data/lib/problem/i18nable.rb +87 -0
- data/lib/problem/railtie.rb +28 -0
- data/lib/problem/renderer.rb +34 -0
- data/lib/problem/rescuable.rb +80 -0
- data/lib/problem/retry_after.rb +59 -0
- data/lib/problem/version.rb +10 -0
- data/lib/problem.rb +69 -0
- data/sig/generated/problem/detailable.rbs +90 -0
- data/sig/generated/problem/document.rbs +36 -0
- data/sig/generated/problem/exceptions_app.rbs +40 -0
- data/sig/generated/problem/i18nable.rbs +55 -0
- data/sig/generated/problem/railtie.rbs +12 -0
- data/sig/generated/problem/renderer.rbs +16 -0
- data/sig/generated/problem/rescuable.rbs +54 -0
- data/sig/generated/problem/retry_after.rbs +47 -0
- data/sig/generated/problem/version.rbs +6 -0
- data/sig/generated/problem.rbs +39 -0
- data/sig/manual/core.rbs +5 -0
- data/sig/manual/detailable.rbs +37 -0
- data/sig/manual/details.rbs +24 -0
- data/sig/manual/document.rbs +12 -0
- data/sig/manual/i18nable.rbs +15 -0
- data/sig/manual/rails.rbs +14 -0
- data/sig/manual/rescuable.rbs +19 -0
- metadata +206 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Generated from lib/problem/detailable.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Declares an exception class as an RFC 9457 problem.
|
|
5
|
+
#
|
|
6
|
+
# class InsufficientBalance < StandardError
|
|
7
|
+
# include Problem::Detailable
|
|
8
|
+
#
|
|
9
|
+
# type "insufficient-balance"
|
|
10
|
+
# status 403
|
|
11
|
+
# title "Insufficient Balance"
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# raise InsufficientBalance.new(detail: "Required: 100, Available: 50")
|
|
15
|
+
#
|
|
16
|
+
# This holds only what RFC 9457 defines: a type, a title, a status, a per-occurrence
|
|
17
|
+
# detail, and the conversion to Problem::Details. Each is declared literally, which is
|
|
18
|
+
# all a deployment needs to render an error.
|
|
19
|
+
#
|
|
20
|
+
# A deployment that keeps a catalogue of its problems — a registry, an enum, a locale
|
|
21
|
+
# file — derives those three from it instead, by prepending a module to the error
|
|
22
|
+
# class's singleton that overrides `type`, `status` and `title` and calls `super` for a
|
|
23
|
+
# class the catalogue does not cover. Three things make that work and none of them are
|
|
24
|
+
# visible at the call site: ClassMethods is attached with `extend`, so a singleton
|
|
25
|
+
# prepend sits ahead of it; the gem never defines those three on the including class
|
|
26
|
+
# itself; and #to_problem reaches them through `self.class` rather than reading the
|
|
27
|
+
# class attributes behind them.
|
|
28
|
+
#
|
|
29
|
+
# @rbs module-self ::Exception
|
|
30
|
+
# @rbs module-self _DetailableSelf
|
|
31
|
+
module Detailable : ::Exception, _DetailableSelf
|
|
32
|
+
extend ActiveSupport::Concern
|
|
33
|
+
|
|
34
|
+
# The occurrence's own explanation, which RFC 9457 §3.1 defines as specific to this
|
|
35
|
+
# occurrence rather than to the problem type.
|
|
36
|
+
attr_reader detail: String?
|
|
37
|
+
|
|
38
|
+
# Extended into the including class by ActiveSupport::Concern, which picks up a nested
|
|
39
|
+
# ClassMethods by name. Written as a module rather than a `class_methods` block so the
|
|
40
|
+
# body is ordinary code that RBS can describe.
|
|
41
|
+
#
|
|
42
|
+
# @rbs module-self _DetailableClass
|
|
43
|
+
module ClassMethods : _DetailableClass
|
|
44
|
+
# Each reader doubles as its own setter so a subclass can override one declaration
|
|
45
|
+
# and inherit the rest, which is what class_attribute buys over a constant.
|
|
46
|
+
# : (?Integer?) -> Integer?
|
|
47
|
+
def status: (?Integer?) -> Integer?
|
|
48
|
+
|
|
49
|
+
# The identifier this class publishes, resolved against the type prefix when the
|
|
50
|
+
# occurrence is converted.
|
|
51
|
+
# : (?String?) -> String?
|
|
52
|
+
def type: (?String?) -> String?
|
|
53
|
+
|
|
54
|
+
# `interpolations` is part of the signature even though a literal title rarely needs
|
|
55
|
+
# it: a catalogue-backed override reads a template, and this is where the occurrence
|
|
56
|
+
# hands it the values.
|
|
57
|
+
# : (?String?, ?interpolations: Hash[Symbol, untyped]) -> String?
|
|
58
|
+
def title: (?String?, ?interpolations: Hash[Symbol, untyped]) -> String?
|
|
59
|
+
|
|
60
|
+
# Falls back to the global setting, so a deployment declares its authority once.
|
|
61
|
+
# : (?String?) -> String?
|
|
62
|
+
def type_prefix: (?String?) -> String?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Intercepts `detail:` and forwards everything else to the exception, so
|
|
66
|
+
# `raise Klass, "message"` keeps working.
|
|
67
|
+
# : (*untyped, ?detail: String?, **untyped) -> void
|
|
68
|
+
def initialize: (*untyped, ?detail: String?, **untyped) -> void
|
|
69
|
+
|
|
70
|
+
# Values the title template interpolates for this occurrence.
|
|
71
|
+
# : () -> Hash[Symbol, untyped]
|
|
72
|
+
def title_interpolations: () -> Hash[Symbol, untyped]
|
|
73
|
+
|
|
74
|
+
# Extension members this occurrence publishes alongside the RFC 9457 ones.
|
|
75
|
+
# : () -> Hash[Symbol, untyped]
|
|
76
|
+
def problem_extensions: () -> Hash[Symbol, untyped]
|
|
77
|
+
|
|
78
|
+
# A URI identifying this occurrence, which only the caller's request can supply.
|
|
79
|
+
# : () -> String?
|
|
80
|
+
def problem_instance: () -> String?
|
|
81
|
+
|
|
82
|
+
# Response headers this occurrence needs, applied by Problem::Rescuable.
|
|
83
|
+
# : () -> Hash[String, String]
|
|
84
|
+
def problem_headers: () -> Hash[String, String]
|
|
85
|
+
|
|
86
|
+
# This occurrence as an RFC 9457 document.
|
|
87
|
+
# : () -> Details
|
|
88
|
+
def to_problem: () -> Details
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated from lib/problem/document.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Serializes an RFC 9457 problem document.
|
|
5
|
+
#
|
|
6
|
+
# Included by Problem::Details, and by any value object that carries the same readers —
|
|
7
|
+
# the way to add typed members is to define a Data over Problem::Details.members rather
|
|
8
|
+
# than to subclass Details, which cannot gain members.
|
|
9
|
+
#
|
|
10
|
+
# @rbs module-self _Document
|
|
11
|
+
module Document : _Document
|
|
12
|
+
# The members RFC 9457 §3.1 defines, in the order they are serialized.
|
|
13
|
+
MEMBERS: Array[Symbol]
|
|
14
|
+
|
|
15
|
+
# The type of a problem that says nothing beyond its HTTP status (RFC 9457 §4.2.1).
|
|
16
|
+
ABOUT_BLANK: String
|
|
17
|
+
|
|
18
|
+
# The media type a problem document is served as (RFC 9457 §3).
|
|
19
|
+
CONTENT_TYPE: String
|
|
20
|
+
|
|
21
|
+
# Extension members are members of the problem object itself, not a nested container,
|
|
22
|
+
# so they merge at the top level. An absent member is omitted rather than sent as null.
|
|
23
|
+
# : () -> Hash[Symbol, untyped]
|
|
24
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
25
|
+
|
|
26
|
+
# The document with string keys, so a member whose value knows how to render itself
|
|
27
|
+
# gets the chance before JSON.generate sees it.
|
|
28
|
+
# : (?untyped) -> Hash[String, untyped]
|
|
29
|
+
def as_json: (?untyped) -> Hash[String, untyped]
|
|
30
|
+
|
|
31
|
+
# Defined explicitly because the generic Object#to_json would serialize the Data's
|
|
32
|
+
# inspect output, which looks like a response body until someone reads one.
|
|
33
|
+
# : (*untyped) -> String
|
|
34
|
+
def to_json: (*untyped) -> String
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Generated from lib/problem/exceptions_app.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Answers an exception that escaped the controller — a routing error, an unreadable
|
|
5
|
+
# body, a failure in middleware — as application/problem+json. Nothing else sees those:
|
|
6
|
+
# they are raised before dispatch, so no controller rescue_from ever runs.
|
|
7
|
+
#
|
|
8
|
+
# config.exceptions_app = Problem::ExceptionsApp.new(
|
|
9
|
+
# ActionDispatch::PublicExceptions.new(Rails.public_path),
|
|
10
|
+
# )
|
|
11
|
+
#
|
|
12
|
+
# A wrapper rather than an ActionDispatch::PublicExceptions subclass, so each layer of a
|
|
13
|
+
# deployment's stack claims the requests it recognizes and passes on the rest; a
|
|
14
|
+
# subclass can only ever be the innermost one.
|
|
15
|
+
class ExceptionsApp
|
|
16
|
+
# Wraps the exceptions app to fall back to, which keeps whatever it already answers
|
|
17
|
+
# for the requests this one does not claim.
|
|
18
|
+
# : (untyped app) -> void
|
|
19
|
+
def initialize: (untyped app) -> void
|
|
20
|
+
|
|
21
|
+
# Answers with a problem document, or hands the request to the wrapped app.
|
|
22
|
+
# : (Hash[String, untyped]) -> [Integer, Hash[String, String], Array[String]]
|
|
23
|
+
def call: (Hash[String, untyped]) -> [ Integer, Hash[String, String], Array[String] ]
|
|
24
|
+
|
|
25
|
+
# Browser traffic keeps getting the wrapped app's static error pages.
|
|
26
|
+
# : (Hash[String, untyped]) -> bool
|
|
27
|
+
private def html?: (Hash[String, untyped]) -> bool
|
|
28
|
+
|
|
29
|
+
# The problem to send for an escaped exception. The title is the status's own text,
|
|
30
|
+
# never the exception's message, which can quote internals.
|
|
31
|
+
#
|
|
32
|
+
# Override to publish a type: a deployment with a catalogue of problems maps the
|
|
33
|
+
# status onto it here, so every identifier a client can see comes from one place.
|
|
34
|
+
# : (Exception, Hash[String, untyped]) -> Details
|
|
35
|
+
private def problem_for: (Exception, Hash[String, untyped]) -> Details
|
|
36
|
+
|
|
37
|
+
# : (Details) -> [Integer, Hash[String, String], Array[String]]
|
|
38
|
+
private def render_problem: (Details) -> [ Integer, Hash[String, String], Array[String] ]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Generated from lib/problem/i18nable.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Looks a problem's title up through I18n instead of declaring it in Ruby.
|
|
5
|
+
#
|
|
6
|
+
# class ApiError < StandardError
|
|
7
|
+
# include Problem::I18nable
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# class NotFound < ApiError
|
|
11
|
+
# type "not-found"
|
|
12
|
+
# status 404
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# en:
|
|
16
|
+
# problem_details:
|
|
17
|
+
# titles:
|
|
18
|
+
# not_found: "Not Found"
|
|
19
|
+
#
|
|
20
|
+
# Titles are keyed by the declared type, dashes replaced, so two classes publishing one
|
|
21
|
+
# type publish one title as well. That is what keeps a subclass written to be
|
|
22
|
+
# indistinguishable from its parent indistinguishable in the title too.
|
|
23
|
+
#
|
|
24
|
+
# A class that declares a literal title keeps it, so a codebase can move over gradually.
|
|
25
|
+
#
|
|
26
|
+
# i18n is not a declared dependency of this gem. Referencing this module is opting in,
|
|
27
|
+
# and it is autoloaded so that a host which never does pays nothing for it.
|
|
28
|
+
#
|
|
29
|
+
# @rbs module-self ::Exception
|
|
30
|
+
# @rbs module-self _DetailableSelf
|
|
31
|
+
module I18nable : ::Exception, _DetailableSelf
|
|
32
|
+
extend ActiveSupport::Concern
|
|
33
|
+
|
|
34
|
+
include Detailable
|
|
35
|
+
|
|
36
|
+
# Where titles live unless a class says otherwise.
|
|
37
|
+
DEFAULT_SCOPE: String
|
|
38
|
+
|
|
39
|
+
# @rbs module-self _I18nableClass
|
|
40
|
+
module ClassMethods : _I18nableClass
|
|
41
|
+
# The I18n scope titles are looked up under.
|
|
42
|
+
# : (?String?) -> String
|
|
43
|
+
def title_scope: (?String?) -> String
|
|
44
|
+
|
|
45
|
+
# The key within that scope, derived from the declared type unless it is set.
|
|
46
|
+
# : (?(String | Symbol)?) -> String?
|
|
47
|
+
def title_key: (?(String | Symbol)?) -> String?
|
|
48
|
+
|
|
49
|
+
# Falls through to the literal DSL for a class that declares a title of its own, or
|
|
50
|
+
# that has no type to build a key from.
|
|
51
|
+
# : (?String?, ?interpolations: Hash[Symbol, untyped]) -> String?
|
|
52
|
+
def title: (?String?, ?interpolations: Hash[Symbol, untyped]) -> String?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Generated from lib/problem/railtie.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Wires the gem into a Rails application on boot.
|
|
5
|
+
#
|
|
6
|
+
# config.problem.type_prefix = "https://api-probs.example.com/"
|
|
7
|
+
#
|
|
8
|
+
# config.exceptions_app is deliberately left alone: replacing what an application set
|
|
9
|
+
# there is not an initializer's business, so Problem::ExceptionsApp is wired by hand.
|
|
10
|
+
class Railtie < ::Rails::Railtie
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated from lib/problem/renderer.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Registers the application/problem+json media type and the `problem:` renderer.
|
|
5
|
+
#
|
|
6
|
+
# render problem: error.to_problem
|
|
7
|
+
#
|
|
8
|
+
# Installed by Problem::Railtie on boot; a Rack host or a spec that never boots Rails
|
|
9
|
+
# calls Problem.install! itself.
|
|
10
|
+
module Renderer
|
|
11
|
+
# Renders whatever it is handed, so a deployment can pass its own serializer's object
|
|
12
|
+
# rather than a Problem::Details as long as it answers #status and #to_json.
|
|
13
|
+
# : () -> void
|
|
14
|
+
def self.install!: () -> void
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Generated from lib/problem/rescuable.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Renders any Problem::Detailable a controller raises as application/problem+json.
|
|
5
|
+
#
|
|
6
|
+
# class ApplicationController < ActionController::API
|
|
7
|
+
# include Problem::Rescuable
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# One registration covers every error class that mixed Problem::Detailable in, because
|
|
11
|
+
# rescue_from matches a Module with ===.
|
|
12
|
+
#
|
|
13
|
+
# Each step is its own method, so a deployment overrides the one it needs and a
|
|
14
|
+
# different transport — a Connect RPC error, say — replaces only #render_problem.
|
|
15
|
+
#
|
|
16
|
+
# @rbs module-self _RescuableSelf
|
|
17
|
+
module Rescuable : _RescuableSelf
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
# : (detailable_error) -> void
|
|
21
|
+
private def render_problem_detailable: (detailable_error) -> void
|
|
22
|
+
|
|
23
|
+
# Wraps building, reporting and rendering. Override to re-establish per-request state
|
|
24
|
+
# that has already unwound: ActionController::Rescue wraps
|
|
25
|
+
# AbstractController::Callbacks, so an around_action is gone by the time a rescue_from
|
|
26
|
+
# handler runs, and #problem_for may read a translation.
|
|
27
|
+
#
|
|
28
|
+
# private def around_problem_render(&) = I18n.with_locale(negotiated_locale, &)
|
|
29
|
+
#
|
|
30
|
+
# : () { () -> void } -> void
|
|
31
|
+
private def around_problem_render: () { () -> void } -> void
|
|
32
|
+
|
|
33
|
+
# Override to fill members only the request can supply, such as `instance`.
|
|
34
|
+
# : (detailable_error) -> Details
|
|
35
|
+
private def problem_for: (detailable_error) -> Details
|
|
36
|
+
|
|
37
|
+
# A client error the API expected is not an incident; a server-side failure is.
|
|
38
|
+
# : (detailable_error, Details) -> bool
|
|
39
|
+
private def report_problem?: (detailable_error, Details) -> bool
|
|
40
|
+
|
|
41
|
+
# A rescue_from handler swallows the exception, so nothing logs it and no error
|
|
42
|
+
# reporter sees it. Reported through the registry Rails error reporters subscribe to,
|
|
43
|
+
# rather than to a particular one.
|
|
44
|
+
# : (detailable_error, Details) -> void
|
|
45
|
+
private def report_problem: (detailable_error, Details) -> void
|
|
46
|
+
|
|
47
|
+
# : (detailable_error) -> void
|
|
48
|
+
private def apply_problem_headers: (detailable_error) -> void
|
|
49
|
+
|
|
50
|
+
# The only transport-specific step.
|
|
51
|
+
# : (Details, detailable_error) -> void
|
|
52
|
+
private def render_problem: (Details, detailable_error) -> void
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Generated from lib/problem/retry_after.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Problem
|
|
4
|
+
# Publishes when a rejected request may be retried, as a Retry-After response header
|
|
5
|
+
# (RFC 9110 §10.2.3) and a `retry_after` extension member carrying the same seconds.
|
|
6
|
+
#
|
|
7
|
+
# Mixed in after Problem::Detailable, whose hooks it extends rather than replaces:
|
|
8
|
+
#
|
|
9
|
+
# class Throttled < StandardError
|
|
10
|
+
# include Problem::Detailable
|
|
11
|
+
# include Problem::RetryAfter
|
|
12
|
+
#
|
|
13
|
+
# status 429
|
|
14
|
+
# title "Try again in %{retry_after} seconds"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# raise Throttled.new(retry_after: 30)
|
|
18
|
+
#
|
|
19
|
+
# @rbs module-self Detailable
|
|
20
|
+
module RetryAfter : Detailable
|
|
21
|
+
# The response header the wait is published in.
|
|
22
|
+
HEADER: String
|
|
23
|
+
|
|
24
|
+
# Seconds to wait, or the Time to wait until.
|
|
25
|
+
attr_reader retry_after: Integer | Time
|
|
26
|
+
|
|
27
|
+
# Takes the wait as seconds or as the Time to wait until, and forwards the rest.
|
|
28
|
+
# : (*untyped, retry_after: (Integer | Time), **untyped) -> void
|
|
29
|
+
def initialize: (*untyped, retry_after: Integer | Time, **untyped) -> void
|
|
30
|
+
|
|
31
|
+
# A deadline that has already passed is published as 0 rather than as a negative wait.
|
|
32
|
+
# : () -> Integer
|
|
33
|
+
def retry_after_seconds: () -> Integer
|
|
34
|
+
|
|
35
|
+
# Adds the wait, so a title template can name it.
|
|
36
|
+
# : () -> Hash[Symbol, untyped]
|
|
37
|
+
def title_interpolations: () -> Hash[Symbol, untyped]
|
|
38
|
+
|
|
39
|
+
# Publishes the wait in the document as a `retry_after` extension member.
|
|
40
|
+
# : () -> Hash[Symbol, untyped]
|
|
41
|
+
def problem_extensions: () -> Hash[Symbol, untyped]
|
|
42
|
+
|
|
43
|
+
# Publishes the wait as a Retry-After header, which a generic HTTP client obeys.
|
|
44
|
+
# : () -> Hash[String, String]
|
|
45
|
+
def problem_headers: () -> Hash[String, String]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Generated from lib/problem.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
# RFC 9457 Problem Details for HTTP APIs.
|
|
4
|
+
module Problem
|
|
5
|
+
# Anything a deployment sets once, rather than per error class.
|
|
6
|
+
class Configuration
|
|
7
|
+
# Resolves a declared type slug into the URI a client dispatches on, so an error class
|
|
8
|
+
# names `"bad-request"` instead of repeating the deployment's authority. Left unset,
|
|
9
|
+
# a slug goes out as-is, which RFC 9457 allows: `type` is a URI reference.
|
|
10
|
+
attr_accessor type_prefix: String?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# A URI reference carrying a scheme is already absolute (RFC 3986 §3.1).
|
|
14
|
+
ABSOLUTE_URI: Regexp
|
|
15
|
+
|
|
16
|
+
self.@config: Configuration?
|
|
17
|
+
|
|
18
|
+
# The settings in effect, created on first use.
|
|
19
|
+
# : () -> Configuration
|
|
20
|
+
def self.config: () -> Configuration
|
|
21
|
+
|
|
22
|
+
# Yields the settings, for a host that is not a Rails application.
|
|
23
|
+
#
|
|
24
|
+
# Problem.configure { |c| c.type_prefix = "https://api-probs.example.com/" }
|
|
25
|
+
#
|
|
26
|
+
# : () { (Configuration) -> void } -> void
|
|
27
|
+
def self.configure: () { (Configuration) -> void } -> void
|
|
28
|
+
|
|
29
|
+
# Registers the application/problem+json media type and the `problem:` renderer. Called
|
|
30
|
+
# by Problem::Railtie on boot; a Rack host or a spec that never boots Rails calls it.
|
|
31
|
+
# : () -> void
|
|
32
|
+
def self.install!: () -> void
|
|
33
|
+
|
|
34
|
+
# The type URI a declared value publishes. `about:blank` and anything already absolute
|
|
35
|
+
# are returned untouched — prefixing `about:blank` would produce a URI that looks valid
|
|
36
|
+
# and means nothing.
|
|
37
|
+
# : (String?, ?prefix: String?) -> String?
|
|
38
|
+
def self.type_uri: (String?, ?prefix: String?) -> String?
|
|
39
|
+
end
|
data/sig/manual/core.rbs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Problem
|
|
2
|
+
# What `rescue_from Problem::Detailable` actually hands a handler: the exception itself,
|
|
3
|
+
# carrying the concern. A module self-type constrains includers, not the module's own
|
|
4
|
+
# type, so the two have to be spelled out together.
|
|
5
|
+
type detailable_error = ::Exception & Detailable
|
|
6
|
+
|
|
7
|
+
# The class-level state and DSL Problem::Detailable installs. Declared by hand because
|
|
8
|
+
# `class_attribute` defines its accessors at runtime and `class_methods do ... end`
|
|
9
|
+
# builds ClassMethods from a block, neither of which RBS can infer.
|
|
10
|
+
interface _DetailableClass
|
|
11
|
+
def problem_status: () -> Integer?
|
|
12
|
+
def problem_status=: (Integer?) -> void
|
|
13
|
+
def problem_title: () -> String?
|
|
14
|
+
def problem_title=: (String?) -> void
|
|
15
|
+
def problem_uri: () -> String?
|
|
16
|
+
def problem_uri=: (String?) -> void
|
|
17
|
+
def problem_type_prefix: () -> String?
|
|
18
|
+
def problem_type_prefix=: (String?) -> void
|
|
19
|
+
|
|
20
|
+
def status: (?Integer?) -> Integer?
|
|
21
|
+
def title: (?String?, ?interpolations: Hash[Symbol, untyped]) -> String?
|
|
22
|
+
def type: (?String?) -> String?
|
|
23
|
+
def type_prefix: (?String?) -> String?
|
|
24
|
+
|
|
25
|
+
def name: () -> String?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# #to_problem reaches the DSL through `self.class`, which is what keeps a prepended
|
|
29
|
+
# catalogue override in the path.
|
|
30
|
+
interface _DetailableSelf
|
|
31
|
+
def class: () -> _DetailableClass
|
|
32
|
+
|
|
33
|
+
# The including exception's own constructor, which #initialize forwards to. Its shape
|
|
34
|
+
# belongs to that class, so it is untyped here rather than pinned to Exception.
|
|
35
|
+
def initialize: (*untyped, **untyped) -> void
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Problem
|
|
2
|
+
# Hand-written: rbs-inline cannot describe a `Data.define` assignment, so
|
|
3
|
+
# lib/problem/details.rb carries no `rbs_inline` magic comment.
|
|
4
|
+
class Details < ::Data
|
|
5
|
+
include Document
|
|
6
|
+
|
|
7
|
+
attr_reader type: String?
|
|
8
|
+
attr_reader title: String?
|
|
9
|
+
attr_reader status: Integer
|
|
10
|
+
attr_reader detail: String?
|
|
11
|
+
attr_reader instance: String?
|
|
12
|
+
attr_reader extensions: Hash[Symbol, untyped]
|
|
13
|
+
|
|
14
|
+
def self.new: (status: Integer, ?type: String?, ?title: String?, ?detail: String?,
|
|
15
|
+
?instance: String?, ?extensions: Hash[untyped, untyped]) -> Details
|
|
16
|
+
|
|
17
|
+
def self.members: () -> Array[Symbol]
|
|
18
|
+
|
|
19
|
+
def initialize: (status: Integer, ?type: String?, ?title: String?, ?detail: String?,
|
|
20
|
+
?instance: String?, ?extensions: Hash[untyped, untyped]) -> void
|
|
21
|
+
|
|
22
|
+
private def sanitized_extensions: (Hash[untyped, untyped]) -> Hash[Symbol, untyped]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Problem
|
|
2
|
+
# What Problem::Document needs from the value object including it. Declared by hand
|
|
3
|
+
# because a module cannot state the readers of its includers on its own.
|
|
4
|
+
interface _Document
|
|
5
|
+
def type: () -> String?
|
|
6
|
+
def title: () -> String?
|
|
7
|
+
def status: () -> Integer
|
|
8
|
+
def detail: () -> String?
|
|
9
|
+
def instance: () -> String?
|
|
10
|
+
def extensions: () -> Hash[Symbol, untyped]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Problem
|
|
2
|
+
# The class-level state Problem::I18nable adds, plus what it reaches on
|
|
3
|
+
# Problem::Detailable to fall through to.
|
|
4
|
+
interface _I18nableClass
|
|
5
|
+
def problem_title_scope: () -> String?
|
|
6
|
+
def problem_title_scope=: (String?) -> void
|
|
7
|
+
def problem_title_key: () -> String?
|
|
8
|
+
def problem_title_key=: (String?) -> void
|
|
9
|
+
|
|
10
|
+
def problem_title: () -> String?
|
|
11
|
+
def type: (?String?) -> String?
|
|
12
|
+
def title_scope: (?String?) -> String
|
|
13
|
+
def title_key: (?(String | Symbol)?) -> String?
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Rails APIs the gem uses that gem_rbs_collection does not declare.
|
|
2
|
+
module Rails
|
|
3
|
+
class Railtie
|
|
4
|
+
def self.initializer: (String, **untyped) ?{ (untyped) -> void } -> void
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class Application
|
|
8
|
+
class Configuration
|
|
9
|
+
# Set by Problem::Railtie, read by its initializer.
|
|
10
|
+
def problem: () -> untyped
|
|
11
|
+
def problem=: (untyped) -> void
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Problem
|
|
2
|
+
# What Problem::Rescuable needs from the controller it is mixed into. Declared by hand
|
|
3
|
+
# for the same reason the sibling gem declares its controller self-type: a mix-in cannot
|
|
4
|
+
# state the API of its includers.
|
|
5
|
+
interface _RescuableSelf
|
|
6
|
+
def render: (**untyped) -> void
|
|
7
|
+
def response: () -> untyped
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ActiveSupport
|
|
12
|
+
# Absent from gem_rbs_collection's activesupport signatures.
|
|
13
|
+
def self.error_reporter: () -> ErrorReporter
|
|
14
|
+
|
|
15
|
+
class ErrorReporter
|
|
16
|
+
def report: (Exception, ?handled: bool, ?severity: Symbol, ?context: Hash[Symbol, untyped],
|
|
17
|
+
?source: String?) -> void
|
|
18
|
+
end
|
|
19
|
+
end
|