File Coverage

blib/lib/Sub/Middler.pm
Criterion Covered Total %
statement 17 31 54.8
branch 0 6 0.0
condition 0 3 0.0
subroutine 6 9 66.6
pod 3 3 100.0
total 26 52 50.0


line stmt bran cond sub pod time code
1             package Sub::Middler;
2 1     1   68577 use 5.024000;
  1         4  
3 1     1   6 use strict;
  1         1  
  1         19  
4 1     1   5 use warnings;
  1         2  
  1         41  
5 1     1   6 use feature "refaliasing";
  1         2  
  1         209  
6              
7              
8             our $VERSION = 'v0.1.0';
9              
10             sub new {
11             #simply an array...
12 0     0 1   bless [], __PACKAGE__;
13             }
14              
15             # register sub refs to middleware makers
16             sub register {
17 1     1   7 no warnings "experimental";
  1         1  
  1         114  
18 0     0 1   \my @middleware=$_[0]; #self
19 0           my $sub=$_[1];
20 0           push @middleware, $sub;
21 0           return $_[0]; #allow chaining
22             }
23              
24              
25             # Link together sub and give each one an index
26             # Required argument is the 'dispatcher' which is the end point to call
27             #
28             sub link {
29 1     1   7 no warnings "experimental";
  1         2  
  1         225  
30              
31 0 0 0 0 1   die "A CODE reference is requred when linking middleware" unless(@_ >=2 and ref $_[1] eq "CODE");
32            
33 0           \my @middleware=$_[0]; #self;
34              
35 0           my $dispatcher=$_[1];
36              
37 0           my @mw; # The generated subs
38              
39 0           for my $i (reverse 0..@middleware-1){
40 0           my $maker=$middleware[$i];
41 0 0         my $next=($i==@middleware-1)?$dispatcher:$mw[$i+1];
42            
43              
44 0           $mw[$i]=$maker->($next, $i);
45             }
46              
47 0 0         @middleware?$mw[0]:$dispatcher;
48             }
49              
50              
51             1;
52              
53             =head1 NAME
54              
55             Sub::Middler - Middleware subroutine chaining
56              
57             =head1 SYNOPSIS
58              
59             use strict;
60             use warnings;
61             use Sub::Middler;
62              
63             my $middler=Sub::Middler->new;
64              
65             $middler->register(mw1(x=>1));
66             $middler->register(mw2(y=>10));
67              
68             my $head=$middler->link(
69             sub {
70             print "Result: $_[0]\n";
71             }
72             );
73              
74             $head->(0); # Call the Chain
75              
76             # Middleware 1
77             sub mw1 {
78             my %options=@_;
79             sub {
80             my ($next,$index)=@_;
81             sub {
82             my $work=$_[0]+$options{x};
83             $next->($work);
84             }
85             }
86             }
87              
88             # Middleware 2
89             sub mw2 {
90             my %options=@_;
91             sub {
92             my ($next, $index)=@_;
93             sub {
94             my $work= $_[0]*$options{y};
95             $next->( $work);
96             }
97             }
98             }
99              
100             =head1 DESCRIPTION
101              
102             A small module, facilitating linking together subroutines, acting as middleware
103             or filters into chains with low runtime overhead.
104              
105             To achieve this, the 'complexity' is offloaded to the definition of
106             middleware/filters subroutines. They must be wrapped in subroutines
107             appropriately to facilitate the lexical binding of linking variables.
108              
109             This differs from other 'sub chaining' modules as it does not use a loop
110             internally to iterate over a list of subroutines at runtime. As such there is
111             no implicit call to the next item in the chain. Each stage can run
112             synchronously or asynchronously or even not at all. Each element in the chain
113             is responsible for calling the next.
114              
115             Finally the arguments and signatures at each stage of middleware are completely
116             user defined and are not interfered with by this module. This allows reuse of
117             the C<@_> array in calling subsequent stages for ultimate performance if you
118             know what you're doing.
119              
120              
121             =head1 API
122              
123             =head2 Managing a chain
124              
125             =head3 new
126            
127             my $object=Sub::Middler->new;
128              
129             Creates a empty middler object ready to accept middleware. The object is a
130             blessed array reference which stores the middleware directly.
131              
132             =head3 register
133              
134             $object->register(my_middlware());
135              
136             Appends the middleware to the internal list for later linking.
137              
138             =head3 link
139              
140             $object->link($last);
141              
142             Links together the registered middleware. Each middleware is intrinsically
143             linked to the next middleware in the list. The last middleware being linked to
144             the C<$last> argument, which must be a code ref.
145              
146             The C<$last> ref MUST be a regular subroutine reference, not middleware as it
147             is defined below.
148              
149             Calls C if C<$last> is not a code ref.
150              
151             =head2 Creating Middleware
152              
153             To achieve low over head in linking middleware, functional programming
154             techniques (higher order functions) are utilised. This also give the greatest
155             flexibility to the middleware, as signatures are completely user defined.
156              
157             The trade off is that the middleware must be defined in a certain code
158             structure. While this isn't difficult, it takes a minute to wrap your head
159             around.
160              
161              
162             =head3 Middlware Definition
163              
164             Middleware must be a subroutine (top/name) which returns a anonymous subroutine
165             (maker), which also returns a anonymous subroutine to perform work (kernel).
166              
167             This sounds complicated by this is what is looks like in code:
168              
169             sub my_middleware { (1) Top/name subroutine
170             my %options=@_; Store any config
171            
172             sub { (2) maker sub is returned
173             my ($next, $index)=@_; (3) Must store these vars
174              
175             sub { (4) Returns the kernel sub
176             # Code here implements your middleware
177             # %options are lexically accessable here
178            
179              
180             # Execute the next item in the chain
181             $next->(...); (5) Does work and calls the next entry
182              
183              
184             (6) Post work if applicable
185             }
186             }
187             }
188              
189             =over
190              
191             =item Top Subroutine
192              
193             The top sub routine (1) can take any arguments you desire and can be called
194             what you like. The idea is it represents your middleware/filter and stores any
195             setup lexically for the B sub to close over. It returns the B
196             sub.
197              
198             =item Maker Subroutine
199              
200             This anonymous sub (2) closes over the variables stored in B and is the
201             input to this module (via C). When being linked (called) by this
202             module it is provided two arguments; the reference to the next item in
203             the chain and the current middleware index. These B be stored to be
204             useful, but can be called anything you like (3).
205              
206              
207             =item Kernel subroutine
208              
209             This anonymous subroutine (4) actually performs the work of the
210             middleware/filter. After work is done, the next item in the chain must be
211             called explicitly (5). This supports synchronous or asynchronous middleware.
212             Any extra work can be performed after the chain is completed after this call
213             (6).
214              
215             =back
216              
217              
218             =head2 LINKING CHAINS
219              
220             Multiple chains of middleware can be linked together. This needs to be done in
221             reverse order. The last segment becomes the C<$last> item when linking the
222             preceding chain and so on.
223              
224              
225             =head2 EXAMPLES
226              
227             The synopsis example can be found in the examples directory of this
228             distribution.
229              
230              
231             =head1 SEE ALSO
232              
233             L and L links together subs. They provide other
234             features that this module does not.
235              
236             These iterate over a list of subroutines at runtime to achieve named subs etc.
237             where as this module pre links subroutines together, reducing overhead.
238              
239              
240             =head1 AUTHOR
241              
242             Ruben Westerberg, Edrclaw@mac.comE
243              
244             =head1 REPOSITORTY and BUGS
245              
246             Please report any bugs via git hub: L
247              
248             =head1 COPYRIGHT AND LICENSE
249              
250             Copyright (C) 2023 by Ruben Westerberg
251              
252             This library is free software; you can redistribute it
253             and/or modify it under the same terms as Perl or the MIT
254             license.
255              
256             =head1 DISCLAIMER OF WARRANTIES
257              
258             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
259             OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE
260             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
261             PARTICULAR PURPOSE.
262             =cut
263