File Coverage

blib/lib/Dancer2/ConfigReader.pm
Criterion Covered Total %
statement 115 121 95.0
branch 36 54 66.6
condition 8 10 80.0
subroutine 19 21 90.4
pod n/a
total 178 206 86.4


line stmt bran cond sub pod time code
1             # ABSTRACT: Config reader for Dancer2 App
2             package Dancer2::ConfigReader;
3             $Dancer2::ConfigReader::VERSION = '2.1.0';
4 163     163   541760 use Moo;
  163         22998  
  163         1637  
5              
6 163     163   162169 use Config::Any;
  163         1735782  
  163         7852  
7 163     163   101834 use Hash::Merge::Simple;
  163         95008  
  163         10884  
8 163     163   1386 use Carp 'croak';
  163         412  
  163         9565  
9 163     163   981 use Module::Runtime qw{ use_module };
  163         358  
  163         1243  
10 163     163   10772 use Ref::Util qw/ is_arrayref is_hashref /;
  163         6976  
  163         9695  
11 163     163   1006 use Scalar::Util qw/ blessed /;
  163         310  
  163         6980  
12 163     163   2749 use Path::Tiny ();
  163         29452  
  163         3694  
13              
14 163     163   85520 use Dancer2::Core::Factory;
  163         538  
  163         6216  
15 163     163   1129 use Dancer2::Core;
  163         302  
  163         3636  
16 163     163   67332 use Dancer2::Core::Types;
  163         774  
  163         1754  
17 163     163   2460762 use Dancer2::ConfigUtils 'normalize_config_entry';
  163         603  
  163         351414  
18              
19             our $MAX_CONFIGS = $ENV{DANCER_MAX_CONFIGS} || 100;
20              
21             my %KNOWN_CORE_KEYS = map +( $_ => 1 ), qw(
22             additional_config_readers
23             appdir
24             apphandler
25             appname
26             auto_page
27             behind_proxy
28             charset
29             content_type
30             default_mime_type
31             engines
32             environment
33             error_template
34             host
35             layout
36             layout_dir
37             log
38             logger
39             no_default_middleware
40             no_server_tokens
41             plugins
42             port
43             public_dir
44             route_handlers
45             serializer
46             session
47             show_errors
48             show_stacktrace
49             startup_info
50             static_handler
51             template
52             timeout
53             traces
54             type_library
55             views
56             strict_config
57             strict_config_allow
58             );
59              
60             my %KNOWN_ENGINE_CONFIG = (
61             'logger' => {
62             'base_keys' => {
63             'app_name' => 1,
64             'auto_encoding_charset' => 1,
65             'log_format' => 1,
66             'log_level' => 1,
67             },
68             'engines' => {
69             'capture' => { keys => {} },
70             'console' => { keys => {} },
71             'diag' => { keys => {} },
72             'file' => {
73             'keys' => {
74             'file_name' => 1,
75             'log_dir' => 1,
76             },
77             },
78             'note' => { keys => {} },
79             'null' => { keys => {} },
80             },
81             },
82             'serializer' => {
83             'engines' => {
84             'dumper' => { keys => {} },
85             'json' => { allow_any => 1 },
86             'mutable' => {
87             keys => {
88             mapping => 1,
89             },
90             },
91             'yaml' => { keys => {} },
92             },
93             },
94             'session' => {
95             'base_keys' => {
96             'cookie_domain' => 1,
97             'cookie_duration' => 1,
98             'cookie_name' => 1,
99             'cookie_path' => 1,
100             'cookie_same_site' => 1,
101             'is_http_only' => 1,
102             'is_secure' => 1,
103             'session_duration' => 1,
104             },
105             'engines' => {
106             'simple' => { keys => {} },
107             'yaml' => {
108             'keys' => {
109             'session_dir' => 1,
110             },
111             },
112             },
113             },
114             'template' => {
115             'engines' => {
116             'templatetoolkit' => { allow_any => 1 },
117             'tiny' => { allow_any => 1 },
118             },
119             },
120             );
121              
122             has location => (
123             is => 'ro',
124             isa => Str,
125             required => 1,
126             );
127              
128             has default_config => (
129             is => 'ro',
130             isa => HashRef,
131             required => 1,
132             );
133              
134             has config_location => (
135             is => 'ro',
136             isa => ReadableFilePath,
137             lazy => 1,
138             default => sub { $_[0]->location },
139             );
140              
141             # The type for this attribute is Str because we don't require
142             # an existing directory with configuration files for the
143             # environments. An application without environments is still
144             # valid and works.
145             has environments_location => (
146             is => 'ro',
147             isa => Str,
148             lazy => 1,
149             default => sub {
150             # short circuit
151             defined $ENV{'DANCER_ENVDIR'}
152             and return $ENV{'DANCER_ENVDIR'};
153              
154             my $self = shift;
155              
156             foreach my $maybe_path ( $self->config_location, $self->location ) {
157             my $path = Path::Tiny::path($maybe_path, 'environments');
158             $path->exists && $path->is_dir
159             and return $path->stringify;
160             }
161              
162             return '';
163             },
164             );
165              
166             has _config_location_path => (
167             is => 'ro',
168             lazy => 1,
169             builder => '_build_config_location_path',
170             init_arg => undef,
171             );
172              
173             has _environments_location_path => (
174             is => 'ro',
175             lazy => 1,
176             builder => '_build_environments_location_path',
177             init_arg => undef,
178             );
179              
180             sub _build_config_location_path {
181 0     0   0 my $self = shift;
182 0         0 return Path::Tiny::path( $self->config_location );
183             }
184              
185             sub _build_environments_location_path {
186 0     0   0 my $self = shift;
187 0         0 return Path::Tiny::path( $self->environments_location );
188             }
189              
190             has config => (
191             is => 'ro',
192             isa => HashRef,
193             lazy => 1,
194             builder => '_build_config',
195             );
196              
197             has environment => (
198             is => 'ro',
199             isa => Str,
200             required => 1,
201             );
202              
203             has config_readers => (
204             is => 'ro',
205             lazy => 1,
206             isa => ArrayRef,
207             builder => '_build_config_readers',
208             );
209              
210             # The config builder
211             sub _build_config {
212 271     271   339667 my ($self) = @_;
213              
214 271         1178 my $config = $self->default_config;
215              
216 271         669 my $nbr_config = 0;
217              
218 271         700 my @readers = @{ $self->config_readers };
  271         6583  
219              
220             my $config_to_object = sub {
221 102     102   135 my $thing = $_;
222              
223 102 50       190 return $thing if blessed $thing;
224              
225 102 50       232 $thing = { $thing => {} } unless ref $thing;
226              
227 102 50       200 die "additional_config_readers entry must have exactly one key\n"
228             if keys %$thing != 1;
229              
230 102         200 my( $class, $args ) = %$thing;
231              
232 102         227 return use_module($class)->new(
233             location => $self->location,
234             environment => $self->environment,
235             %$args,
236             );
237 270         75859 };
238              
239 270         1768 while( my $r = shift @readers ) {
240 373 100       3794 die <<"END" if $nbr_config++ >= $MAX_CONFIGS;
241             MAX_CONFIGS exceeded: read over $MAX_CONFIGS configurations
242              
243             Looks like you have an infinite recursion in your configuration system.
244             Re-run with DANCER_CONFIG_VERBOSE=1 to see what is going on.
245              
246             If your application really read that many configs (may \$dog have mercy
247             on your soul), you can increase the limit via the environment variable
248             DANCER_MAX_CONFIGS.
249              
250             END
251 372 50       1553 warn "Reading config from @{[ $r->name() ]}\n" if $ENV{DANCER_CONFIG_VERBOSE};
  0         0  
252 372         1726 my $local_config = $r->read_config;
253              
254 370 100       2775 if( my $additionals = delete $local_config->{additional_config_readers} ) {
255              
256 101 50       200 warn "Additional config readers found\n" if $ENV{DANCER_CONFIG_VERBOSE};
257              
258 101 100       196 unshift @readers, map { $config_to_object->($_) } is_arrayref($additionals) ? @$additionals : ($additionals);
  102         219  
259             }
260              
261 370         15140 $config = Hash::Merge::Simple->merge(
262             $config, $local_config
263             );
264             }
265              
266 267         16426 return $self->_normalize_config($config);
267             }
268              
269             sub _normalize_config {
270 268     268   2340 my ( $self, $config ) = @_;
271              
272 268         634 foreach my $key ( keys %{$config} ) {
  268         1416  
273 3117         5759 my $value = $config->{$key};
274 3117         6866 $config->{$key} = normalize_config_entry( $key, $value );
275             }
276              
277 267         1815 $self->_strict_config_keys($config);
278              
279 267         13229 return $config;
280             }
281              
282             sub _build_config_readers {
283 268     268   3735 my ($self) = @_;
284              
285             my @config_reader_names = $ENV{'DANCER_CONFIG_READERS'}
286 268 100       1777 ? (split qr{,}msx, $ENV{'DANCER_CONFIG_READERS'})
287             : ( q{Dancer2::ConfigReader::Config::Any} );
288              
289 268 50       1456 warn "ConfigReaders to use: @config_reader_names\n" if $ENV{DANCER_CONFIG_VERBOSE};
290             return [
291 268         1999 map use_module($_)->new(
292             location => $self->location,
293             environment => $self->environment,
294             ), @config_reader_names
295             ];
296             }
297              
298             sub _strict_config_keys {
299 267     267   944 my ( $self, $config ) = @_;
300              
301             return
302             if exists $config->{'strict_config'}
303 267 100 66     2388 && !$config->{'strict_config'};
304              
305 2         5 my %allowed_keys;
306 2 100       11 if ( exists $config->{'strict_config_allow'} ) {
307 1         3 my $allow = $config->{'strict_config_allow'};
308 1 50       6 if ( is_arrayref($allow) ) {
309 1         3 %allowed_keys = map +( $_ => 1 ), @{$allow};
  1         8  
310             } else {
311 0         0 croak('strict_config_allow can only be arrayref');
312             }
313             }
314              
315             my @warnings = map +(
316             $KNOWN_CORE_KEYS{$_} || $allowed_keys{$_}
317             ? ()
318             : "Unknown configuration key '$_'"
319 2 100 100     5 ), sort keys %{$config};
  2         39  
320              
321 2 50       8 if ( my $engines = $config->{'engines'} ) {
322 2         9 push @warnings, $self->_warn_unknown_engine_config_keys($engines);
323             }
324              
325 2 50       7 @warnings or return;
326              
327 2         53 warn join(
328             "\n",
329             @warnings,
330             'Set strict_config => 0 to silence these warnings.'
331             ) . "\n";
332             }
333              
334             sub _warn_unknown_engine_config_keys {
335 2     2   5 my ( $self, $engines ) = @_;
336 2 50       8 is_hashref($engines)
337             or return;
338              
339 2         4 my @warnings;
340              
341 2         5 for my $engine_type ( sort keys %{$engines} ) {
  2         9  
342 4         9 my $type_config = $engines->{$engine_type};
343 4 50       10 is_hashref($type_config)
344             or next;
345              
346 4 50       12 my $known_type = $KNOWN_ENGINE_CONFIG{$engine_type}
347             or next;
348              
349 4   100     19 my $base_keys = $known_type->{'base_keys'} || {};
350 4   50     12 my $known_engines = $known_type->{'engines'} || {};
351              
352 4         7 for my $engine_name ( sort keys %{$type_config} ) {
  4         13  
353 4         9 my $engine_config = $type_config->{$engine_name};
354 4 50       12 is_hashref($engine_config)
355             or next;
356              
357 4         13 my $normalized = _normalize_engine_name($engine_name);
358 4 50       12 defined $normalized
359             or next;
360              
361 4 50       14 my $known_engine = $known_engines->{$normalized} or next;
362 4 100       16 next if $known_engine->{'allow_any'};
363              
364 2 50       4 my %allowed = (%{$base_keys}, %{ $known_engine->{keys} || {} });
  2         7  
  2         53  
365              
366 2         6 for my $key ( sort keys %{$engine_config} ) {
  2         10  
367 4 100       14 next if $allowed{$key};
368 2         11 push @warnings,
369             "Unknown configuration key '$key' for engine '$engine_type/$engine_name'";
370             }
371             }
372             }
373              
374 2         7 return @warnings;
375             }
376              
377             sub _normalize_engine_name {
378 4     4   10 my ($name) = @_;
379              
380 4 50       11 return if !defined $name;
381 4 50       14 return if $name =~ /::/xms;
382              
383 4         11 my $normalized = lc $name;
384 4         12 $normalized =~ s/_//xmsg;
385 4         12 return $normalized;
386             }
387              
388             1;
389              
390             __END__