-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathcontract.rb
More file actions
163 lines (146 loc) · 5.34 KB
/
contract.rb
File metadata and controls
163 lines (146 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# frozen_string_literal: true
module Html2rss
module Web
module Api
module V1
##
# Shared API v1 contract constants and payload builders.
module Contract # rubocop:disable Metrics/ModuleLength
CODES = {
unauthorized: Html2rss::Web::UnauthorizedError::CODE,
forbidden: Html2rss::Web::ForbiddenError::CODE,
internal_server_error: Html2rss::Web::InternalServerError::CODE
}.freeze
ERROR_KINDS = {
auth: 'auth',
input: 'input',
network: 'network',
server: 'server'
}.freeze
NEXT_ACTIONS = {
enter_token: 'enter_token',
correct_input: 'correct_input',
retry: 'retry',
wait: 'wait',
none: 'none'
}.freeze
RETRY_ACTIONS = {
alternate: 'alternate',
primary: 'primary',
none: 'none'
}.freeze
READINESS_PHASES = {
link_created: 'link_created',
feed_ready: 'feed_ready',
feed_not_ready_yet: 'feed_not_ready_yet',
preview_unavailable: 'preview_unavailable'
}.freeze
PREVIEW_STATUSES = {
pending: 'pending',
ready: 'ready',
degraded: 'degraded',
unavailable: 'unavailable'
}.freeze
MESSAGES = {
auto_source_disabled: 'Auto source feature is disabled',
health_check_failed: 'Health check failed'
}.freeze
class << self
# Builds the structured API error envelope used by JSON API routes.
#
# @param error [StandardError]
# @return [Hash{Symbol=>Object}] structured API error details.
def failure_payload(error)
metadata = failure_metadata(error)
base = {
message: client_message_for(error),
code: error_code_for(error),
kind: metadata[:kind],
retryable: metadata[:retryable],
next_action: metadata[:next_action],
retry_action: metadata[:retry_action]
}
metadata[:next_strategy] ? base.merge(next_strategy: metadata[:next_strategy]) : base
end
# Builds a warning entry for readiness/status responses.
#
# @param code [String]
# @param message [String]
# @param retryable [Boolean]
# @param next_action [String]
# @return [Hash{Symbol=>Object}] structured warning payload.
def warning(code:, message:, retryable:, next_action:)
{
code: code,
message: message,
retryable: retryable,
next_action: next_action
}
end
private
# @param error [StandardError]
# @return [Hash{Symbol=>Object}]
def failure_metadata(error)
case error
when Html2rss::Web::AutoSourceDisabledError, Html2rss::Web::HealthCheckFailedError
non_retryable_server_failure_metadata
when Html2rss::Web::UnauthorizedError then auth_failure_metadata
when Html2rss::Web::BadRequestError, Html2rss::Web::ForbiddenError then input_failure_metadata
else
generic_failure_metadata(error)
end
end
# @param error [StandardError]
# @return [Hash{Symbol=>Object}]
def generic_failure_metadata(error)
kind = Html2rss::Web::ErrorClassification.network_error?(error) ? :network : :server
{
kind: ERROR_KINDS[kind],
retryable: true,
next_action: NEXT_ACTIONS[:retry],
retry_action: RETRY_ACTIONS[:primary]
}
end
# @return [Hash{Symbol=>Object}]
def auth_failure_metadata
{
kind: ERROR_KINDS[:auth],
retryable: false,
next_action: NEXT_ACTIONS[:enter_token],
retry_action: RETRY_ACTIONS[:none]
}
end
# @return [Hash{Symbol=>Object}]
def input_failure_metadata
{
kind: ERROR_KINDS[:input],
retryable: false,
next_action: NEXT_ACTIONS[:correct_input],
retry_action: RETRY_ACTIONS[:none]
}
end
# @return [Hash{Symbol=>Object}]
def non_retryable_server_failure_metadata
{
kind: ERROR_KINDS[:server],
retryable: false,
next_action: NEXT_ACTIONS[:none],
retry_action: RETRY_ACTIONS[:none]
}
end
# @param error [StandardError]
# @return [String]
def client_message_for(error)
error.is_a?(Html2rss::Web::HttpError) ? error.message : Html2rss::Web::HttpError::DEFAULT_MESSAGE
end
# @param error [StandardError]
# @return [String]
def error_code_for(error)
error.respond_to?(:code) ? error.code : CODES[:internal_server_error]
end
end
end
end
end
end
end