File Coverage

blib/lib/Message/Router.pm
Criterion Covered Total %
statement 108 108 100.0
branch 62 80 77.5
condition 20 38 52.6
subroutine 10 10 100.0
pod 2 2 100.0
total 202 238 84.8


line stmt bran cond sub pod time code
1             package Message::Router;
2             $Message::Router::VERSION = '1.161240';
3 5     5   2476 use strict;use warnings;
  5     5   5  
  5         110  
  5         13  
  5         4  
  5         135  
4 5     5   2698 use Storable;
  5         11217  
  5         253  
5 5     5   156569 use Message::Match qw(mmatch);
  5         220974  
  5         263  
6 5     5   1804 use Message::Transform qw(mtransform);
  5         1571  
  5         283  
7             require Exporter;
8 5     5   21 use vars qw(@ISA @EXPORT_OK $config);
  5         7  
  5         2160  
9             @ISA = qw(Exporter);
10             @EXPORT_OK = qw(mroute mroute_config);
11              
12             sub mroute_config {
13 18     18 1 11275 my $new_config;
14 18         20 eval {
15 18 100       50 $new_config = shift
16             or die 'single argument must be a HASH reference';
17 17 100       37 die 'single argument must be a HASH reference'
18             if shift;
19 16 100 33     72 die 'single argument must be a HASH reference'
20             if not $new_config or not ref $new_config eq 'HASH';
21             die "passed config must have an ARRAY or HASH 'routes' key"
22 15 100       31 if not $new_config->{routes};
23 14 100 100     46 if( ref $new_config->{routes} ne 'ARRAY' and
24             ref $new_config->{routes} ne 'HASH') {
25 1         11 die "passed config must have an ARRAY or HASH 'routes' key"
26             }
27 13 100       30 if(ref $new_config->{routes} eq 'ARRAY') {
28 11         9 foreach my $route (@{$new_config->{routes}}) {
  11         18  
29 12 100       23 die "each route must be a HASH reference"
30             if not $route;
31 11 100       26 die "each route must be a HASH reference"
32             if not ref $route eq 'HASH';
33             die "each route has to have a HASH reference 'match' key"
34 10 100       19 if not $route->{match};
35             die "each route has to have a HASH reference 'match' key"
36 9 50       17 if not ref $route->{match} eq 'HASH';
37 9 100       19 if($route->{transform}) {
38             die "the optional 'transform' key must be a HASH reference"
39 4 100       19 if ref $route->{transform} ne 'HASH';
40             }
41 8 50       13 if($route->{forwards}) {
42             die "the optional 'forwards' key must be an ARRAY reference"
43 8 100       20 if ref $route->{forwards} ne 'ARRAY';
44 7         7 foreach my $forward (@{$route->{forwards}}) {
  7         10  
45 7 50       13 die 'each forward must be a HASH reference'
46             if not $forward;
47 7 100       32 die 'each forward must be a HASH reference'
48             if ref $forward ne 'HASH';
49             die "each forward must have a scalar 'handler' key"
50 6 100       16 if not $forward->{handler};
51             die "each forward must have a scalar 'handler' key"
52 5 100       21 if ref $forward->{handler};
53             }
54             }
55             }
56             }
57             };
58 18 100       33 if($@) {
59 13         41 die "Message::Router::mroute_config: $@\n";
60             }
61 5         8 $config = $new_config;
62 5         14 return $config;
63             }
64              
65             sub mroute {
66 7     7 1 1138 eval {
67 7 100       38 my $message = shift or die 'single argument must be a HASH reference';
68 6 100 66     47 die 'single argument must be a HASH reference'
69             unless ref $message and ref $message eq 'HASH';
70 5 50       10 die 'single argument must be a HASH reference'
71             if shift;
72 5 50 66     26 if( $message->{static_forwards} and
      66        
      50        
73             ref $message->{static_forwards} and
74             ref $message->{static_forwards} eq 'ARRAY' and
75 1         8 scalar @{$message->{static_forwards}}) {
76 1         1 my $forward_recs = shift @{$message->{static_forwards}};
  1         4  
77 1 50       1 delete $message->{static_forwards} unless scalar @{$message->{static_forwards}};
  1         3  
78 1 50 33     8 die 'static_forwards: defined forward must be an ARRAY reference'
79             if not ref $forward_recs or ref $forward_recs ne 'ARRAY';
80 1         1 foreach my $forward_rec (@{$forward_recs}) {
  1         1  
81             die 'static_forwards: defined forward must contain a forward that is a HASH reference'
82             if not $forward_rec->{forward} or
83             not ref $forward_rec->{forward} or
84 1 50 33     7 ref $forward_rec->{forward} ne 'HASH';
      33        
85 1         56 my $message = Storable::dclone $message;
86 1 50       2 if($forward_rec->{log_history}) {
87 1         19 $forward_rec = Storable::dclone $forward_rec;
88             $message->{'.static_forwards_log'} = {
89             forward_history => []
90 1 50       14 } unless $message->{'.static_forwards_log'};
91 1         1 push @{$message->{'.static_forwards_log'}->{forward_history}}, $forward_rec;
  1         2  
92             }
93 1 50 33     7 if( $forward_rec->{transform} and
      33        
94             ref $forward_rec->{transform} and
95             ref $forward_rec->{transform} eq 'HASH') {
96 1         3 mtransform($message, $forward_rec->{transform});
97             }
98 1         13 eval {
99 5     5   23 no strict 'refs';
  5         11  
  5         732  
100             $forward_rec->{forward}->{handler} = 'IPC::Transit::Router::handler'
101 1 50       2 unless $forward_rec->{forward}->{handler};
102 1         4 &{$forward_rec->{forward}->{handler}}(
103             message => $message,
104             forward => $forward_rec->{forward}
105 1         2 );
106             };
107 1 50       7 die "static_forwards: handler failed: $@" if $@;
108             }
109 1         2 return 1;
110             }
111 4         5 my @routes;
112 4 100       11 if(ref $config->{routes} eq 'ARRAY') {
    50          
113 3         3 @routes = @{$config->{routes}};
  3         7  
114             } elsif(ref $config->{routes} eq 'HASH') {
115 1         1 foreach my $order (sort { $a <=> $b } keys %{$config->{routes}}) {
  1         5  
  1         6  
116 2         3 push @routes, $config->{routes}->{$order};
117             }
118             }
119             ROUTE:
120 4         8 foreach my $route (@routes) {
121 5         6 my $did_short_circuit = eval {
122 5 50       14 if(mmatch($message, $route->{match})) {
123 4 50       103 if($route->{transform}) {
124 4         25 mtransform($message, $route->{transform});
125             }
126 4 50       48 if($route->{forwards}) {
127 4         19 foreach my $forward (@{$route->{forwards}}) {
  4         7  
128 5     5   19 no strict 'refs';
  5         5  
  5         867  
129 4         17 &{$forward->{handler}}(
130             message => $message,
131             route => $route,
132             routes => $config->{routes},
133 4         4 forward => $forward
134             );
135             }
136             }
137 4 50 66     41 if( $route->{'.router_control'} and
      66        
138             ref $route->{'.router_control'} eq 'HASH' and
139             $route->{'.router_control'}->{short_circuit}) {
140 1         2 return 1;
141             }
142             }
143 3         3 return 0;
144             };
145 5 100       56 if($@) {
146 1         3 die "Message::Router::mroute: $@\n";
147             }
148 4 100       10 last if $did_short_circuit;
149             }
150             };
151 7 100       17 if($@) {
152 3         10 die "Message::Router::mmatch: $@\n";
153             }
154 4         11 return 1;
155             }
156             1;
157              
158             __END__
159              
160             =head1 NAME
161              
162             Message::Router - Fast, simple message routing
163              
164             =head1 SYNOPSIS
165              
166             use Message::Router qw(mroute mroute_config);
167              
168             sub main::handler1 {
169             my %args = @_;
170             #gets:
171             # $args{message}
172             # $args{route}
173             # $args{routes}
174             # $args{forward}
175             print "$args{message}->{this}\n"; #from the transform
176             print "$args{forward}->{x}\n"; #from the specific forward
177             }
178              
179             mroute_config({
180             routes => [
181             { match => {
182             a => 'b',
183             },
184             forwards => [
185             { handler => 'main::handler1',
186             x => 'y',
187             },
188             ],
189             transform => {
190             this => 'that',
191             },
192             }
193             ],
194             });
195             mroute({a => 'b'}); #prints 'that', and then 'y', per the handler1 sub
196              
197             mroute_config({
198             routes => {
199             10 => {
200             match => {
201             a => 'b',
202             },
203             forwards => [
204             { handler => 'main::handler1',
205             x => 'y',
206             },
207             ],
208             transform => {
209             this => 'that',
210             },
211             }
212             ],
213             });
214             mroute({a => 'b'}); #prints 'that', and then 'y', per the handler1 sub
215             #same as the ARRAY based, but it uses the HASH keys in numerical order
216              
217             =head1 DESCRIPTION
218              
219             This library allows fast, flexible and general message routing.
220              
221             =head1 FUNCTIONS
222              
223             =head2 mroute_config($config);
224              
225             The config used by all mroute calls
226              
227             =head2 mroute($message);
228              
229             Pass $message through the config; this will emit zero or more callbacks.
230              
231             =head1 TODO
232              
233             A config validator.
234              
235             Short-circuiting
236              
237             More flexible match and transform configuration forms
238              
239             =head1 BUGS
240              
241             None known.
242              
243             =head1 COPYRIGHT
244              
245             Copyright (c) 2012, 2013 Dana M. Diederich. All Rights Reserved.
246              
247             =head1 AUTHOR
248              
249             Dana M. Diederich <dana@realms.org>
250              
251             =cut
252