=head1 NAME

Config::Strict - Add strict name- and type-checking to configuration data

=head1 VERSION

0.07 (alpha release)

=head1 SYNOPSIS

    use Config::Strict;
    use Declare::Constraints::Simple -All;               # For custom checks

    my $config = Config::Strict->new( {
        params => {                                      # Parameter types & names
            Bool     => [ qw( my_bool1 my_bool2 ) ],     # Multiple parameters
            Int      => 'my_i',                          # Single parameter
            Num      => 'my_n',
            HashRef  => 'my_href',
            Enum     => { 
                my_enum => [ qw( v1 v2 ), undef ] 
            },
            Anon   => {                                  # anonymous profiles
                my_pos2 =>                               # Positive number
                    And( IsNumber, Matches( qr/^[^-]+$/ ) ),
                my_nest => IsA( 'Config::Strict' ),      # Nested configuration
            }
        },
        required => [ qw( my_bool1 my_n ) ],             # Required parameters
        defaults => {                                    # Default values
            my_bool1    => 1,
            my_enum     => 'e2',
            my_n        => -1.1,
            my_pos2     => 1_000,
        },
    } );
    
    # Access and change the data
    
    # Retrieve a single value
    $got = $config->get( 'my_n' );                # $got = -1.1
    
    # Retrieve a list of values
    @got = $config->get( qw( my_bool1 my_n ) );   # @got = ( 1, -1.1 )
    
    # Set multiple parameters
    $config->set( my_bool1 => 1, 'my_pos2' => 2 );   
    
    # Unset parameters
    $config->unset( 'my_n' );
    $config->param_is_set( 'my_n' );              # false
    
    # The following will die:
    $config->get( 'foo' );                # foo doesn't exist
    $config->set( 'my_i' => 2.2 );        # my_i must be an integer
    $config->set( 'my_pos2' => -5 );      # my_pos2 must be positive
    $config->unset( 'my_n' );             # my_n is required
    
=head1 DESCRIPTION

Config::Strict wraps L<Declare::Constraints::Simple> to enable strict parameter name- and type-checking on configuration data. That is, it will complain anytime an attempt is made to access a parameter with an invalid name or type; or if an attempt is made to unset a required parameter. Both built-in and custom types can be used to build a validation profile for the entire configuration.

This module is meant to be used alongside any configuration parser that hashes configuration data.

See L<Declare::Constraints::Simple::Library> for an index to available constraints.

=head1 CONSTRUCTING A TYPE SYSTEM

Declare the configuration profile during construction:

=head2 new

    $config = Config::Strict->new( \%config_profile );

C<%config_profile> is a multi-level hash with the following top-level keys:

=over 4

=item B<params> (Required)

Points to the hash of parameter types and names, where the keys are the 
built-in Config::Strict types (more below).

The values are either a single parameter name or an arrayref of parameter names,
with the exception of the special types C<Enum> and C<Anon> which point to a 
uniquely defined hashref.

Any parameter not named here cannot be added later - any attempt to L<access a
parameter|/"GETTING AND SETTING PARAMETER VALUES"> that is not provided in the 
constructor will result in an error.

Built-In Parameter Types:

=over 4

=item B<Bool> => $param | [ $param1, $param2, ... ]

Parameters taking the value 0 or 1.

=item B<Int> => $param | [ $param1, $param2, ... ]

Integer parameters.

=item B<Num> => $param | [ $param1, $param2, ... ]

Generic number parameters.

=item B<Str> => $param | [ $param1, $param2, ... ]

Generic string parameters.

=item B<Enum> => { $enum1 => [ $val1, $val2, ... ], ... }

Enumerated parameters. The C<Enum> key points to a hashref with parameter-values pairs, where valid values for the enumerated parameter are provided as an arrayref.

It may be cleaner to instead L<register your own named enum|"USER TYPE REGISTRATION"> using C<IsOneOf>. (more below)

This, along with B<Anon>, is the only exception to the normal declaration syntax.

=item B<Regexp> => $param | [ $param1, $param2, ... ]

Compiled regexp parameters (with C<qr//>).

=item B<ArrayRef> => $param | [ $param1, $param2, ... ]

Generic list parameters.

=item B<HashRef> => $param | [ $param1, $param2, ... ]

Generic hash parameters.

=item B<CodeRef> => $param | [ $param1, $param2, ... ]

Generic code parameters.

=item B<Anon> => { $param1 => $profile1, ... }

Anonymous profiles. The C<Anon> key points to a hashref with parameter-profile pairs, where profiles are declared via imported L<Declare::Constraints::Simple> functions. These code profiles will in fact return a L<Declare::Constraints::Simple::Result> which evaluate properly in boolean context along with other additional information when used as an object.

Note that, unlike registered profiles (below), anonymous code blocks cannot be used.

=back

=item B<required> (Optional)

Points to a arrayref of parameter names that must have valid values at all times.
These parameters must also be given a default value.

The special value '*' (the literal scalar value; not an arrayref) is a shortcut to specify that all parameters are to be required.

=item B<defaults> (Optional)

Points to a hashref of default values to any number of parameters. 
Those parameters listed in C<required> I<must> be present in this section.

=back

=head2 USER TYPE REGISTRATION

Aside from the default type built-ins (Bool,Int,...) you can register your own named types to streamline construction and add your own validation logic:

=head3 register_types

    Config::Strict->register_types( 
        $name1 => $constraint1, 
        $name2 => $constraint2, 
        ... 
    );

An example:

	use Date::Calc qw(Decode_Month);	# For 'FirstQuarter'
	
	# Register user-defined types...
	Config::Strict->register_types(
	    
	    # Normal profiles:
	    'Private'        => Matches( qr/^_/ ),
	    'ArrayOfAllCaps' => IsArrayRef( Matches( qr/^[A-Z]+$/ ) ),
	    'LessThan8Chars' => HasLength( 1, 8 ),
	    'Size'           => IsOneOf( qw( small medium large ) ),
	    
	    # Custom routines:
	    'FirstQuarter'   => sub { Decode_Month($_[0]) <= 4 },
	    
	    ...
	);
	
	# ...and use them to make a configuration profile:
	my $config = Config::Strict->new( {
		params => {
		    Private        => 'myvar',
		    ArrayOfAllCaps => 'caps',
		    LessThan8Chars => [ qw( short1 short2 ) ],
		    Size           => 'beverage',
		    FirstQuarter   => 'date_q1',
		    ...
		},
		...
    } );

The parameters syntax is the same for that of any of the existing built-ins that take either a single name or arrayref of names. More importantly, they can be used in the same way.

Bare code blocks are automatically converted to named L<Declare::Constraints::Simple> profiles so you can easily provide your own validation logic.

Names cannot duplicate an existing registered type name.

=head1 METHODS

=head2 GETTING AND SETTING PARAMETER VALUES

These methods will die with a stack trace if a given parameter doesn't exist in the configuration profile, or if an attempt is made to set a parameter with an invalid value.

=head3 get

    @values = $config->get( $param1, $param2, ... );

Returns the list of values corresponding to each parameter.

=head3 set

    $config->set( $param1 => $value1, $param2 => $value2, ... );

Sets each parameter-value configuration pair.

=head3 unset

    $config->unset( $param1, $param2, ... );

Unsets each parameter; internally deletes the parameter-value pair from the underlying parameter hash.

Required parameters cannot be unset.

=head2 PARAMETER CHECKING

The following methods can be used to check conditions on parameters without killing the program.

=head3 validate

    $ok = $config->validate( $param1 => $value1, $param2 => $value2, ... );

Returns true if all parameter-value pairs are valid; false otherwise.

=head3 param_exists

    $ok = $config->param_exists( $param );

Returns true if C<$param> exists in the configuration; false otherwise.

=head3 param_is_set

    $ok = $config->param_is_set( $param );

Returns true if C<$param> has been set; false otherwise.

=head3 param_is_required

    $ok = $config->param_is_required( $param );

Returns true if C<$param> is a required parameter; false otherwise.

=head2 OTHER METHODS

=head3 all_params

    @params = $config->all_params();

Returns the list of all parameters in the configuration.

=head3 all_set_params

    @params = $config->all_set_params();

Returns the list of all set parameters in the configuration.

=head3 param_hash

    %data = $config->param_hash();

Returns a copy of the underlying configuration data as a list of key-value pairs.

=head3 param_array

    @data = $config->param_array();

Returns a copy of the underlying configuration data as an array of array references.

=head3 get_profile

    $profile = $config->get_profile( $param );

Returns the L<Declare::Constraints::Simple> profile used to validate C<$param>, or C<undef> if C<$param> isn't in the profile.

=head1 EXTENDING THE DEFAULT TYPES (OVERVIEW)

There are several ways to make your type system even more strict than the
built-in parameter types:

=over 4

=item 1. Using L</register_types> to register your own class-wide types.

=item 2. Using the L<Anon|/new> key and combining any number of 
C<Declare::Constraints::Simple> profiles.

=item 3. Subclassing Config::Strict and overloading any of the C<_get_check>, C<_set_check>, and C<_unset_check> validation methods to add your own general validation semantics. These methods are executed before L</get>, L</set>, and L</unset>, respectively.

=back

=head1 SEE ALSO

L<Declare::Constraints::Simple>

=head1 CAVEATS

This is an alpha release - the API is subject to change.

=head1 AUTHOR

Blake Willmarth

bwillmarth at gmail.com

=head1 BUGS

Please report any bugs or feature requests to C<bug-config-strict at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Config-Strict>.  I will be notified, and then you'll automatically be notified of progress on your bug as changes are made.

=head1 LICENSE AND COPYRIGHT

Copyright 2010 Blake Willmarth.

This program is free software; you can redistribute it and/or
modify it under the terms of either:

=over 4

=item * the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or

=item * the Artistic License version 2.0.

=back

=cut
