package Bread::Board::Service::WithParameters;
our $AUTHORITY = 'cpan:STEVAN';
# ABSTRACT: Services with parameters
$Bread::Board::Service::WithParameters::VERSION = '0.34';
use Moose::Role;
use MooseX::Params::Validate qw(validated_hash);

use Bread::Board::Types;

with 'Bread::Board::Service';

has 'parameters' => (
    traits    => [ 'Hash', 'Copy' ],
    is        => 'ro',
    isa       => 'Bread::Board::Service::Parameters',
    lazy      => 1,
    coerce    => 1,
    builder   => '_build_parameters',
    handles   => {
        'has_parameters' => 'count'
    }
);

has '_parameter_keys_to_remove' => (
    is        => 'rw',
    isa       => 'ArrayRef',
    clearer   => '_clear_parameter_keys_to_remove',
    predicate => '_has_parameter_keys_to_remove',
);

before 'get' => sub {
    my $self = shift;
    my %params = $self->check_parameters(@_);
    $self->_parameter_keys_to_remove( [ keys %params ] );
    $self->params({ %{ $self->params }, %params });
};

after 'get' => sub {
    my $self = shift;
    return unless $self->_has_parameter_keys_to_remove;
    map { $self->_clear_param( $_ ) } @{ $self->_parameter_keys_to_remove };
    $self->_clear_parameter_keys_to_remove;
};

sub _build_parameters { +{} }

sub check_parameters {
    my $self = shift;
    return validated_hash(\@_, (
        %{ $self->parameters },
        # NOTE:
        # cache the parameters in a per-service
        # basis, this should be more than adequate
        # since each service can only have one set
        # of parameters at a time. If this does end
        # up breaking then we can give it a better
        # key at that point.
        # - SL
        (MX_PARAMS_VALIDATE_CACHE_KEY => Scalar::Util::refaddr($self))
    )) if $self->has_parameters;
    return ();
}

sub has_required_parameters {
    my $self = shift;
    scalar grep { ! $_->{optional} } values %{ $self->parameters };
}

sub has_parameter_defaults {
    my $self = shift;
    scalar grep { $_->{default} } values %{ $self->parameters };
}

no Moose::Role; 1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Bread::Board::Service::WithParameters - Services with parameters

=head1 VERSION

version 0.34

=head1 DESCRIPTION

This is a sub-role of L<Bread::Board::Service>, for parameterized
services. These are services that will instantiate different values
depending on parameters that are passed to the C<get> method. You can
pass those parameters via the L<< C<service_params> attribute of
C<Bread::Board::Dependency>|Bread::Board::Dependency/service_params
>>, or via the L<< C<inflate> method of
C<Bread::Board::Service::Deferred::Thunk>|Bread::Board::Service::Deferred::Thunk/inflate
>>.

=head1 ATTRIBUTES

=head2 C<parameters>

Read-only hashref, will be passed as-is to L<<
C<MooseX::Params::Validate>'s
C<validated_hash>|MooseX::Params::Validate/validated_hash >>, so you
can use things like C<optional> and C<default> in addition to type
constraints:

  service something => (
    class => 'Thing',
    parameters => {
       type => { isa => 'Str', default => 'text' },
    },
  );

This attribute uses coercions on L<<
C<Bread::Board::Service::Parameters>|Bread::Board::Types/Bread::Board::Service::Parameters
>> so that you can also say:

  service something => (
    class => 'Thing',
    parameters => ['type'],
  );

and it will be equivalent to:

  service something => (
    class => 'Thing',
    parameters => {
       type => { optional => 0 },
    },
  );

=head1 METHODS

=head2 C<has_parameters>

Predicate for the L</parameters> attribute.

=head2 C<has_parameter_defaults>

Returns true if any of the L</parameters> have a C<default> value.

=head2 C<has_required_parameters>

Returns true if any of the L</parameters> does I<not> have C<optional>
set to true.

=head2 C<check_parameters>

  my %parameters = $service->check_parameters(name1=>$value1,name2=>$value2);
  my %parameters = $service->check_parameters({name1=>$value1,name2=>$value2});

If any L</parameters> are defined, this function validates its
arguments against the parameters' definitions (using
L<MooseX::Params::Validate>). It will die if the validation fails, or
return the validated parameters (including default value) if it
succeeds.

=head2 C<get>

I<Before> the C<get> method, arguments to C<get> are passed through
L</check_parameters> and added to the L<<
C<params>|Bread::Board::Service/params >> hashref. I<After> the C<get>
method, those keys/values will be removed. In practice, this makes all
parameters available to the actual C<get> method body.

=head1 AUTHOR

Stevan Little <stevan@iinteractive.com>

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website
https://github.com/stevan/BreadBoard/issues

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Infinity Interactive.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
