-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor errors #213
Open
pedro
wants to merge
13
commits into
main
Choose a base branch
from
pedro-errors-v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Refactor errors #213
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b01a8f7
change Error interface, take options hash
400b5c6
support for error metadata
0d343ed
automatically turn NoMatchingRow exceptions in a 404
45fccbf
keep HTTPStatusError as an abstract class
3bb5dcd
change error interface, always take message as the first param
bd93fca
remove errors outside the 40x-50x range
57779ef
add I18n to the template app
4a83438
configure pliny specs to use locales from the template
2222851
change error interface: read user message from locales
a333272
fix path
74ab13b
sort
8518639
oops, make clear this is a method
b9ddc68
make Error#user_message lazy
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,64 @@ | ||
require "i18n" | ||
|
||
module Pliny | ||
module Errors | ||
class Error < StandardError | ||
attr_accessor :id | ||
|
||
def self.render(error) | ||
headers = { "Content-Type" => "application/json; charset=utf-8" } | ||
data = { id: error.id, message: error.message } | ||
[error.status, headers, [MultiJson.encode(data)]] | ||
end | ||
class << self | ||
attr_accessor :error_class_id, :error_class_status | ||
|
||
def initialize(message, id) | ||
@id = id | ||
super(message) | ||
def render(error) | ||
headers = { "Content-Type" => "application/json; charset=utf-8" } | ||
data = { id: error.id, message: error.user_message }.merge(error.metadata) | ||
[error.status, headers, [MultiJson.encode(data)]] | ||
end | ||
end | ||
end | ||
|
||
class HTTPStatusError < Error | ||
attr_accessor :status | ||
attr_accessor :id, :status, :metadata | ||
|
||
def initialize(message = nil, id = nil, status = nil) | ||
meta = Pliny::Errors::META[self.class] | ||
message = message || meta[1] + "." | ||
id = id || meta[1].downcase.tr(' ', '_').to_sym | ||
@status = status || meta[0] | ||
super(message, id) | ||
def initialize(id=nil, metadata: {}) | ||
@id = (id || self.class.error_class_id).to_sym | ||
@status = self.class.error_class_status | ||
@metadata = metadata | ||
super(@id.to_s) | ||
end | ||
|
||
def user_message | ||
I18n.t("errors.#{self.id}") | ||
end | ||
end | ||
|
||
class Continue < HTTPStatusError; end # 100 | ||
class SwitchingProtocols < HTTPStatusError; end # 101 | ||
class OK < HTTPStatusError; end # 200 | ||
class Created < HTTPStatusError; end # 201 | ||
class Accepted < HTTPStatusError; end # 202 | ||
class NonAuthoritativeInformation < HTTPStatusError; end # 203 | ||
class NoContent < HTTPStatusError; end # 204 | ||
class ResetContent < HTTPStatusError; end # 205 | ||
class PartialContent < HTTPStatusError; end # 206 | ||
class MultipleChoices < HTTPStatusError; end # 300 | ||
class MovedPermanently < HTTPStatusError; end # 301 | ||
class Found < HTTPStatusError; end # 302 | ||
class SeeOther < HTTPStatusError; end # 303 | ||
class NotModified < HTTPStatusError; end # 304 | ||
class UseProxy < HTTPStatusError; end # 305 | ||
class TemporaryRedirect < HTTPStatusError; end # 307 | ||
class BadRequest < HTTPStatusError; end # 400 | ||
class Unauthorized < HTTPStatusError; end # 401 | ||
class PaymentRequired < HTTPStatusError; end # 402 | ||
class Forbidden < HTTPStatusError; end # 403 | ||
class NotFound < HTTPStatusError; end # 404 | ||
class MethodNotAllowed < HTTPStatusError; end # 405 | ||
class NotAcceptable < HTTPStatusError; end # 406 | ||
class ProxyAuthenticationRequired < HTTPStatusError; end # 407 | ||
class RequestTimeout < HTTPStatusError; end # 408 | ||
class Conflict < HTTPStatusError; end # 409 | ||
class Gone < HTTPStatusError; end # 410 | ||
class LengthRequired < HTTPStatusError; end # 411 | ||
class PreconditionFailed < HTTPStatusError; end # 412 | ||
class RequestEntityTooLarge < HTTPStatusError; end # 413 | ||
class RequestURITooLong < HTTPStatusError; end # 414 | ||
class UnsupportedMediaType < HTTPStatusError; end # 415 | ||
class RequestedRangeNotSatisfiable < HTTPStatusError; end # 416 | ||
class ExpectationFailed < HTTPStatusError; end # 417 | ||
class UnprocessableEntity < HTTPStatusError; end # 422 | ||
class TooManyRequests < HTTPStatusError; end # 429 | ||
class InternalServerError < HTTPStatusError; end # 500 | ||
class NotImplemented < HTTPStatusError; end # 501 | ||
class BadGateway < HTTPStatusError; end # 502 | ||
class ServiceUnavailable < HTTPStatusError; end # 503 | ||
class GatewayTimeout < HTTPStatusError; end # 504 | ||
def self.make_error(status, id) | ||
Class.new(Pliny::Errors::Error) do | ||
@error_class_id = id | ||
@error_class_status = status | ||
end | ||
end | ||
|
||
# Messages for nicer exceptions, from rfc2616 | ||
META = { | ||
Continue => [100, 'Continue'], | ||
SwitchingProtocols => [101, 'Switching protocols'], | ||
OK => [200, 'OK'], | ||
Created => [201, 'Created'], | ||
Accepted => [202, 'Accepted'], | ||
NonAuthoritativeInformation => [203, 'Non-authoritative information'], | ||
NoContent => [204, 'No content'], | ||
ResetContent => [205, 'Reset content'], | ||
PartialContent => [206, 'Partial content'], | ||
MultipleChoices => [300, 'Multiple choices'], | ||
MovedPermanently => [301, 'Moved permanently'], | ||
Found => [302, 'Found'], | ||
SeeOther => [303, 'See other'], | ||
NotModified => [304, 'Not modified'], | ||
UseProxy => [305, 'Use proxy'], | ||
TemporaryRedirect => [307, 'Temporary redirect'], | ||
BadRequest => [400, 'Bad request'], | ||
Unauthorized => [401, 'Unauthorized'], | ||
PaymentRequired => [402, 'Payment required'], | ||
Forbidden => [403, 'Forbidden'], | ||
NotFound => [404, 'Not found'], | ||
MethodNotAllowed => [405, 'Method not allowed'], | ||
NotAcceptable => [406, 'Not acceptable'], | ||
ProxyAuthenticationRequired => [407, 'Proxy authentication required'], | ||
RequestTimeout => [408, 'Request timeout'], | ||
Conflict => [409, 'Conflict'], | ||
Gone => [410, 'Gone'], | ||
LengthRequired => [411, 'Length required'], | ||
PreconditionFailed => [412, 'Precondition failed'], | ||
RequestEntityTooLarge => [413, 'Request entity too large'], | ||
RequestURITooLong => [414, 'Request-URI too long'], | ||
UnsupportedMediaType => [415, 'Unsupported media type'], | ||
RequestedRangeNotSatisfiable => [416, 'Requested range not satisfiable'], | ||
ExpectationFailed => [417, 'Expectation failed'], | ||
UnprocessableEntity => [422, 'Unprocessable entity'], | ||
TooManyRequests => [429, 'Too many requests'], | ||
InternalServerError => [500, 'Internal server error'], | ||
NotImplemented => [501, 'Not implemented'], | ||
BadGateway => [502, 'Bad gateway'], | ||
ServiceUnavailable => [503, 'Service unavailable'], | ||
GatewayTimeout => [504, 'Gateway timeout'], | ||
}.freeze | ||
BadRequest = make_error(400, :bad_request) | ||
Unauthorized = make_error(401, :unauthorized) | ||
PaymentRequired = make_error(402, :payment_required) | ||
Forbidden = make_error(403, :forbidden) | ||
NotFound = make_error(404, :not_found) | ||
MethodNotAllowed = make_error(405, :method_not_allowed) | ||
NotAcceptable = make_error(406, :not_acceptable) | ||
ProxyAuthenticationRequired = make_error(407, :proxy_authentication_required) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that removing all the non 4xx-5xx error classes is going to cause things to break in a few of my apps. To make matters a little worse, I suspect a few of them might be used for control flow. That said, I do think this is a good change and what I've been doing is BAD (tm). |
||
RequestTimeout = make_error(408, :request_timeout) | ||
Conflict = make_error(409, :conflict) | ||
Gone = make_error(410, :gone) | ||
LengthRequired = make_error(411, :length_required) | ||
PreconditionFailed = make_error(412, :precondition_failed) | ||
RequestEntityTooLarge = make_error(413, :request_entity_too_large) | ||
RequestURITooLong = make_error(414, :request_uri_too_long) | ||
UnsupportedMediaType = make_error(415, :unsupported_media_type) | ||
RequestedRangeNotSatisfiable = make_error(416, :requested_range_not_satisfiable) | ||
ExpectationFailed = make_error(417, :expectation_failed) | ||
UnprocessableEntity = make_error(422, :unprocessable_entity) | ||
TooManyRequests = make_error(429, :too_many_requests) | ||
InternalServerError = make_error(500, :internal_server_error) | ||
NotImplemented = make_error(501, :not_implemented) | ||
BadGateway = make_error(502, :bad_gateway) | ||
ServiceUnavailable = make_error(503, :service_unavailable) | ||
GatewayTimeout = make_error(504, :gateway_timeout) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
source "https://rubygems.org" | ||
ruby "2.2.3" | ||
|
||
gem "i18n", "~> 0.7" | ||
gem "multi_json" | ||
gem "oj" | ||
gem "pg" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
en: | ||
errors: | ||
bad_gateway: Bad gateway | ||
bad_request: Bad request | ||
conflict: Conflict | ||
expectation_failed: Expectation failed | ||
forbidden: Forbidden | ||
gateway_timeout: Gateway timed out | ||
gone: Gone | ||
internal_server_error: Internal server error | ||
length_required: Length required | ||
method_not_allowed: Method not allowed | ||
not_acceptable: Not acceptable | ||
not_found: Not found | ||
not_implemented: Not implemented | ||
payment_required: Payment required | ||
precondition_failed: Precondition failed | ||
proxy_authentication_required: Proxy authentication required | ||
request_entity_too_large: Request entity too large | ||
request_timeout: Request timed out | ||
request_uri_too_long: Requrest URI too long | ||
requested_range_not_satisfiable: Requested range not satisfiable | ||
service_unavailable: Service unavailable | ||
too_many_requests: Too many requests | ||
unauthorized: Unauthorized | ||
unprocessable_entity: Unprocessable entity | ||
unsupported_media_type: Unsupported media type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,32 @@ | ||
require "spec_helper" | ||
|
||
describe Pliny::Errors do | ||
it "includes a general error that requires an identifier" do | ||
e = Pliny::Errors::Error.new("General error.", :general_error) | ||
assert_equal "General error.", e.message | ||
assert_equal :general_error, e.id | ||
it "bundles classes to represent the different status codes" do | ||
error = Pliny::Errors::BadRequest.new | ||
assert_equal :bad_request, error.id | ||
assert_equal 400, error.status | ||
assert_equal "Bad request", error.user_message | ||
|
||
error = Pliny::Errors::InternalServerError.new | ||
assert_equal :internal_server_error, error.id | ||
assert_equal 500, error.status | ||
assert_equal "Internal server error", error.user_message | ||
end | ||
|
||
it "keeps the error id stored as the internal message" do | ||
error = Pliny::Errors::BadRequest.new | ||
assert_equal "bad_request", error.message | ||
end | ||
|
||
it "includes an HTTP error that will take generic parameters" do | ||
e = Pliny::Errors::HTTPStatusError.new( | ||
"Custom HTTP error.", :custom_http_error, 499) | ||
assert_equal "Custom HTTP error.", e.message | ||
assert_equal :custom_http_error, e.id | ||
assert_equal 499, e.status | ||
it "takes a custom id" do | ||
error = Pliny::Errors::BadRequest.new(:invalid_json) | ||
assert_equal :invalid_json, error.id | ||
assert_equal 400, error.status | ||
end | ||
|
||
it "includes pre-defined HTTP error templates" do | ||
e = Pliny::Errors::NotFound.new | ||
assert_equal "Not found.", e.message | ||
assert_equal :not_found, e.id | ||
assert_equal 404, e.status | ||
it "takes optional metadata" do | ||
metadata = { foo: "bar" } | ||
error = Pliny::Errors::BadRequest.new(:invalid_json, metadata: metadata) | ||
assert_equal metadata, error.metadata | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# configure i18n to use locales from the template app | ||
I18n.config.enforce_available_locales = true | ||
I18n.load_path += Dir[File.dirname(__FILE__) + "/../../lib/template/config/locales/*.{rb,yml}"] | ||
I18n.backend.load_translations |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking API change which will cause me some pain in a few places. Mostly where we are raising errors with custom messages, for example:
Again, I agree that using an error
id
andi18n
to sort this out is a better solution long term.