Skip to content

Commit

Permalink
Drop AED::EventListenerInterface (#391)
Browse files Browse the repository at this point in the history
* Simplify event listener registration logic
  • Loading branch information
Blacksmoke16 authored Apr 7, 2024
1 parent 97c3c3f commit 51f2321
Show file tree
Hide file tree
Showing 27 changed files with 39 additions and 282 deletions.
4 changes: 0 additions & 4 deletions docs/getting_started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,6 @@ ACF.configuration_annotation MyAnnotation, name : String? = nil
# Define and register our listener that will do something based on our annotation.
@[ADI::Register]
class MyAnnotationListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def on_view(event : ATH::Events::View) : Nil
# Represents all custom annotations applied to the current ATH::Action.
Expand Down Expand Up @@ -250,8 +248,6 @@ ACF.configuration_annotation Paginated, page : Int32 = 1, per_page : Int32 = 100
# Define and register our listener that will handle paginating the response.
@[ADI::Register]
struct PaginationListener
include AED::EventListenerInterface
private PAGE_QUERY_PARAM = "page"
private PER_PAGE_QUERY_PARAM = "per_page"
Expand Down
7 changes: 1 addition & 6 deletions docs/getting_started/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,13 @@ It is also possible to handle specific error states differently by registering m

Unlike other frameworks, Athena Framework leverages event based middleware instead of a pipeline based approach.
The primary use case for event listeners is to tap into the life-cycle of the request, such as adding common headers, setting state extracted from the request, or whatever else the application requires.
These can be created by creating a type annotated with [ADI::Register](/DependencyInjection/Register) and including [AED::EventListenerInterface](/EventDispatcher/EventListenerInterface).
From here, 1 or more methods may be defined that has the [AEDA::AsEventListener](/EventDispatcher/Annotations/AsEventListener) annotation applied to it.
These can be created by creating a type annotated with [ADI::Register](/DependencyInjection/Register), then annotating one or more methods with [AEDA::AsEventListener](/EventDispatcher/Annotations/AsEventListener).

```crystal
require "athena"
@[ADI::Register]
class CustomListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def on_response(event : ATH::Events::Response) : Nil
event.response.headers["FOO"] = "BAR"
Expand Down Expand Up @@ -161,8 +158,6 @@ end
# Define a listener that listens our the custom event.
@[ADI::Register]
class CustomEventListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def call(event : MyEvent) : Nil
event.value *= 10
Expand Down
2 changes: 0 additions & 2 deletions docs/getting_started/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ checking if the request's path represents a file/directory within the applicatio
# Register a request event listener to handle returning static files.
@[ADI::Register]
struct StaticFileListener
include AED::EventListenerInterface
# This could be parameter if the directory changes between environments.
private PUBLIC_DIR = Path.new("public").expand
Expand Down
4 changes: 0 additions & 4 deletions docs/why_athena.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,6 @@ Unlike other frameworks, Athena Framework leverages event based middleware inste
```crystal
@[ADI::Register]
class CustomListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def on_response(event : ATH::Events::Response) : Nil
event.response.headers["FOO"] = "BAR"
Expand Down Expand Up @@ -231,8 +229,6 @@ ACF.configuration_annotation MyAnnotation, name : String? = nil
# Define and register our listener that will do something based on our annotation.
@[ADI::Register]
class MyAnnotationListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def on_view(event : ATH::Events::View) : Nil
# Represents all custom annotations applied to the current ATH::Action.
Expand Down
2 changes: 0 additions & 2 deletions src/components/event_dispatcher/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ However having a dedicated type is usually the better practice.

```crystal
struct SendConfirmationListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def order_placed(event : OrderPlaced) : Nil
# Send a confirmation email to the user
Expand Down
1 change: 0 additions & 1 deletion src/components/event_dispatcher/spec/callable_spec.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require "./spec_helper"

private class TestListener
include AED::EventListenerInterface
end

describe AED::Callable do
Expand Down
16 changes: 0 additions & 16 deletions src/components/event_dispatcher/spec/compiler_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Event listener methods can only be defined as instance methods. Did you mean 'MyListener#listener'?", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def self.listener(blah : AED::GenericEvent(String, String)) : Nil
end
Expand All @@ -21,8 +19,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected 'MyListener#listener' to have 1..2 parameters, got '0'.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener : Nil
end
Expand All @@ -35,8 +31,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected 'MyListener#listener' to have 1..2 parameters, got '3'.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener(foo, bar, baz) : Nil
end
Expand All @@ -49,8 +43,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected parameter #1 of 'MyListener#listener' to have a type restriction of an 'AED::Event' instance, but it is not restricted.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener(foo) : Nil
end
Expand All @@ -63,8 +55,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected parameter #1 of 'MyListener#listener' to have a type restriction of an 'AED::Event' instance, not 'String'.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener(foo : String) : Nil
end
Expand All @@ -77,8 +67,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected parameter #2 of 'MyListener#listener' to have a type restriction of 'AED::EventDispatcherInterface', but it is not restricted.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener(foo : AED::GenericEvent(String, String), dispatcher) : Nil
end
Expand All @@ -91,8 +79,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Expected parameter #2 of 'MyListener#listener' to have a type restriction of 'AED::EventDispatcherInterface', not 'String'.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener]
def listener(foo : AED::GenericEvent(String, String), dispatcher : String) : Nil
end
Expand All @@ -105,8 +91,6 @@ describe Athena::EventDispatcher do
ASPEC::Methods.assert_error "Event listener method 'MyListener#listener' expects a 'NumberLiteral' for its 'AEDA::AsEventListener#priority' field, but got a 'StringLiteral'.", <<-CR
require "./spec_helper.cr"
class MyListener
include AED::EventListenerInterface
@[AEDA::AsEventListener(priority: "foo")]
def listener(foo : AED::GenericEvent(String, String)) : Nil
end
Expand Down
2 changes: 0 additions & 2 deletions src/components/event_dispatcher/spec/event_dispatcher_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ class Sum < AED::Event
end

class TestListener
include AED::EventListenerInterface

getter values = [] of Int32

@[AEDA::AsEventListener]
Expand Down
4 changes: 1 addition & 3 deletions src/components/event_dispatcher/src/annotations.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Can be applied to method(s) within a type including `AED::EventListenerInterface` to denote that method is an event listener.
# Can be applied to method(s) within a type to denote that method is an event listener.
# The annotation expects to be assigned to an instance method with between 1 and 2 parameters with a return type of `Nil`.
# The first parameter should be the concrete `AED::Event` instance the method is listening on.
# The optional second parameter should be typed as an `AED::EventDispatcherInterface`.
Expand All @@ -7,8 +7,6 @@
#
# ```
# class MyListener
# include AED::EventListenerInterface
#
# # Single parameter
# @[AEDA::AsEventListener]
# def single_param(event : MyEvent) : Nil
Expand Down
8 changes: 3 additions & 5 deletions src/components/event_dispatcher/src/callable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# See each subclass for more information.
#
# TIP: These types can be manually instantiated and added via the related `AED::EventDispatcherInterface#listener(callable)` overload.
# This can be useful as a point of integration to other libraries, such as lazily instantiating a `AED::EventListenerInterface` instance.
# This can be useful as a point of integration to other libraries, such as lazily instantiating listener instances.
#
# ### Name
#
Expand All @@ -21,8 +21,6 @@
# dispatcher.listener(MyEvent, name: "block-listener") { }
#
# class MyListener
# include AED::EventListenerInterface
#
# @[AEDA::AsEventListener]
# def on_my_event(event : MyEvent) : Nil
# end
Expand Down Expand Up @@ -127,9 +125,9 @@ abstract struct Athena::EventDispatcher::Callable
end
end

# Represents a listener associated with an `AED::EventListenerInterface` when using the `AEDA::AsEventListener` annotation.
# Represents a dedicated type based listener using `AEDA::AsEventListener` annotations.
struct EventListenerInstance(I, E) < Athena::EventDispatcher::Callable
# Returns the `AED::EventListenerInterface` instance this listener is associated with.
# Returns the listener instance this callable is associated with.
getter instance : I

@callback : Proc(E, Nil) | Proc(E, AED::EventDispatcherInterface, Nil)
Expand Down
10 changes: 2 additions & 8 deletions src/components/event_dispatcher/src/event_dispatcher.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require "./event_dispatcher_interface"
require "./event"
require "./event_listener_interface"
require "./callable"

# Default implementation of `AED::EventDispatcherInterface`.
Expand Down Expand Up @@ -47,15 +46,10 @@ class Athena::EventDispatcher::EventDispatcher
end

# :inherit:
def listener(listener : AED::EventListenerInterface) : Nil
self.add_listener listener
end

private def add_listener(listener : T) : Nil forall T
def listener(listener : T) : Nil forall T
{% begin %}
{% listeners = [] of Nil %}

# Changes made here should also be reflected within `ATH::EventDispatcher::CompilerPasses::RegisterEventListenersPass`.
{%
class_listeners = T.class.methods.select &.annotation(AEDA::AsEventListener)

Expand Down Expand Up @@ -156,7 +150,7 @@ class Athena::EventDispatcher::EventDispatcher
end

# :inherit:
def remove_listener(listener : AED::EventListenerInterface) : Nil
def remove_listener(listener : T) : Nil forall T
@listeners.each do |event_class, listeners|
listeners.reject! { |l| l.is_a?(AED::Callable::EventListenerInstance) && l.instance == listener }

Expand Down
16 changes: 13 additions & 3 deletions src/components/event_dispatcher/src/event_dispatcher_interface.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# ```
#
# Another way involves passing an `AED::Callable` instance, created manually or via the `AED::Event.callable` method.
# Lastly, an `AED::EventListenerInterface` instance may also be passed.
# Lastly, a type that has one or more `AEDA::AsEventListener` annotated methods may also be passed.
#
# Once all listeners are registered, you can begin to dispatch events.
# Dispatching an event is simply calling the `#dispatch` method with an `AED::Event` subclass instance as an argument.
Expand Down Expand Up @@ -68,7 +68,12 @@ module Athena::EventDispatcher::EventDispatcherInterface
abstract def listener(event_class : E.class, *, priority : Int32 = 0, name : String? = nil, &block : E, AED::EventDispatcherInterface -> Nil) : AED::Callable forall E

# Registers the provided *listener* instance to this dispatcher.
abstract def listener(listener : AED::EventListenerInterface) : Nil
#
# `T` is any type that has methods annotated with `AEDA::AsEventListener`.
def listener(listener : T) : Nil forall T
# TODO: Make this actually abstract once https://github.com/crystal-lang/crystal/issues/14451 is resolved.
{% @type.raise "abstract `def Athena::EventDispatcher::EventDispatcherInterface#listener(listener : T) : Nil forall T` must be implemented by #{@type}" %}
end

# Returns a hash of all registered listeners as a `Hash(AED::Event.class, Array(AED::Callable))`.
abstract def listeners : Hash(AED::Event.class, Array(AED::Callable))
Expand All @@ -82,5 +87,10 @@ module Athena::EventDispatcher::EventDispatcherInterface
abstract def remove_listener(callable : AED::Callable) : Nil

# Deregisters listeners based on the provided *listener* from this dispatcher.
abstract def remove_listener(listener : AED::EventListenerInterface) : Nil
#
# `T` is any type that has methods annotated with `AEDA::AsEventListener`.
def remove_listener(listener : T) : Nil forall T
# TODO: Make this actually abstract once https://github.com/crystal-lang/crystal/issues/14451 is resolved.
{% @type.raise "abstract `def Athena::EventDispatcher::EventDispatcherInterface#remove_listener(listener : T) : Nil forall T` must be implemented by #{@type}" %}
end
end

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ ACF.configuration_annotation MyApp::NestedParameterAnn

@[ADI::Register]
struct CustomAnnotationListener
include AED::EventListenerInterface

@[AEDA::AsEventListener]
def on_response(event : ATH::Events::Response) : Nil
return unless action = event.request.action?
Expand Down
Loading

0 comments on commit 51f2321

Please sign in to comment.