From b01a8f73fd26cff23342d73f0f8dcabe8d205207 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 2 Sep 2015 11:01:27 -0700 Subject: [PATCH 01/13] change Error interface, take options hash --- lib/pliny/errors.rb | 19 ++++++++++--------- spec/errors_spec.rb | 36 ++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index abfd7ca4..c452a498 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -9,21 +9,21 @@ def self.render(error) [error.status, headers, [MultiJson.encode(data)]] end - def initialize(message, id) - @id = id - super(message) + def initialize(options={}) + @id = options[:id] + super(options[:message]) end end class HTTPStatusError < Error attr_accessor :status - 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(options={}) + meta = Pliny::Errors::META[self.class] + options[:message] ||= "#{meta[1]}." + options[:id] ||= meta[1].downcase.tr(' ', '_').to_sym + @status = options[:status] || meta[0] + super(options) end end @@ -108,6 +108,7 @@ class GatewayTimeout < HTTPStatusError; end # 504 UnprocessableEntity => [422, 'Unprocessable entity'], TooManyRequests => [429, 'Too many requests'], InternalServerError => [500, 'Internal server error'], + HTTPStatusError => [500, 'Unspecified'], NotImplemented => [501, 'Not implemented'], BadGateway => [502, 'Bad gateway'], ServiceUnavailable => [503, 'Service unavailable'], diff --git a/spec/errors_spec.rb b/spec/errors_spec.rb index 2c115aa2..f4ec243e 100644 --- a/spec/errors_spec.rb +++ b/spec/errors_spec.rb @@ -1,24 +1,28 @@ 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 + describe Pliny::Errors::Error do + it "takes a message" do + e = Pliny::Errors::Error.new(message: "Fail.") + assert_equal "Fail.", e.message + end + it "takes an identifier" do + e = Pliny::Errors::Error.new(id: :fail) + assert_equal :fail, e.id + end 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 - end + describe Pliny::Errors::HTTPStatusError do + it "includes an HTTP error that will take generic parameters" do + e = Pliny::Errors::HTTPStatusError.new(status: 499) + assert_equal 499, e.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 "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 + end end end From 400b5c6a2d6553c5641bd214858fc6f94a9c8214 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 2 Sep 2015 11:03:38 -0700 Subject: [PATCH 02/13] support for error metadata --- lib/pliny/errors.rb | 5 +++-- spec/errors_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index c452a498..57a946ab 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -1,16 +1,17 @@ module Pliny module Errors class Error < StandardError - attr_accessor :id + attr_accessor :id, :metadata def self.render(error) headers = { "Content-Type" => "application/json; charset=utf-8" } - data = { id: error.id, message: error.message } + data = { id: error.id, message: error.message }.merge(error.metadata) [error.status, headers, [MultiJson.encode(data)]] end def initialize(options={}) @id = options[:id] + @metadata = options[:metadata] || {} super(options[:message]) end end diff --git a/spec/errors_spec.rb b/spec/errors_spec.rb index f4ec243e..8cd09269 100644 --- a/spec/errors_spec.rb +++ b/spec/errors_spec.rb @@ -10,6 +10,12 @@ e = Pliny::Errors::Error.new(id: :fail) assert_equal :fail, e.id end + + it "takes metadata" do + meta = { resource: "artists" } + e = Pliny::Errors::Error.new(metadata: meta) + assert_equal meta, e.metadata + end end describe Pliny::Errors::HTTPStatusError do From 0d343edeef717e7c305912a6782493994db8dd1f Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 16 Sep 2015 14:32:20 -0700 Subject: [PATCH 03/13] automatically turn NoMatchingRow exceptions in a 404 --- lib/template/lib/endpoints/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/template/lib/endpoints/base.rb b/lib/template/lib/endpoints/base.rb index acc1d1bd..0c9d80ec 100644 --- a/lib/template/lib/endpoints/base.rb +++ b/lib/template/lib/endpoints/base.rb @@ -16,7 +16,7 @@ class Base < Sinatra::Base also_reload "#{Config.root}/lib/**/*.rb" end - error Sinatra::NotFound do + error Sinatra::NotFound, Sequel::NoMatchingRow do raise Pliny::Errors::NotFound end end From 45fccbfeb526b9307a05d772af5c7d9f87bcb14f Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Mon, 28 Sep 2015 11:55:19 -0700 Subject: [PATCH 04/13] keep HTTPStatusError as an abstract class it's not supposed to be instantiated directly unless you pass all the required error attributes --- lib/pliny/errors.rb | 1 - spec/errors_spec.rb | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index 57a946ab..7f651b5f 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -109,7 +109,6 @@ class GatewayTimeout < HTTPStatusError; end # 504 UnprocessableEntity => [422, 'Unprocessable entity'], TooManyRequests => [429, 'Too many requests'], InternalServerError => [500, 'Internal server error'], - HTTPStatusError => [500, 'Unspecified'], NotImplemented => [501, 'Not implemented'], BadGateway => [502, 'Bad gateway'], ServiceUnavailable => [503, 'Service unavailable'], diff --git a/spec/errors_spec.rb b/spec/errors_spec.rb index 8cd09269..45f14261 100644 --- a/spec/errors_spec.rb +++ b/spec/errors_spec.rb @@ -20,8 +20,10 @@ describe Pliny::Errors::HTTPStatusError do it "includes an HTTP error that will take generic parameters" do - e = Pliny::Errors::HTTPStatusError.new(status: 499) + e = Pliny::Errors::HTTPStatusError.new(message: "error", id: :foo, status: 499) + assert_equal :foo, e.id assert_equal 499, e.status + assert_equal "error", e.message end it "includes pre-defined HTTP error templates" do From 3bb5dcd488da6fcdd6348160d6558290fceef8f9 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Mon, 28 Sep 2015 12:01:27 -0700 Subject: [PATCH 05/13] change error interface, always take message as the first param better conforming to regular ruby exception classes --- lib/pliny/errors.rb | 16 ++++++++-------- spec/errors_spec.rb | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index 7f651b5f..63205e46 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -9,22 +9,22 @@ def self.render(error) [error.status, headers, [MultiJson.encode(data)]] end - def initialize(options={}) - @id = options[:id] - @metadata = options[:metadata] || {} - super(options[:message]) + def initialize(message, id: nil, metadata: {}) + @id = id + @metadata = metadata + super(message) end end class HTTPStatusError < Error attr_accessor :status - def initialize(options={}) + def initialize(message=nil, options={}) meta = Pliny::Errors::META[self.class] - options[:message] ||= "#{meta[1]}." + message ||= "#{meta[1]}." options[:id] ||= meta[1].downcase.tr(' ', '_').to_sym - @status = options[:status] || meta[0] - super(options) + @status = options.delete(:status) || meta[0] + super(message, options) end end diff --git a/spec/errors_spec.rb b/spec/errors_spec.rb index 45f14261..c1db97ce 100644 --- a/spec/errors_spec.rb +++ b/spec/errors_spec.rb @@ -3,24 +3,24 @@ describe Pliny::Errors do describe Pliny::Errors::Error do it "takes a message" do - e = Pliny::Errors::Error.new(message: "Fail.") + e = Pliny::Errors::Error.new("Fail.") assert_equal "Fail.", e.message end it "takes an identifier" do - e = Pliny::Errors::Error.new(id: :fail) + e = Pliny::Errors::Error.new(nil, id: :fail) assert_equal :fail, e.id end it "takes metadata" do meta = { resource: "artists" } - e = Pliny::Errors::Error.new(metadata: meta) + e = Pliny::Errors::Error.new(nil, metadata: meta) assert_equal meta, e.metadata end end describe Pliny::Errors::HTTPStatusError do it "includes an HTTP error that will take generic parameters" do - e = Pliny::Errors::HTTPStatusError.new(message: "error", id: :foo, status: 499) + e = Pliny::Errors::HTTPStatusError.new("error", id: :foo, status: 499) assert_equal :foo, e.id assert_equal 499, e.status assert_equal "error", e.message From bd93fca7e1618127b3a85b5914a83c34639afc45 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 19:16:48 -0800 Subject: [PATCH 06/13] remove errors outside the 40x-50x range --- lib/pliny/errors.rb | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index 63205e46..dfe29edc 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -28,22 +28,6 @@ def initialize(message=nil, options={}) 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 @@ -72,22 +56,6 @@ class GatewayTimeout < HTTPStatusError; end # 504 # 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'], From 57779eff42515ae6461351c958db14335315ab80 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 19:26:26 -0800 Subject: [PATCH 07/13] add I18n to the template app --- lib/template/Gemfile | 1 + lib/template/config/locales/en.yml | 1 + lib/template/lib/initializer.rb | 7 +++++++ 3 files changed, 9 insertions(+) create mode 100644 lib/template/config/locales/en.yml diff --git a/lib/template/Gemfile b/lib/template/Gemfile index 0ad58177..90b38db7 100644 --- a/lib/template/Gemfile +++ b/lib/template/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" ruby "2.2.3" +gem "i18n", "~> 0.7" gem "multi_json" gem "oj" gem "pg" diff --git a/lib/template/config/locales/en.yml b/lib/template/config/locales/en.yml new file mode 100644 index 00000000..63f1c3e9 --- /dev/null +++ b/lib/template/config/locales/en.yml @@ -0,0 +1 @@ +en: diff --git a/lib/template/lib/initializer.rb b/lib/template/lib/initializer.rb index 02e6fd57..07396334 100644 --- a/lib/template/lib/initializer.rb +++ b/lib/template/lib/initializer.rb @@ -1,6 +1,7 @@ module Initializer def self.run require_config + load_locales require_lib require_initializers require_models @@ -10,6 +11,12 @@ def self.require_config require_relative "../config/config" end + def self.load_locales + I18n.config.enforce_available_locales = true + I18n.load_path += Dir[Config.root + "/config/locales/*.{rb,yml}"] + I18n.backend.load_translations + end + def self.require_lib require! %w( lib/endpoints/base From 4a83438262f0d7fb44414a54880d85be6adee4b8 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 20:13:36 -0800 Subject: [PATCH 08/13] configure pliny specs to use locales from the template --- spec/support/i18n.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 spec/support/i18n.rb diff --git a/spec/support/i18n.rb b/spec/support/i18n.rb new file mode 100644 index 00000000..9a31cdd2 --- /dev/null +++ b/spec/support/i18n.rb @@ -0,0 +1,4 @@ +# configure i18n to use locales from the template app +I18n.config.enforce_available_locales = true +I18n.load_path += Dir["./lib/template/config/locales/*.{rb,yml}"] +I18n.backend.load_translations From 22228510dc582267c05f8b9cffcea699fcedec39 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 20:14:24 -0800 Subject: [PATCH 09/13] change error interface: read user message from locales --- lib/pliny/errors.rb | 117 ++++++++++---------------- lib/template/config/locales/en.yml | 26 ++++++ pliny.gemspec | 1 + spec/errors_spec.rb | 50 +++++------ spec/middleware/rescue_errors_spec.rb | 4 +- 5 files changed, 98 insertions(+), 100 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index dfe29edc..d371b6b8 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -1,86 +1,61 @@ +require "i18n" + module Pliny module Errors class Error < StandardError - attr_accessor :id, :metadata - def self.render(error) - headers = { "Content-Type" => "application/json; charset=utf-8" } - data = { id: error.id, message: error.message }.merge(error.metadata) - [error.status, headers, [MultiJson.encode(data)]] + class << self + attr_accessor :error_class_id, :error_class_status + + 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 - def initialize(message, id: nil, metadata: {}) - @id = id + attr_accessor :id, :status, :metadata, :user_message + + def initialize(id=nil, metadata: {}) + @id = (id || self.class.error_class_id).to_sym + @status = self.class.error_class_status @metadata = metadata - super(message) + @user_message = I18n.t("errors.#{@id}") + super(@id.to_s) end end - class HTTPStatusError < Error - attr_accessor :status - - def initialize(message=nil, options={}) - meta = Pliny::Errors::META[self.class] - message ||= "#{meta[1]}." - options[:id] ||= meta[1].downcase.tr(' ', '_').to_sym - @status = options.delete(:status) || meta[0] - super(message, options) + def self.MakeError(status, id) + Class.new(Pliny::Errors::Error) do + @error_class_id = id + @error_class_status = status end end - 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 - - # Messages for nicer exceptions, from rfc2616 - META = { - 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 = MakeError(400, :bad_request) + Unauthorized = MakeError(401, :unauthorized) + PaymentRequired = MakeError(402, :payment_required) + Forbidden = MakeError(403, :forbidden) + NotFound = MakeError(404, :not_found) + MethodNotAllowed = MakeError(405, :method_not_allowed) + NotAcceptable = MakeError(406, :not_acceptable) + ProxyAuthenticationRequired = MakeError(407, :proxy_authentication_required) + RequestTimeout = MakeError(408, :request_timeout) + Conflict = MakeError(409, :conflict) + Gone = MakeError(410, :gone) + LengthRequired = MakeError(411, :length_required) + PreconditionFailed = MakeError(412, :precondition_failed) + RequestEntityTooLarge = MakeError(413, :request_entity_too_large) + RequestURITooLong = MakeError(414, :request_uri_too_long) + UnsupportedMediaType = MakeError(415, :unsupported_media_type) + RequestedRangeNotSatisfiable = MakeError(416, :requested_range_not_satisfiable) + ExpectationFailed = MakeError(417, :expectation_failed) + UnprocessableEntity = MakeError(422, :unprocessable_entity) + TooManyRequests = MakeError(429, :too_many_requests) + InternalServerError = MakeError(500, :internal_server_error) + NotImplemented = MakeError(501, :not_implemented) + BadGateway = MakeError(502, :bad_gateway) + ServiceUnavailable = MakeError(503, :service_unavailable) + GatewayTimeout = MakeError(504, :gateway_timeout) end end diff --git a/lib/template/config/locales/en.yml b/lib/template/config/locales/en.yml index 63f1c3e9..6c93e45f 100644 --- a/lib/template/config/locales/en.yml +++ b/lib/template/config/locales/en.yml @@ -1 +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 + proxy_authentication_required: Proxy authentication required + precondition_failed: Precondition failed + 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 diff --git a/pliny.gemspec b/pliny.gemspec index 96475c7c..66ba138d 100644 --- a/pliny.gemspec +++ b/pliny.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |gem| gem.files = %x{ git ls-files }.split("\n").select { |d| d =~ %r{^(License|README|bin/|data/|ext/|lib/|spec/|test/)} } gem.add_dependency "activesupport", "~> 4.1", ">= 4.1.0" + gem.add_dependency "i18n", "~> 0.7", ">= 0.7" gem.add_dependency "multi_json", "~> 1.9", ">= 1.9.3" gem.add_dependency "prmd", "~> 0.7.0" gem.add_dependency "sinatra", "~> 1.4", ">= 1.4.5" diff --git a/spec/errors_spec.rb b/spec/errors_spec.rb index c1db97ce..4c910a9c 100644 --- a/spec/errors_spec.rb +++ b/spec/errors_spec.rb @@ -1,36 +1,32 @@ require "spec_helper" describe Pliny::Errors do - describe Pliny::Errors::Error do - it "takes a message" do - e = Pliny::Errors::Error.new("Fail.") - assert_equal "Fail.", e.message - end - it "takes an identifier" do - e = Pliny::Errors::Error.new(nil, id: :fail) - assert_equal :fail, e.id - end + 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 - it "takes metadata" do - meta = { resource: "artists" } - e = Pliny::Errors::Error.new(nil, metadata: meta) - assert_equal meta, e.metadata - end + 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 - describe Pliny::Errors::HTTPStatusError do - it "includes an HTTP error that will take generic parameters" do - e = Pliny::Errors::HTTPStatusError.new("error", id: :foo, status: 499) - assert_equal :foo, e.id - assert_equal 499, e.status - assert_equal "error", e.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 "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 - end + it "takes optional metadata" do + metadata = { foo: "bar" } + error = Pliny::Errors::BadRequest.new(:invalid_json, metadata: metadata) + assert_equal metadata, error.metadata end end diff --git a/spec/middleware/rescue_errors_spec.rb b/spec/middleware/rescue_errors_spec.rb index 85494540..c0a2cda3 100644 --- a/spec/middleware/rescue_errors_spec.rb +++ b/spec/middleware/rescue_errors_spec.rb @@ -23,7 +23,7 @@ def app assert_equal 503, last_response.status error_json = MultiJson.decode(last_response.body) assert_equal "service_unavailable", error_json["id"] - assert_equal "Service unavailable.", error_json["message"] + assert_equal "Service unavailable", error_json["message"] end it "intercepts exceptions and renders" do @@ -32,7 +32,7 @@ def app assert_equal 500, last_response.status error_json = MultiJson.decode(last_response.body) assert_equal "internal_server_error", error_json["id"] - assert_equal "Internal server error.", error_json["message"] + assert_equal "Internal server error", error_json["message"] end it "raises given the raise option" do From a3332727bb52038fd359ec2ad7f92928f835fe79 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 20:18:22 -0800 Subject: [PATCH 10/13] fix path --- spec/support/i18n.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/i18n.rb b/spec/support/i18n.rb index 9a31cdd2..c2bfc766 100644 --- a/spec/support/i18n.rb +++ b/spec/support/i18n.rb @@ -1,4 +1,4 @@ # configure i18n to use locales from the template app I18n.config.enforce_available_locales = true -I18n.load_path += Dir["./lib/template/config/locales/*.{rb,yml}"] +I18n.load_path += Dir[File.dirname(__FILE__) + "/../../lib/template/config/locales/*.{rb,yml}"] I18n.backend.load_translations From 74ab13b81d9c6345788ce5badafd34ce94f54a65 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Wed, 6 Jan 2016 20:20:24 -0800 Subject: [PATCH 11/13] sort --- lib/template/config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/template/config/locales/en.yml b/lib/template/config/locales/en.yml index 6c93e45f..373de593 100644 --- a/lib/template/config/locales/en.yml +++ b/lib/template/config/locales/en.yml @@ -14,8 +14,8 @@ en: not_found: Not found not_implemented: Not implemented payment_required: Payment required - proxy_authentication_required: Proxy authentication 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 From 85186395c60bc5e5d97da082b95a5e30ed467d1b Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Thu, 7 Jan 2016 16:54:45 -0800 Subject: [PATCH 12/13] oops, make clear this is a method --- lib/pliny/errors.rb | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index d371b6b8..2f4c7bed 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -25,37 +25,37 @@ def initialize(id=nil, metadata: {}) end end - def self.MakeError(status, id) + def self.make_error(status, id) Class.new(Pliny::Errors::Error) do @error_class_id = id @error_class_status = status end end - BadRequest = MakeError(400, :bad_request) - Unauthorized = MakeError(401, :unauthorized) - PaymentRequired = MakeError(402, :payment_required) - Forbidden = MakeError(403, :forbidden) - NotFound = MakeError(404, :not_found) - MethodNotAllowed = MakeError(405, :method_not_allowed) - NotAcceptable = MakeError(406, :not_acceptable) - ProxyAuthenticationRequired = MakeError(407, :proxy_authentication_required) - RequestTimeout = MakeError(408, :request_timeout) - Conflict = MakeError(409, :conflict) - Gone = MakeError(410, :gone) - LengthRequired = MakeError(411, :length_required) - PreconditionFailed = MakeError(412, :precondition_failed) - RequestEntityTooLarge = MakeError(413, :request_entity_too_large) - RequestURITooLong = MakeError(414, :request_uri_too_long) - UnsupportedMediaType = MakeError(415, :unsupported_media_type) - RequestedRangeNotSatisfiable = MakeError(416, :requested_range_not_satisfiable) - ExpectationFailed = MakeError(417, :expectation_failed) - UnprocessableEntity = MakeError(422, :unprocessable_entity) - TooManyRequests = MakeError(429, :too_many_requests) - InternalServerError = MakeError(500, :internal_server_error) - NotImplemented = MakeError(501, :not_implemented) - BadGateway = MakeError(502, :bad_gateway) - ServiceUnavailable = MakeError(503, :service_unavailable) - GatewayTimeout = MakeError(504, :gateway_timeout) + 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) + 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 From b9ddc6835cc42146175b8ff751454601784fc823 Mon Sep 17 00:00:00 2001 From: Pedro Belo Date: Thu, 7 Jan 2016 16:58:16 -0800 Subject: [PATCH 13/13] make Error#user_message lazy facilitate error messages in other languages --- lib/pliny/errors.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pliny/errors.rb b/lib/pliny/errors.rb index 2f4c7bed..b2169795 100644 --- a/lib/pliny/errors.rb +++ b/lib/pliny/errors.rb @@ -14,15 +14,18 @@ def render(error) end end - attr_accessor :id, :status, :metadata, :user_message + attr_accessor :id, :status, :metadata def initialize(id=nil, metadata: {}) @id = (id || self.class.error_class_id).to_sym @status = self.class.error_class_status @metadata = metadata - @user_message = I18n.t("errors.#{@id}") super(@id.to_s) end + + def user_message + I18n.t("errors.#{self.id}") + end end def self.make_error(status, id)