File Coverage

blib/lib/Sub/Middler.pm
Criterion Covered Total %
statement 33 33 100.0
branch 7 8 87.5
condition 2 3 66.6
subroutine 9 9 100.0
pod 3 3 100.0
total 54 56 96.4


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