File Coverage

blib/lib/Dancer2/Session/Redis.pm
Criterion Covered Total %
statement 44 86 51.1
branch 4 48 8.3
condition 0 18 0.0
subroutine 13 18 72.2
pod n/a
total 61 170 35.8


line stmt bran cond sub pod time code
1             package Dancer2::Session::Redis;
2 3     3   1455437 use strictures 1;
  3         24  
  3         144  
3             # ABSTRACT: Perl Dancer2 session provider for storing session data within key-value-store Redis.
4             #
5             # This file is part of Dancer2-Session-Redis
6             #
7             # This software is Copyright (c) 2016 by BURNERSK .
8             #
9             # This is free software, licensed under:
10             #
11             # The MIT (X11) License
12             #
13              
14             BEGIN {
15 3     3   358 our $VERSION = '0.008'; # VERSION: generated by DZP::OurPkgVersion
16             }
17              
18 3     3   14 use Carp qw( carp croak );
  3         4  
  3         188  
19 3     3   17 use Types::Standard qw( Maybe Undef InstanceOf );
  3         3  
  3         48  
20 3     3   1986 use Moo;
  3         4  
  3         14  
21 3     3   697 use Redis;
  3         5  
  3         73  
22 3     3   11 use Safe::Isa;
  3         3  
  3         373  
23 3     3   16 use Try::Tiny;
  3         3  
  3         157  
24 3     3   17 use Type::Tiny;
  3         25  
  3         2880  
25              
26             with 'Dancer2::Core::Role::SessionFactory';
27              
28              
29             ############################################################################
30              
31             my $TYPE_SERIALIZATIONOBJECT = Type::Tiny->new(
32             name => 'SerializationObject',
33             constraint => sub { $_->$_call_if_object( 'does' => 'Dancer2::Session::Redis::SerializationRole' ) },
34             message => sub { qq{$_ does not consume a SerializationRole} },
35             );
36              
37             has redis_server => ( is => 'ro' );
38             has redis_sock => ( is => 'ro' );
39             has redis_password => ( is => 'ro' );
40             has redis_reconnect => ( is => 'ro' );
41             has redis_on_connect => ( is => 'ro' );
42             has redis_every => ( is => 'ro' );
43             has redis_debug => ( is => 'ro' );
44             has redis_name => ( is => 'ro' );
45             has redis_key => ( is => 'ro', default => 'session:%s' );
46             has redis_serialization => ( is => 'ro' );
47             has redis_test_mock => ( is => 'ro', default => sub { $ENV{DANCER_SESSION_REDIS_TEST_MOCK} || 0 } );
48              
49             has _serialization => (
50             is => 'lazy',
51             isa => Maybe [ $TYPE_SERIALIZATIONOBJECT ],
52             );
53              
54             sub _build__serialization {
55 1     1   375 my ($dsl1) = @_;
56 1         2 my $serialization;
57 1 50       18 return unless $dsl1->redis_serialization;
58              
59             # Setup serialization.
60 0 0       0 if ( my $serialization_module = delete $dsl1->redis_serialization->{module} ) {
61 0 0       0 $serialization_module =~ s/^/Dancer2::Session::Redis::Serialization::/
62             if $serialization_module !~ m/^Dancer2::Session::Redis::Serialization::/;
63 0 0       0 croak qq{Invalid serialization module: $serialization_module}
64             if $serialization_module !~ m/^Dancer2::Session::Redis::Serialization::[a-zA-Z][a-zA-Z0-9_]*$/;
65             try {
66 0 0   0   0 eval "require $serialization_module" or croak $@;
67 0         0 $serialization = "$serialization_module"->new( %{ $dsl1->redis_serialization } );
  0         0  
68             }
69             catch {
70 0     0   0 croak(qq{Unable to set up serialization '$serialization_module': $_});
71 0         0 };
72             }
73 0         0 return $serialization;
74             }
75              
76             has _redis => (
77             is => 'lazy',
78             isa => InstanceOf ['Redis'] | InstanceOf ['t::TestApp::RedisMock'],
79             );
80              
81             sub _build__redis {
82 1     1   488 my ($dsl2) = @_;
83              
84 1 50       8 if ( $dsl2->redis_test_mock ) {
85 1         502 require t::TestApp::RedisMock;
86 1         1884 return t::TestApp::RedisMock->new;
87             }
88              
89             # Build Redis->new settings.
90 0 0       0 my %opts = (
    0          
    0          
    0          
    0          
    0          
    0          
91             ( $dsl2->redis_server ? ( server => $dsl2->redis_server ) : () ),
92             ( $dsl2->redis_sock ? ( sock => $dsl2->redis_sock ) : () ),
93             ( $dsl2->redis_password ? ( password => $dsl2->redis_password ) : () ),
94             ( $dsl2->redis_reconnect ? ( reconnect => $dsl2->redis_reconnect ) : () ),
95             ( $dsl2->redis_every ? ( every => $dsl2->redis_every ) : () ),
96             ( $dsl2->redis_name ? ( name => $dsl2->redis_name ) : () ),
97             ( $dsl2->redis_debug ? ( debug => $dsl2->redis_debug ) : () ),
98             );
99              
100             # Cleanup settings.
101 0 0       0 delete $opts{server} if $opts{sock}; # prefer UNIX/Linux sockets.
102 0 0       0 delete $opts{sock} if $opts{server};
103 0 0 0     0 delete $opts{password} if exists $opts{password} && ( !defined $opts{password} || $opts{password} eq '' );
      0        
104 0 0       0 delete $opts{name} unless $opts{name};
105              
106             # Validate reconnect settings.
107 0 0 0     0 if ( ( exists $opts{reconnect} || exists $opts{every} ) && ( !$opts{reconnect} || !$opts{every} ) ) {
      0        
      0        
108 0         0 croak(q{Incomplete Redis configuration for 'reconnect' and 'every', skipping...});
109 0         0 delete $opts{reconnect};
110 0         0 delete $opts{every};
111             }
112              
113             # Validate on_connect settings.
114 0 0       0 if ( $dsl2->redis_on_connect ) {
115 0 0       0 if ( !$dsl2->redis_on_connect ) {
116 0         0 croak(q{Invalid Redis configuration for 'on_connect', skipping...});
117             }
118             else {
119 0         0 $opts{on_connect} = $dsl2->redis_on_connect;
120             }
121             }
122              
123             # Validate connection settings.
124             croak(q{Incomplete Redis configuration: required is either 'server' or 'sock'})
125 0 0 0     0 if !$opts{server} && !$opts{sock};
126              
127 0 0       0 croak(q{Incomplete Redis configuration: session_duration is required})
128             unless $dsl2->has_session_duration;
129              
130 0         0 return Redis->new(%opts);
131             }
132              
133             ############################################################################
134              
135             # Get session data.
136             sub _retrieve {
137 2     2   27794 my ( $dsl, $session_id ) = @_;
138 2         11 my $key = sprintf $dsl->redis_key, $session_id;
139 2         29 my $data = $dsl->_redis->get($key);
140 2 50       50 if ( my $serialization = $dsl->_serialization ) {
141 0         0 $data = $serialization->decode($data);
142             }
143 2         38 $dsl->_redis->expire( $key => $dsl->session_duration );
144 2         31 return $data;
145             }
146              
147             # Set session data.
148             sub _flush {
149 2     2   43135 my ( $dsl, $session_id, $data ) = @_;
150 2         11 my $key = sprintf $dsl->redis_key, $session_id;
151 2 50       17 if ( my $serialization = $dsl->_serialization ) {
152 0         0 $data = $serialization->encode($data);
153             }
154 2         110 $dsl->_redis->set( $key => $data );
155 2         996 $dsl->_redis->expire( $key => $dsl->session_duration );
156 2         16 return;
157             }
158              
159             # Delete session data.
160             sub _destroy {
161 0     0     my ( $dsl, $session_id ) = @_;
162 0           my $key = sprintf $dsl->redis_key, $session_id;
163 0           $dsl->_redis->del($key);
164 0           return;
165             }
166              
167             # Get all session ids.
168             sub _sessions {
169 0     0     my ($dsl) = @_;
170 0           my $key = sprintf $dsl->redis_key, '*';
171 0           my $key_pattern = quotemeta sprintf $dsl->redis_key, '';
172 0           my @keys = $dsl->_redis->keys($key);
173 0           return [ map { my $a = $_; $a =~ s/^$key_pattern(.*)$/$1/; $a } @keys ];
  0            
  0            
  0            
174             }
175              
176             # Change session ID.
177             sub _change_id {
178 0     0     my ( $self, $old_id, $new_id ) = @_;
179 0           $self->_flush( $new_id, $self->_retrieve( $old_id ) );
180 0           $self->_destroy( $old_id );
181             }
182              
183             ############################################################################
184              
185              
186             1;
187              
188             __END__