restful_error 1.0.3 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4744dd451535b3d85e7ce8b47a76abee9fa33970a72d6748e553f89bb830e9e5
4
- data.tar.gz: 6cfc09fbf86294c2453a3effeb1537f83a9c8fef8bed444f24916e3f45c2d470
3
+ metadata.gz: ed208c312dae52e3f02f31f16ee66ebe34b6a98f02195c6dae1178c8ae638aab
4
+ data.tar.gz: f28848072ce6af1819ccefce94b474c11d5eb92543698f5fb4478d80984cc6a4
5
5
  SHA512:
6
- metadata.gz: 6d339f3e39861bfdc9bf64fc983d9b03e15708f4323d3f9987da1ae70c7bc973b8cfecb7cc7568a1078957f7eef6ba9a8e1f5c533f50a734b2fcc0005f5c2170
7
- data.tar.gz: cbbf4ee06b69b396f79cd351c2659458422090d927af7584f31eab4e8e4fe1bcc4019cb21b90f2695ddbfe2683199afd412a4f6bad11ddf61365cb182a26f55f
6
+ metadata.gz: 833c5516e492850176e4321769fd2eb4060036b2948c9a9bcbbe40bace7c082bb4f473879713c02ba55a204509214bd57f50963128a384e16db23586c710519a
7
+ data.tar.gz: ed25e5a04a3ddb4761449d034b2457097c7d7aa2a4a684dfcaa9a4dccd211c9baecd7d54e1dc3460fac03501eb8a49c5ed32751a6bc96f6353336e7fa2f67e4e
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2025 Ohkubo KOHEI
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ [![Gem Version](https://badge.fury.io/rb/restful_error.svg)](https://badge.fury.io/rb/restful_error)
2
+
3
+ # RestfulError
4
+
5
+ Define your error with status code. Raise it and you will get formatted response with i18nized message.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'restful_error'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ ## Usage
18
+
19
+ ### Pure ruby (without Rails)
20
+ #### Predefined errors
21
+ ```ruby
22
+ ex = RestfulError[404].new
23
+
24
+ StandardError === ex # => true # because inherit from StandardError
25
+ RestfulError::BaseError === ex # => true
26
+
27
+ RestfulError[404] == RestfulError::NotFound # => true # same class
28
+
29
+ ex.status_data # returns Data about status code
30
+ # => #<data RestfulError::Status
31
+ # code=404,
32
+ # reason_phrase="Not Found",
33
+ # symbol=:not_found,
34
+ # const_name="NotFound">
35
+ ex.status_data.code # => 404
36
+ ```
37
+
38
+ #### Custom error by subclassing
39
+ ```ruby
40
+ class ::NoSession < RestfulError[404]; end
41
+ # or
42
+ class ::NoSession < RestfulError::NotFound; end
43
+ ```
44
+
45
+ #### Custom error with http_status
46
+ ```ruby
47
+ # define http_status and include RestfulError::Helper
48
+ class User::PermissionError < StandardError
49
+ include RestfulError::Helper
50
+ def http_status = :unauthorized # or 401
51
+ end
52
+ User::PermissionError.new.status_data.reason_phrase # => "Unauthorized"
53
+ ```
54
+
55
+ ### With I18n
56
+ `#response_message` returns i18nized message.
57
+ ```yaml
58
+ ja:
59
+ restful_error:
60
+ unauthorized: ログインが必要です
61
+ not_found: ページが存在しません
62
+ user/permission_error: 権限がありません
63
+ ```
64
+ ```ruby
65
+ # lookup class name first, then status symbol
66
+ User::PermissionError.new.response_message # => "権限がありません"
67
+ AnotherPermissionError.new.response_message # => "ログインが必要です"
68
+ ```
69
+
70
+ ### With Rails
71
+ `config.exceptions_app` will automatically set to RestfulError::ExceptionsApp.
72
+
73
+ If you want to disable it, you have two options.
74
+ - `config.restful_error.exceptions_app.enable = false` (will not set exceptions_app)
75
+ - `config.exceptions_app = ActionDispatch::PublicExceptions.new(Rails.public_path)` (set Rails default explicitly, or set your own)
76
+
77
+ #### Raise me in request handling
78
+ ```ruby
79
+ class PostsController < ApplicationController
80
+ before_action do
81
+ raise RestfulError[401] unless current_user
82
+ # or
83
+ raise RestfulError::Unauthorized unless current_user
84
+ end
85
+ end
86
+ ```
87
+
88
+
89
+ #### Render response
90
+ Default view files are in https://github.com/kuboon/restful_error/tree/main/app/views/restful_error
91
+
92
+ `html`, `json` and `xml` are supported.
93
+
94
+ You can override them by creating view file `show.{format}.{handler}` under your `app/views/restful_error/` directory.
95
+
96
+ `@status` `@status_code` `@reason_phrase` `@response_message` are available in the view.
97
+
98
+ If you have `layouts/restful_error.*.*`, or `layouts/application.*.*`, it will be used as layout. This is done by inheriting `::ApplicationController`.
99
+
100
+ To change superclass,
101
+ set `config.restful_error.exceptions_app.inherit_from = 'AnotherBaseController'`
102
+
103
+ #### Library defined error
104
+ You can assign status code to error classes which are not yours. (This is Rails standard)
105
+
106
+ ```ruby
107
+ config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :unauthorized # or 401
108
+ ```
109
+
110
+ RestfulError will use this configuration to lookup status code and
111
+ `@response_message` will be set.
112
+
113
+ ```yaml
114
+ ja:
115
+ restful_error:
116
+ pundit/not_authorized_error: アクセス権限がありません
117
+ active_record/record_not_found: 要求されたリソースが存在しません
118
+ ```
119
+
120
+ ## Why `response_message`, not `message`?
121
+ `StandardError#message` is used for debugging purpose, not intended to be shown to users.
122
+ Rails default behavior does not show `message` in production environment. So I decided to use `response_message` instead.
123
+
124
+ You can `def response_message` or set `@resposne_message` in your error class to build dynamic message.
125
+
126
+
127
+ ## Contributing
128
+
129
+ 1. Fork it ( https://github.com/kuboon/restful_error/fork )
130
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
131
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
132
+ 4. Push to the branch (`git push origin my-new-feature`)
133
+ 5. Create a new Pull Request
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <%= yield %>
4
+ </body>
5
+ </html>
@@ -0,0 +1,4 @@
1
+ <div class="'page_header">
2
+ <h1><%= @status_code %> <%= @reason_phrase %></h1>
3
+ <p><%= @response_message %></p>
4
+ </div>
@@ -0,0 +1,5 @@
1
+ raw JSON.generate(
2
+ status_code: @status_code,
3
+ reason_phrase: @reason_phrase,
4
+ response_message: @response_message
5
+ )
@@ -0,0 +1,5 @@
1
+ <error>
2
+ <status_code><%= @status_code %></status_code>
3
+ <reason_phrase><%= @reason_phrase %></reason_phrase>
4
+ <response_message><%= @response_message %></response_message>
5
+ </error>
@@ -0,0 +1,14 @@
1
+ en:
2
+ restful_error:
3
+ active_record/record_not_found: Requested resource is not found
4
+ action_controller/routing_error: Requested resource is not found
5
+ can_can/unauthorized: Unauthorized
6
+ bad_request: Bad request #400
7
+ unauthorized: Unauthorized #401
8
+ forbidden: Forbidden #403
9
+ not_found: Page not found #404
10
+ method_not_allowed: Requested HTTP Method is not allowd #405
11
+ gone: Gone #410
12
+ unprocessable_content: Content is readable but unprocessable #422
13
+ internal_server_error: Internal server error #500
14
+ service_unavailable: Service unavailable #503
@@ -0,0 +1,13 @@
1
+ ja:
2
+ restful_error:
3
+ active_record/record_not_found: 要求されたリソースが存在しません
4
+ can_can/unauthorized: 権限がありません
5
+ bad_request: 不正なリクエスト #400
6
+ unauthorized: 権限がありません #401
7
+ forbidden: そのURLへのアクセスは禁止されています #403
8
+ not_found: そのURLは存在しません #404
9
+ method_not_allowed: その HTTP method は使用できません #405
10
+ gone: 要求されたリソースは消滅しました #410
11
+ unprocessable_content: コンテンツを読みましたが処理できません #422
12
+ internal_server_error: サーバエラーです #500
13
+ service_unavailable: サービスが一時的に利用不可能になっています。しばらく時間をおいて、再度ご確認願います。 #503
@@ -0,0 +1,11 @@
1
+ require "abstract_controller"
2
+ require "action_controller/metal"
3
+
4
+ module RestfulError
5
+ class ApplicationController < ::ActionController::Metal
6
+ abstract!
7
+ include AbstractController::Rendering
8
+ include ActionView::Layouts
9
+ include ActionController::Rendering
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ require "abstract_controller"
2
+ require "action_controller/metal"
3
+
4
+ module RestfulError
5
+ class ExceptionsApp
6
+ Config = Struct.new(:enable, :inherit_from, :fallback)
7
+ def self.config
8
+ @config ||= Config.new.tap do |config|
9
+ config.enable = true
10
+ config.inherit_from = "::ApplicationController"
11
+ end
12
+ end
13
+
14
+ def initialize(config = self.class.config)
15
+ @config = config
16
+ end
17
+ def call(env)
18
+ app.call(env)
19
+ rescue Exception => _e
20
+ raise unless @config.fallback
21
+ @config.fallback.call(env)
22
+ end
23
+
24
+ private
25
+
26
+ def app
27
+ @app ||= begin
28
+ # To use "layouts/application" we need inherit from ::ApplicationController
29
+ # It is not defined at config time, so we need to load it here
30
+ if @config.inherit_from && Object.const_defined?(@config.inherit_from)
31
+ inherit_from = @config.inherit_from.constantize
32
+ else
33
+ inherit_from = RestfulError::ApplicationController
34
+ end
35
+ const_set_without_warn(RestfulError, "SuperController", inherit_from)
36
+ ExceptionsController.action(:show)
37
+ end
38
+ end
39
+ def const_set_without_warn(klass, const_name, value)
40
+ klass.send(:remove_const, const_name) if klass.const_defined?(const_name)
41
+ klass.const_set(const_name, value)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ module RestfulError
2
+ class ExceptionsController < SuperController
3
+ def self.controller_path = "restful_error"
4
+ append_view_path File.join(File.dirname(__FILE__), "../../app/views")
5
+
6
+ layout nil
7
+
8
+ def show
9
+ @exception = request.env["action_dispatch.exception"]
10
+ code = request.path_info[1..].to_i
11
+ status = RestfulError.build_status_from_symbol_or_code(code)
12
+ @status_code = status.code
13
+ @reason_phrase = status.reason_phrase
14
+ @response_message = @exception.try(:response_message) || RestfulError.localized_phrase(@exception.class.name, status) || nil
15
+
16
+ render status: status.code, formats: request.format.symbol
17
+ end
18
+ end
19
+ end
@@ -1,27 +1,15 @@
1
- require "abstract_controller"
2
- require "action_controller/metal"
1
+ require "restful_error/exceptions_app"
3
2
 
4
3
  module RestfulError
5
- class ExceptionsController < ::ActionController::Metal
6
- include AbstractController::Rendering
7
- include ActionView::Layouts
8
-
9
- append_view_path File.join(File.dirname(__FILE__), "../../app/views")
10
-
11
- def show
12
- @exception = request.env["action_dispatch.exception"]
13
- code = request.path_info[1..].to_i
14
- status = RestfulError.build_status_from_symbol_or_code(code)
15
- @status_code = status.code
16
- @reason_phrase = status.reason_phrase
17
- @response_message = @exception.try(:response_message) || RestfulError.localized_phrase(@exception.class.name, status) || nil
18
-
19
- self.status = status.code
20
- render "restful_error/show", formats: request.format.symbol
4
+ class Railtie < Rails::Railtie
5
+ config.restful_error = ActiveSupport::OrderedOptions.new
6
+ config.restful_error.exceptions_app = RestfulError::ExceptionsApp.config
7
+
8
+ initializer "restful_error.exceptions_app", before: :build_middleware_stack do |app|
9
+ if app.config.restful_error.exceptions_app.enable
10
+ app.config.restful_error.exceptions_app.fallback ||= ActionDispatch::PublicExceptions.new(Rails.public_path)
11
+ app.config.exceptions_app ||= RestfulError::ExceptionsApp.new
12
+ end
21
13
  end
22
14
  end
23
-
24
- def self.exceptions_app
25
- ExceptionsController.action(:show)
26
- end
27
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RestfulError
4
- VERSION = "1.0.3"
4
+ VERSION = "1.0.5"
5
5
  end
data/lib/restful_error.rb CHANGED
@@ -1,29 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack/utils"
4
- require "restful_error/railtie" if defined? ActionController
5
4
  require "restful_error/status"
6
5
  require "restful_error/version"
6
+ require "restful_error/railtie" if defined? Rails::Railtie
7
7
 
8
8
  module RestfulError
9
+ autoload :ExceptionsApp, "restful_error/exceptions_app"
10
+ autoload :ApplicationController, "restful_error/application_controller"
11
+ autoload :ExceptionsController, "restful_error/exceptions_controller"
12
+
9
13
  module Helper
10
- def restful
11
- @restful ||= begin
12
- raise NotImplementedError, "http_status must be implemented by including class" unless respond_to?(:http_status)
13
- RestfulError.build_status_from_symbol_or_code(http_status)
14
- end
14
+ def status_data
15
+ @status_data ||= RestfulError.build_status_from_symbol_or_code(http_status)
15
16
  end
16
17
  def response_message
17
18
  return @response_message unless @response_message.nil?
18
- @response_message = RestfulError.localized_phrase(self.class.name, restful)
19
+ @response_message = RestfulError.localized_phrase(self.class.name, status_data)
19
20
  end
20
21
  end
21
22
 
22
23
  class BaseError < StandardError
23
24
  include RestfulError::Helper
24
- def initialize(message = nil)
25
- @response_message = message
26
- super
25
+ def http_status
26
+ raise NotImplementedError, "http_status must be implemented"
27
27
  end
28
28
  end
29
29
 
@@ -58,7 +58,7 @@ module RestfulError
58
58
  def build_error_class_for(status)
59
59
  klass = Class.new(BaseError) do
60
60
  define_method(:http_status) { status.code }
61
- define_method(:restful) { status }
61
+ define_method(:status_data) { status }
62
62
  end
63
63
  const_set(status.const_name, klass)
64
64
  if defined? ActionDispatch::ExceptionWrapper
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restful_error
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - kuboon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
11
+ date: 2025-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -38,14 +38,22 @@ executables: []
38
38
  extensions: []
39
39
  extra_rdoc_files: []
40
40
  files:
41
+ - LICENSE.txt
42
+ - README.md
43
+ - app/views/layouts/application.html.erb
44
+ - app/views/restful_error/show.html.erb
45
+ - app/views/restful_error/show.json.ruby
46
+ - app/views/restful_error/show.xml.erb
47
+ - config/locales/en.restful_error.yml
48
+ - config/locales/ja.restful_error.yml
41
49
  - lib/restful_error.rb
50
+ - lib/restful_error/application_controller.rb
51
+ - lib/restful_error/exceptions_app.rb
52
+ - lib/restful_error/exceptions_controller.rb
42
53
  - lib/restful_error/inflector.rb
43
54
  - lib/restful_error/railtie.rb
44
55
  - lib/restful_error/status.rb
45
56
  - lib/restful_error/version.rb
46
- - spec/restful_error_spec.rb
47
- - spec/spec_helper.rb
48
- - spec/with_rails/exceptions_app_spec.rb
49
57
  homepage: https://github.com/kuboon/restful_error
50
58
  licenses:
51
59
  - MIT
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe RestfulError do
6
- describe "RestfullError[404]" do
7
- subject { described_class[404].new }
8
-
9
- it do
10
- expect(subject.http_status).to eq 404
11
- expect(subject.restful.reason_phrase).to eq "Not Found"
12
- end
13
- end
14
-
15
- describe "RestfullError[:bad_request]" do
16
- subject { described_class[:bad_request].new }
17
-
18
- it do
19
- expect(subject.http_status).to eq 400
20
- expect(subject.restful.reason_phrase).to eq "Bad Request"
21
- end
22
- end
23
-
24
- describe described_class::Forbidden do
25
- subject { described_class.new }
26
-
27
- it do
28
- expect(subject.http_status).to eq 403
29
- expect(subject.restful.reason_phrase).to eq "Forbidden"
30
- end
31
- end
32
-
33
- describe "custom class" do
34
- subject { klass.new }
35
-
36
- let(:klass) do
37
- Class.new(StandardError) do
38
- include RestfulError::Helper
39
- def http_status = 404
40
- end
41
- end
42
-
43
- it do
44
- expect(subject.restful.symbol).to eq :not_found
45
- end
46
- end
47
- end
data/spec/spec_helper.rb DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
-
5
- require "debug"
6
- require "restful_error"
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_controller"
4
- require "i18n"
5
- require "spec_helper"
6
- require "restful_error/railtie" # require again for run after restful_error_spec
7
-
8
- RSpec.describe RestfulError::ExceptionsController do
9
- include Rack::Test::Methods
10
- def app = RestfulError.exceptions_app
11
-
12
- shared_context "json" do
13
- let(:request) { get "/#{status_code}", {}, 'HTTP_ACCEPT' => 'application/json' }
14
- let(:json) { request; JSON.parse(last_response.body) }
15
- end
16
-
17
- before do
18
- env "action_dispatch.exception", exception
19
- end
20
- describe RestfulError[404] do
21
- let(:status_code) { 404 }
22
- include_context "json" do
23
- context 'default message' do
24
- let(:exception) { described_class.new }
25
- it do
26
- expect(json).to eq({status_code: 404, reason_phrase: "Not Found", response_message: 'Page not found'}.stringify_keys)
27
- expect(last_response.status).to eq status_code
28
- end
29
- end
30
- context 'custom message' do
31
- let(:exception) { described_class.new("custom message") }
32
- it do
33
- expect(json).to eq({status_code:, reason_phrase: "Not Found", response_message: "custom message"}.stringify_keys)
34
- end
35
- end
36
- end
37
- end
38
- context ActionController::RoutingError do
39
- let(:exception) { described_class.new("no route") }
40
- let(:status_code) { 404 }
41
- include_context "json" do
42
- it do
43
- expect(json).to eq({status_code:, reason_phrase: "Not Found", response_message: 'Requested resource is not found'}.stringify_keys)
44
- expect(last_response.status).to eq status_code
45
- end
46
- end
47
- end
48
- end