1*5486feefSafresh1package Test2::Compare::Hash;
2*5486feefSafresh1use strict;
3*5486feefSafresh1use warnings;
4*5486feefSafresh1
5*5486feefSafresh1use base 'Test2::Compare::Base';
6*5486feefSafresh1
7*5486feefSafresh1our $VERSION = '0.000162';
8*5486feefSafresh1
9*5486feefSafresh1use Test2::Util::HashBase qw/inref meta ending items order for_each_key for_each_val/;
10*5486feefSafresh1
11*5486feefSafresh1use Carp qw/croak confess/;
12*5486feefSafresh1use Scalar::Util qw/reftype/;
13*5486feefSafresh1
14*5486feefSafresh1sub init {
15*5486feefSafresh1    my $self = shift;
16*5486feefSafresh1
17*5486feefSafresh1    if( defined( my $ref = $self->{+INREF} ) ) {
18*5486feefSafresh1        croak "Cannot specify both 'inref' and 'items'" if $self->{+ITEMS};
19*5486feefSafresh1        croak "Cannot specify both 'inref' and 'order'" if $self->{+ORDER};
20*5486feefSafresh1        $self->{+ITEMS} = {%$ref};
21*5486feefSafresh1        $self->{+ORDER} = [sort keys %$ref];
22*5486feefSafresh1    }
23*5486feefSafresh1    else {
24*5486feefSafresh1        # Clone the ref to be safe
25*5486feefSafresh1        $self->{+ITEMS} = $self->{+ITEMS} ? {%{$self->{+ITEMS}}} : {};
26*5486feefSafresh1        if ($self->{+ORDER}) {
27*5486feefSafresh1            my @all = keys %{$self->{+ITEMS}};
28*5486feefSafresh1            my %have = map { $_ => 1 } @{$self->{+ORDER}};
29*5486feefSafresh1            my @missing = grep { !$have{$_} } @all;
30*5486feefSafresh1            croak "Keys are missing from the 'order' array: " . join(', ', sort @missing)
31*5486feefSafresh1                if @missing;
32*5486feefSafresh1        }
33*5486feefSafresh1        else {
34*5486feefSafresh1            $self->{+ORDER} = [sort keys %{$self->{+ITEMS}}];
35*5486feefSafresh1        }
36*5486feefSafresh1    }
37*5486feefSafresh1
38*5486feefSafresh1    $self->{+FOR_EACH_KEY} ||= [];
39*5486feefSafresh1    $self->{+FOR_EACH_VAL} ||= [];
40*5486feefSafresh1
41*5486feefSafresh1    $self->SUPER::init();
42*5486feefSafresh1}
43*5486feefSafresh1
44*5486feefSafresh1sub name { '<HASH>' }
45*5486feefSafresh1
46*5486feefSafresh1sub meta_class  { 'Test2::Compare::Meta' }
47*5486feefSafresh1
48*5486feefSafresh1sub verify {
49*5486feefSafresh1    my $self = shift;
50*5486feefSafresh1    my %params = @_;
51*5486feefSafresh1    my ($got, $exists) = @params{qw/got exists/};
52*5486feefSafresh1
53*5486feefSafresh1    return 0 unless $exists;
54*5486feefSafresh1    return 0 unless defined $got;
55*5486feefSafresh1    return 0 unless ref($got);
56*5486feefSafresh1    return 0 unless reftype($got) eq 'HASH';
57*5486feefSafresh1    return 1;
58*5486feefSafresh1}
59*5486feefSafresh1
60*5486feefSafresh1sub add_prop {
61*5486feefSafresh1    my $self = shift;
62*5486feefSafresh1    $self->{+META} = $self->meta_class->new unless defined $self->{+META};
63*5486feefSafresh1    $self->{+META}->add_prop(@_);
64*5486feefSafresh1}
65*5486feefSafresh1
66*5486feefSafresh1sub add_field {
67*5486feefSafresh1    my $self = shift;
68*5486feefSafresh1    my ($name, $check) = @_;
69*5486feefSafresh1
70*5486feefSafresh1    croak "field name is required"
71*5486feefSafresh1        unless defined $name;
72*5486feefSafresh1
73*5486feefSafresh1    croak "field '$name' has already been specified"
74*5486feefSafresh1        if exists $self->{+ITEMS}->{$name};
75*5486feefSafresh1
76*5486feefSafresh1    push @{$self->{+ORDER}} => $name;
77*5486feefSafresh1    $self->{+ITEMS}->{$name} = $check;
78*5486feefSafresh1}
79*5486feefSafresh1
80*5486feefSafresh1sub add_for_each_key {
81*5486feefSafresh1    my $self = shift;
82*5486feefSafresh1    push @{$self->{+FOR_EACH_KEY}} => @_;
83*5486feefSafresh1}
84*5486feefSafresh1
85*5486feefSafresh1sub add_for_each_val {
86*5486feefSafresh1    my $self = shift;
87*5486feefSafresh1    push @{$self->{+FOR_EACH_VAL}} => @_;
88*5486feefSafresh1}
89*5486feefSafresh1
90*5486feefSafresh1sub deltas {
91*5486feefSafresh1    my $self = shift;
92*5486feefSafresh1    my %params = @_;
93*5486feefSafresh1    my ($got, $convert, $seen) = @params{qw/got convert seen/};
94*5486feefSafresh1
95*5486feefSafresh1    my @deltas;
96*5486feefSafresh1    my $items = $self->{+ITEMS};
97*5486feefSafresh1    my $each_key = $self->{+FOR_EACH_KEY};
98*5486feefSafresh1    my $each_val = $self->{+FOR_EACH_VAL};
99*5486feefSafresh1
100*5486feefSafresh1    # Make a copy that we can munge as needed.
101*5486feefSafresh1    my %fields = %$got;
102*5486feefSafresh1
103*5486feefSafresh1    my $meta     = $self->{+META};
104*5486feefSafresh1    push @deltas => $meta->deltas(%params) if defined $meta;
105*5486feefSafresh1
106*5486feefSafresh1    for my $key (@{$self->{+ORDER}}) {
107*5486feefSafresh1        my $check  = $convert->($items->{$key});
108*5486feefSafresh1        my $exists = exists $fields{$key};
109*5486feefSafresh1        my $val    = delete $fields{$key};
110*5486feefSafresh1
111*5486feefSafresh1        if ($exists) {
112*5486feefSafresh1            for my $kcheck (@$each_key) {
113*5486feefSafresh1                $kcheck = $convert->($kcheck);
114*5486feefSafresh1
115*5486feefSafresh1                push @deltas => $kcheck->run(
116*5486feefSafresh1                    id      => [HASHKEY => $key],
117*5486feefSafresh1                    convert => $convert,
118*5486feefSafresh1                    seen    => $seen,
119*5486feefSafresh1                    exists  => $exists,
120*5486feefSafresh1                    got     => $key,
121*5486feefSafresh1                );
122*5486feefSafresh1            }
123*5486feefSafresh1
124*5486feefSafresh1            for my $vcheck (@$each_val) {
125*5486feefSafresh1                $vcheck = $convert->($vcheck);
126*5486feefSafresh1
127*5486feefSafresh1                push @deltas => $vcheck->run(
128*5486feefSafresh1                    id      => [HASH => $key],
129*5486feefSafresh1                    convert => $convert,
130*5486feefSafresh1                    seen    => $seen,
131*5486feefSafresh1                    exists  => $exists,
132*5486feefSafresh1                    got     => $val,
133*5486feefSafresh1                );
134*5486feefSafresh1            }
135*5486feefSafresh1        }
136*5486feefSafresh1
137*5486feefSafresh1        push @deltas => $check->run(
138*5486feefSafresh1            id      => [HASH => $key],
139*5486feefSafresh1            convert => $convert,
140*5486feefSafresh1            seen    => $seen,
141*5486feefSafresh1            exists  => $exists,
142*5486feefSafresh1            $exists ? (got => $val) : (),
143*5486feefSafresh1        );
144*5486feefSafresh1    }
145*5486feefSafresh1
146*5486feefSafresh1    if (keys %fields) {
147*5486feefSafresh1        for my $key (sort keys %fields) {
148*5486feefSafresh1            my $val = $fields{$key};
149*5486feefSafresh1
150*5486feefSafresh1            for my $kcheck (@$each_key) {
151*5486feefSafresh1                $kcheck = $convert->($kcheck);
152*5486feefSafresh1
153*5486feefSafresh1                push @deltas => $kcheck->run(
154*5486feefSafresh1                    id      => [HASHKEY => $key],
155*5486feefSafresh1                    convert => $convert,
156*5486feefSafresh1                    seen    => $seen,
157*5486feefSafresh1                    got     => $key,
158*5486feefSafresh1                    exists  => 1,
159*5486feefSafresh1                );
160*5486feefSafresh1            }
161*5486feefSafresh1
162*5486feefSafresh1            for my $vcheck (@$each_val) {
163*5486feefSafresh1                $vcheck = $convert->($vcheck);
164*5486feefSafresh1
165*5486feefSafresh1                push @deltas => $vcheck->run(
166*5486feefSafresh1                    id      => [HASH => $key],
167*5486feefSafresh1                    convert => $convert,
168*5486feefSafresh1                    seen    => $seen,
169*5486feefSafresh1                    got     => $val,
170*5486feefSafresh1                    exists  => 1,
171*5486feefSafresh1                );
172*5486feefSafresh1            }
173*5486feefSafresh1
174*5486feefSafresh1            # if items are left over, and ending is true, we have a problem!
175*5486feefSafresh1            if ($self->{+ENDING}) {
176*5486feefSafresh1                push @deltas => $self->delta_class->new(
177*5486feefSafresh1                    dne      => 'check',
178*5486feefSafresh1                    verified => undef,
179*5486feefSafresh1                    id       => [HASH => $key],
180*5486feefSafresh1                    got      => $val,
181*5486feefSafresh1                    check    => undef,
182*5486feefSafresh1
183*5486feefSafresh1                    $self->{+ENDING} eq 'implicit' ? (note => 'implicit end') : (),
184*5486feefSafresh1                );
185*5486feefSafresh1            }
186*5486feefSafresh1        }
187*5486feefSafresh1    }
188*5486feefSafresh1
189*5486feefSafresh1    return @deltas;
190*5486feefSafresh1}
191*5486feefSafresh1
192*5486feefSafresh11;
193*5486feefSafresh1
194*5486feefSafresh1__END__
195*5486feefSafresh1
196*5486feefSafresh1=pod
197*5486feefSafresh1
198*5486feefSafresh1=encoding UTF-8
199*5486feefSafresh1
200*5486feefSafresh1=head1 NAME
201*5486feefSafresh1
202*5486feefSafresh1Test2::Compare::Hash - Representation of a hash in a deep comparison.
203*5486feefSafresh1
204*5486feefSafresh1=head1 DESCRIPTION
205*5486feefSafresh1
206*5486feefSafresh1In deep comparisons this class is used to represent a hash.
207*5486feefSafresh1
208*5486feefSafresh1=head1 SOURCE
209*5486feefSafresh1
210*5486feefSafresh1The source code repository for Test2-Suite can be found at
211*5486feefSafresh1F<https://github.com/Test-More/Test2-Suite/>.
212*5486feefSafresh1
213*5486feefSafresh1=head1 MAINTAINERS
214*5486feefSafresh1
215*5486feefSafresh1=over 4
216*5486feefSafresh1
217*5486feefSafresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt>
218*5486feefSafresh1
219*5486feefSafresh1=back
220*5486feefSafresh1
221*5486feefSafresh1=head1 AUTHORS
222*5486feefSafresh1
223*5486feefSafresh1=over 4
224*5486feefSafresh1
225*5486feefSafresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt>
226*5486feefSafresh1
227*5486feefSafresh1=back
228*5486feefSafresh1
229*5486feefSafresh1=head1 COPYRIGHT
230*5486feefSafresh1
231*5486feefSafresh1Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
232*5486feefSafresh1
233*5486feefSafresh1This program is free software; you can redistribute it and/or
234*5486feefSafresh1modify it under the same terms as Perl itself.
235*5486feefSafresh1
236*5486feefSafresh1See F<http://dev.perl.org/licenses/>
237*5486feefSafresh1
238*5486feefSafresh1=cut
239