1use warnings;
2use strict;
3
4package Jifty::Result;
5
6=head1 NAME
7
8Jifty::Result - Outcome of running a L<Jifty::Action>
9
10=head1 DESCRIPTION
11
12C<Jifty::Result> encapsulates the outcome of running a
13L<Jifty::Action>.  Results are also stored on the framework's
14L<Jifty::Response> object.
15
16=cut
17
18
19
20use base qw/Jifty::Object Class::Accessor::Fast/;
21
22__PACKAGE__->mk_accessors(qw(failure action_class message _content));
23
24
25=head2 new
26
27Construct a new action result.  This is done automatically when the
28action is created, and can be accessed via the
29L<Jifty::Action/result>.
30
31=cut
32
33sub new {
34    my $class = shift;
35    my $self = bless {}, $class;
36
37    $self->failure(0);
38    $self->_content({});
39
40    return $self;
41}
42
43=head2 failure [BOOL]
44
45Gets or sets if the action succeeded or failed.
46
47=head2 success [BOOL]
48
49Gets or sets if the action succeeded or failed -- this is an
50alternate interface from C<failure> but has the same effect.
51
52=cut
53
54sub success {
55    my $self = shift;
56    return 0 if $self->failure(map {not $_} @_);
57    return 1;
58}
59
60=head2 action_class [MESSAGE]
61
62Returns the class for the action that this result came from.
63
64=head2 message [MESSAGE]
65
66Gets or sets the action's response message.  This is an informational
67textual description of the outcome of the action.
68
69=head2 error [ERROR]
70
71Gets or sets the action's error response.  This is an informational
72textual description of what went wrong with the action, overall.  This
73also automatically sets the result to be a L</failure>.
74
75=cut
76
77sub error {
78    my $self = shift;
79
80    $self->failure(1) if @_ and $_[0];
81    $self->{error} = shift if @_;
82    return $self->{error};
83}
84
85=head2 field_error FIELD [ERROR] [OPTIONS]
86
87Gets or sets the error string for a specific field on the action.
88This also automatically sets the result to be a failure.  C<OPTIONS>
89is an optional set of key-value pairs; the only currently supported
90option is C<force>, which sets the L</ajax_force_validate> for this
91field.
92
93=cut
94
95sub field_error {
96    my $self = shift;
97    my $field = shift;
98
99    $self->failure(1) if @_ and $_[0];
100    $self->{field_errors}{ $field } = shift if @_;
101
102    my %args = @_;
103    $self->{ajax_force_validate}{ $field } = $args{force} if exists $args{force};
104
105    return $self->{field_errors}{ $field };
106}
107
108=head2 field_errors
109
110Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
111name to error.
112
113=cut
114
115sub field_errors {
116    my $self = shift;
117    return %{$self->{field_errors} || {}};
118}
119
120=head2 field_warning FIELD [WARNING] [OPTIONS]
121
122Gets or sets the warning string for a specific field on the
123action. C<OPTIONS> is an optional set of key-value pairs; the only
124currently supported option is C<force>, which sets the
125L</ajax_force_validate> for this field.
126
127=cut
128
129sub field_warning {
130    my $self = shift;
131    my $field = shift;
132
133    $self->{field_warnings}{ $field } = shift if @_;
134
135    my %args = @_;
136    $self->{ajax_force_validate}{ $field } = $args{force} if exists $args{force};
137
138    return $self->{field_warnings}{ $field };
139}
140
141=head2 field_warnings
142
143Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
144name to warning.
145
146=cut
147
148sub field_warnings {
149    my $self = shift;
150    return %{$self->{field_warnings} || {}};
151}
152
153=head2 ajax_force_validate FIELD [VALUE]
154
155Gets or sets the flag which determines if warnings and errors are set
156using ajax validation, even if the field is empty.  By default,
157validation warnings and errors are I<not> shown for empty fields, as
158yelling to users about mandatory fields they've not gotten to yet is
159poor form.  You can use this method to force ajax errors to show even
160on empty fields.
161
162=cut
163
164sub ajax_force_validate {
165    my $self = shift;
166    my $field = shift;
167    $self->{ajax_force_validate}{ $field } = shift if @_;
168    return $self->{ajax_force_validate}{$field};
169}
170
171=head2 field_canonicalization_note FIELD [NOTE]
172
173Gets or sets a canonicalization note for a specific field on the action.
174
175=cut
176
177sub field_canonicalization_note {
178    my $self = shift;
179    my $field = shift;
180
181    $self->{field_canonicalization_notes}{ $field } = shift if @_;
182    return $self->{field_canonicalization_notes}{ $field };
183}
184
185=head2 field_canonicalization_notes
186
187Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
188name to canonicalization notes.
189
190=cut
191
192sub field_canonicalization_notes {
193    my $self = shift;
194    return %{$self->{field_canonicalization_notes} || {}};
195}
196
197=head2 content [KEY [, VALUE]]
198
199Gets or sets the content C<KEY>.  This is used when actions need to
200return values.  If not C<KEY> is passed, it returns an anonymous hash
201of all of the C<KEY> and C<VALUE> pairs.
202
203=cut
204
205sub content {
206    my $self = shift;
207
208    return $self->_content unless @_;
209
210    my $key = shift;
211    $self->_content->{$key} = shift if @_;
212    return $self->_content->{$key};
213}
214
215=head2 as_hash
216
217This returns the results as a hash to be given directly to the end user
218(usually via REST or webservices). The difference between
219C<< $result->as_hash >> and C<%$result> is that the latter will expand
220everything as deeply as possible. The former won't inflate C<refers_to>
221columns, among other things.
222
223=cut
224
225sub as_hash {
226    my $self = shift;
227
228    my $out = {
229        success        => $self->success,
230        failure        => $self->failure,
231        action_class   => $self->action_class,
232        message        => $self->message,
233        error          => $self->error,
234        field_errors   => { $self->field_errors },
235        field_warnings => { $self->field_warnings },
236        content        => $self->_recurse_object_to_data($self->content),
237    };
238
239    for (keys %{$out->{field_errors}}) {
240        delete $out->{field_errors}->{$_} unless $out->{field_errors}->{$_};
241    }
242    for (keys %{$out->{field_warnings}}) {
243        delete $out->{field_warnings}->{$_} unless $out->{field_warnings}->{$_};
244    }
245
246    return $out;
247}
248
249sub _recurse_object_to_data {
250    my $self = shift;
251    my $o = shift;
252
253    return $o if !ref($o);
254
255    if (ref($o) eq 'ARRAY') {
256        return [ map { $self->_recurse_object_to_data($_) } @$o ];
257    }
258    elsif (ref($o) eq 'HASH') {
259        my %h;
260        $h{$_} = $self->_recurse_object_to_data($o->{$_}) for keys %$o;
261        return \%h;
262    }
263
264    return $self->_object_to_data($o);
265}
266
267sub _object_to_data {
268    my $self = shift;
269    my $o = shift;
270
271    if ($o->can('jifty_serialize_format')) {
272        return $o->jifty_serialize_format($self);
273    }
274
275    # As the last resort, return the object itself and expect the
276    # $accept-specific renderer to format the object as e.g. YAML or JSON data.
277    return $o;
278}
279
2801;
281