Config-Model

 view release on metacpan or  search on metacpan

lib/Config/Model/AnyId.pm  view on Meta::CPAN

    traits  => ['Array'],
    handles => {
        add_check_content   => 'push',
        get_all_content_checks => 'elements',
    },
    default => sub { []; }
);

# needs_content_check defaults to 1 to trap bad data right after loading
has needs_content_check => ( is => 'rw', isa => 'Bool', default => 1 );

has has_fixes => (
    is => 'ro',
    isa => 'Num',
    default => 0,
    traits => ['Number'],
    handles => {
        inc_fixes =>   [ add => 1 ],
        dec_fixes =>   [ sub => 1 ],
        add_fixes => 'add',
        flush_fixes => [ mul => 0 ],
    }
);

# Some idea for improvement

# suggest => 'foo' or '$bar foo'
# creates a method analog to next_id (or next_id but I need to change
# run_user_command) that suggest the next id as foo_<nb> where
# nb is incremented each time, or compute the passed formula
# and performs the same

my @common_int_params = qw/min_index max_index max_nb auto_create_ids/;
has \@common_int_params => ( is => 'ro', isa => 'Maybe[Int]' );

my @common_hash_params = qw/default_with_init/;
has \@common_hash_params => ( is => 'ro', isa => 'Maybe[HashRef]' );

my @common_list_params = qw/allow_keys default_keys auto_create_keys/;
has \@common_list_params => (
    is => 'ro',
    isa => 'KeyArray',
    coerce => 1,
    default => sub { []; }
);

my @common_str_params = qw/allow_keys_from allow_keys_matching follow_keys_from
    migrate_keys_from migrate_values_from
    duplicates warn_if_key_match warn_unless_key_match/;
has \@common_str_params => ( is => 'ro', isa => 'Maybe[Str]' );

my @common_params =
    ( @common_int_params, @common_str_params, @common_list_params, @common_hash_params );
my @allowed_warp_params = ( @common_params, qw/level convert/ );

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my %args  = @_;
    my %h     = map { ( $_ => $args{$_} ); } grep { defined $args{$_} } @allowed_warp_params;
    return $class->$orig( backup => dclone( \%h ), @_ );
};

has [qw/backup cargo/] => ( is => 'ro', isa => 'HashRef', required => 1 );
has warp => ( is => 'ro', isa => 'Maybe[HashRef]' );
has [qw/morph/] => ( is => 'ro', isa => 'Bool', default => 0 );
has content_warning_list => ( is => 'rw', isa => 'ArrayRef', default => sub { []; } );
has [qw/cargo_class max_index index_class index_type/] =>
    ( is => 'rw', isa => 'Maybe[Str]' );

has config_model => (
    is       => 'ro',
    isa      => 'Config::Model',
    weak_ref => 1,
    lazy     => 1,
    builder  => '_config_model'
);

sub _config_model {
    my $self = shift;
    return $self->instance->config_model;
}

sub config_class_name {
    my $self = shift;
    return $self->cargo->{config_class_name};
}

sub BUILD {
    my $self = shift;

    croak "Missing cargo->type parameter for element " . $self->{element_name} || 'unknown'
        unless defined $self->cargo->{type};

    if ( $self->cargo->{type} eq 'node' and not $self->cargo->{config_class_name} ) {
        croak "Missing cargo->config_class_name parameter for element "
        . $self->element_name || 'unknown';
    }

    if ( $self->{cargo}{type} eq 'hash' or $self->{cargo}{type} eq 'list' ) {
        die "$self->{element_name}: using $self->{cargo}{type} will probably not work";
    }

    $self->set_properties();

    if ( defined $self->warp ) {
        $self->{warper} = Config::Model::Warper->new(
            warped_object => $self,
            %{ $self->warp },
            allowed => \@allowed_warp_params
        );
    }

    return $self;
}

# this method can be called by the warp mechanism to alter (warp) the
# feature of the Id object.
sub set_properties ($self, @args) {
    # mega cleanup
    for ( @allowed_warp_params ) { delete $self->{$_}; }

    my %args = ( %{ $self->{backup} }, @args );

    # these are handled by Node or Warper
    for ( qw/level/ ) { delete $args{$_}; }

    $logger->trace( $self->name, " set_properties called with @args" );

    for ( @common_params ) {
        $self->{$_} = delete $args{$_} if defined $args{$_};
    }

    $self->set_convert( \%args ) if defined $args{convert};

    $self-> clear_warpable_check_content;
    for ( $self-> get_all_content_checks ) {
        $self-> add_warpable_check_content($_);
    }
    for ( qw/duplicates/ ) {
        my $method = "check_$_";
        my $weak_self = $self;
        weaken($weak_self); # weaken reference loop ($self - check_content - closure - self)
        $self-> add_check_content( sub { $weak_self->$method(@_);} ) if  $self->{$_};
    }

    Config::Model::Exception::Model->throw(
        object => $self,
        error  => "Undefined index_type"
    ) unless defined $self->{index_type};

    Config::Model::Exception::Model->throw(
        object => $self,
        error  => "Unexpected index_type $self->{index_type}"
        )
        unless ( $self->{index_type} eq 'integer'
        or $self->{index_type} eq 'string' );

    my @current_idx = $self->_fetch_all_indexes();
    if (@current_idx) {
        my $first_idx = shift @current_idx;
        my $last_idx  = pop @current_idx;

        foreach my $idx ( ( $first_idx, $last_idx ) ) {
            my $ok = $self->check_idx($first_idx);
            next if $ok;

            # here a user input may trigger an exception even if fetch
            # or set value check is disabled. That's mostly because,
            # we cannot enforce more strict settings without random
            # deletion of data. For instance, if a hash contains 5
            # items and the max_nb of items is reduced to 3. Which 2
            # items should we remove ?

            # Since we cannot choose, we must raise an exception in
            # all cases.
            Config::Model::Exception::WrongValue->throw(
                error => "Error while setting id property:"
                    . join( "\n\t", @{ $self->{idx_error_list} } ),
                object => $self
            );
        }
    }

lib/Config/Model/AnyId.pm  view on Meta::CPAN

    return $self->{cargo}{$what};
}

# internal, does a grab with improved error message
sub safe_typed_grab ($self, %args) {
    my $param = $args{param} || croak "safe_typed_grab: missing param";

    my $res = eval {
        $self->grab(
            step  => $self->{$param},
            type  => $self->get_type,
            check => $args{check} || 'yes',
        );
    };

    if ($@) {
        my $e = $@;
        my $msg = $e ? $e->full_message : '';
        Config::Model::Exception::Model->throw(
            object => $self,
            error  => "'$param' parameter: " . $msg
        );
    }

    return $res;
}

sub get_default_keys {
    my $self = shift;

    if ( $self->{follow_keys_from} ) {
        my $followed = $self->safe_typed_grab( param => 'follow_keys_from' );
        my @res = $followed->fetch_all_indexes;
        return wantarray ? @res : \@res;
    }

    my @res;

    push @res, @{ $self->{default_keys} }
        if defined $self->{default_keys};

    push @res, keys %{ $self->{default_with_init} }
        if defined $self->{default_with_init};

    return wantarray ? @res : \@res;
}

sub name {
    my $self = shift;
    return $self->{parent}->name . ' ' . $self->{element_name} . ' id';
}

# internal. Handle model declaration arguments
sub handle_args ($self, %args) {
    my $warp_info = delete $args{warp};

    for (qw/index_class index_type morph ordered/) {
        $self->{$_} = delete $args{$_} if defined $args{$_};
    }

    $self->{backup} = dclone( \%args );

    $self->set_properties(%args) if defined $self->{index_type};

    if ( defined $warp_info ) {
        $self->{warper} = Config::Model::Warper->new(
            warped_object => $self,
            %$warp_info,
            allowed => \@allowed_warp_params
        );
    }

    return $self;
}

sub apply_fixes {
    my $self = shift;
    $fix_logger->trace( $self->location . ": apply_fixes called" );

    $self->deep_check( fix => 1, logger => $fix_logger );
    return;
}

my %check_idx_dispatch =
    map { ( $_ => 'check_' . $_ ); }
    qw/follow_keys_from allow_keys allow_keys_from allow_keys_matching
    warn_if_key_match warn_unless_key_match/;

my %mode_move = (
    layered => { preset => 1, normal => 1 },
    preset  => { normal => 1 },
    normal  => {},
);

around notify_change => sub ($orig, $self, %args) {
    if ($change_logger->is_trace) {
        my @a = map { ( $_ => $args{$_} // '<undef>' ); } sort keys %args;
        $change_logger->trace( "called for ", $self->name, " from ", join( ' ', caller ),
        " with ", join( ' ', @a ) );
    }

    # $idx may be undef if $self has changed, not necessarily its content
    my $idx = $args{index};
    if ( defined $idx ) {

        # use $idx to trigger move from layered->preset->normal
        my $imode = $self->instance->get_data_mode;
        my $old_mode = $self->get_data_mode($idx) || 'normal';
        $self->set_data_mode( $idx, $imode ) if $mode_move{$old_mode}{$imode};
    }

    return if $self->instance->initial_load and not $args{really};

    $self-> needs_content_check(1);
    $self->$orig(%args);
    return;
};

# the number of checks is becoming confusing. We have
# - check_idx to check whether an index is fine. This is called when creating
#   a new index



( run in 2.682 seconds using v1.01-cache-2.11-cpan-13bb782fe5a )