Present a more informative missing action error (#1232)

When a route contains a key for a missing action, raise a (renamed) `Hanami::Routes::MissingActionError` with a full message like the following:

```
Could not find action with key "actions.missing.action" in Admin::Slice

To fix this, define the action class Admin::Actions::Missing::Action in /full/path/to/slices/admin/actions/missing/action.rb
```

Include the slice name being used to resolve the action, as well as the expected action class name and file so that the user can correct the error.

Move these routing-related errors to the `Hanami::Routes` namespace too, since that namespace is already expected by the user, since it is the superclass of their routes definition files.

Lastly, rename the error from UnknownActionError to MissingActionError, to make it clearer that we _know_ the action, but its component/source file is missing.
This commit is contained in:
Tim Riley 2022-11-01 15:32:55 +11:00 committed by GitHub
parent a502f82e5d
commit 70156569b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 26 deletions

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require "hanami/slice/router"
require_relative "constants"
require_relative "slice/router"
module Hanami
# App routes
@ -25,6 +26,30 @@ module Hanami
# @see Hanami::Slice::Router
# @since 2.0.0
class Routes
# @since 2.0.0
class MissingActionError < Error
def initialize(action_key, slice)
action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
action_constant = slice.inflector.camelize(
"#{slice.inflector.underscore(slice.namespace.to_s)}#{PATH_DELIMITER}#{action_path}"
)
action_file = slice.root.join("#{action_path}#{RB_EXT}")
super(<<~MSG)
Could not find action with key #{action_key.inspect} in #{slice}
To fix this, define the action class #{action_constant} in #{action_file}
MSG
end
end
# @since 2.0.0
class NotCallableEndpointError < Error
def initialize(endpoint)
super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
end
end
# @api private
def self.routes
@routes ||= build_routes

View File

@ -1,22 +1,10 @@
# frozen_string_literal: true
require_relative "../../routes"
module Hanami
class Slice
module Routing
# @since 2.0.0
class UnknownActionError < Hanami::Error
def initialize(identifier)
super("unknown action referenced in router: `#{identifier.inspect}'")
end
end
# @since 2.0.0
class NotCallableEndpointError < StandardError
def initialize(endpoint)
super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
end
end
# Hanami app router endpoint resolver
#
# @since 2.0.0
@ -55,7 +43,7 @@ module Hanami
end
unless endpoint.respond_to?(:call)
raise NotCallableEndpointError.new(endpoint)
raise Routes::NotCallableEndpointError.new(endpoint)
end
endpoint
@ -79,7 +67,7 @@ module Hanami
# concerns (which may not be fully loaded at the time of reading the routes)
-> (*args) {
action = slice.resolve(action_key) do
raise UnknownActionError.new(key)
raise Routes::MissingActionError.new(action_key, slice)
end
action.call(*args)
@ -89,7 +77,7 @@ module Hanami
def ensure_action_in_slice(key)
return unless slice.booted?
raise UnknownActionError.new(key) unless slice.key?(key)
raise Routes::MissingActionError.new(key, slice) unless slice.key?(key)
end
end
end

View File

@ -373,8 +373,9 @@ RSpec.describe "Hanami web app", :app_integration do
require "hanami/boot"
expect { Hanami.app.rack_app }.to raise_error do |exception|
expect(exception).to be_kind_of(Hanami::Slice::Routing::UnknownActionError)
expect(exception.message).to include("missing.action")
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in TestApp::App")
expect(exception.message).to match(%r{define the action class TestApp::Actions::Missing::Action.+actions/missing/action.rb})
end
end
end
@ -404,8 +405,9 @@ RSpec.describe "Hanami web app", :app_integration do
require "hanami/boot"
expect { Hanami.app.rack_app }.to raise_error do |exception|
expect(exception).to be_kind_of(Hanami::Slice::Routing::UnknownActionError)
expect(exception.message).to include("missing.action")
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in Admin::Slice")
expect(exception.message).to match(%r{define the action class Admin::Actions::Missing::Action.+slices/admin/actions/missing/action.rb})
end
end
end
@ -434,8 +436,9 @@ RSpec.describe "Hanami web app", :app_integration do
expect { Hanami.app.rack_app }.not_to raise_error
expect { get "/missing" }.to raise_error do |exception|
expect(exception).to be_kind_of(Hanami::Slice::Routing::UnknownActionError)
expect(exception.message).to include("missing.action")
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in TestApp::App")
expect(exception.message).to match(%r{define the action class TestApp::Actions::Missing::Action.+actions/missing/action.rb})
end
end
end
@ -467,8 +470,9 @@ RSpec.describe "Hanami web app", :app_integration do
expect { Hanami.app.rack_app }.not_to raise_error
expect { get "/admin/missing" }.to raise_error do |exception|
expect(exception).to be_kind_of(Hanami::Slice::Routing::UnknownActionError)
expect(exception.message).to include("missing.action")
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in Admin::Slice")
expect(exception.message).to match(%r{define the action class Admin::Actions::Missing::Action.+slices/admin/actions/missing/action.rb})
end
end
end