# This code was forked from the LiveJournal project owned and operated # by Live Journal, Inc. The code has been modified and expanded by # Dreamwidth Studios, LLC. These files were originally licensed under # the terms of the license supplied by Live Journal, Inc, which can # currently be found at: # # http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt # # In accordance with the original license, this code and all its # modifications are provided under the GNU General Public License. # A copy of that license can be found in the LICENSE file included as # part of this distribution. # class for passing around LiveJournal errors/warnings, both user-caused # and system-caused package LJ::Error; use strict; use Carp qw(croak); # helper function, aliased from LJ::errobj, but here so croak bypasses # it, since it has to be in the same package or in an @ISA package # from the caller # # Two ways to call: # # As a shortcut for constructing a new error object: # - LJ::errobj("Userpic::TooManyWords", %opts) # - LJ::errobj("BogusParams"); # this is short for LJ::Error::Userpic::TooManyWords->new(%opts) # but also sets up @ISA on LJ::Error::Userpic::TooManyWords- # # As a way to get an LJ::Error instance after an eval # that died: # eval { LJ::scary(); } # if (my $err = LJ::errobj()) { # or you can pass in errobj($@) # .... # } # # returns undef if $@ is undef, # returns LJ::Error object otherwise. specific instance is: # # LJ::Error::DieString -- for deaths with die "Error message!"; # $ljerr->die_string -- to get the string # LJ::Error::DieObject -- for deaths w/ Exception.pm/etc # $ljerr->die_object -- to get the object # LJ::Error::* -- if $@ isa LJ::Error, returns $@ unmodified # LJ::Error::Database::Failure -- if given a $dbh/$u after a failure, # will get the errstr/errmsg # sub errobj { # constructing a new LJ::Error instance. either with a classname # and args, or just a classname (no whitespace, must have one capital letter) if ( @_ > 0 && ( @_ > 1 || ( $_[0] !~ /\s/ && $_[0] =~ /[A-Z]/ && $_[0] !~ /[^:\w]/ ) ) ) { my ( $classtail, @ctor_args ) = @_; my $class = "LJ::Error::$classtail"; my $makeit = sub { $class->new(@ctor_args); }; my $val = eval { $makeit->(); }; if ( $@ =~ /^Can\'t locate object method "new" via package "(LJ::Error::\S+?)"/ ) { my $class = $1; my $code = "\@${class}::ISA = ('LJ::Error'); 1;"; eval $code or die "Failed to set ISA: [$@] for code: [$code]\n"; } return $val || $makeit->(); } # if no parameters, act like errobj($@) unless (@_) { $_[0] = $@ or return undef; } my $ref = ref $_[0]; # wrapping a database (or database-like) handle if ( LJ::DB::isdb( $_[0] ) || $ref eq "LJ::User" ) { return errobj( "Database::Failure", db => $_[0] ); } # wrapping return of an error object return errobj( "DieString", message => $_[0] ) unless $ref; # wrapping an LJ::Error object, returning it unchanged return $_[0] if $ref =~ /^LJ::Error/; # should use ->isa, but then have to catch it on HASH, etc # else it's a reference, but not one of ours, so we wrap it return errobj( "DieObject", object => $_[0] ); } # don't override this! sub new { my $class = shift; my %opts = @_; my ( $line, $file ); my $self = { _line => $line, _file => $file, }; foreach my $f ( $class->fields ) { croak("Missing field in $class ctor: '$f'") unless exists $opts{$f}; $self->{$f} = delete $opts{$f}; } foreach my $f ( $class->opt_fields ) { $self->{$f} = delete $opts{$f}; } if (%opts) { croak( "Unknown fields in $class ctor: " . join( ", ", keys %opts ) ); } return bless $self, $class; } # don't override. sub field { my ( $self, $field ) = @_; croak( "Invalid field for object " . ref $self ) unless exists $self->{$field}; return $self->{$field}; } # don't override. also aliased from LJ::throw(@throw). or an instance method. sub throw { return unless @_; if ( @_ == 1 ) { my $self = shift; croak $self; } # throw multiple errors as one my @errors = @_; LJ::errobj( "Multiple", errors => \@errors )->throw; } # throws self, if $LJ::THROW_ERRORS is dynamically set w/ local, # else returns undef (by default), or the value passed to it sub cond_throw { my $self = shift; my $ret_value = shift; $self->throw if $LJ::THROW_ERRORS; return defined $ret_value ? $ret_value : undef; } # override this: whether it was user-defined. should return 0 or 1. sub user_caused { undef } # override this: return list of required fields sub fields { (); } # override this: return list of optional fields sub opt_fields { (); } # you may override this sub as_html { my $self = shift; return $self->as_string; } sub as_bullets { my $self = shift; return "
  • " . $self->as_html . "
  • \n"; } # override this sub as_string { my $self = shift; # FIXME: show line/file/function, show some fields? maybe? that are simple values? } # automatic type returned when something dies with just a string package LJ::Error::DieString; sub fields { qw(message) } sub die_string { return $_[0]->field('message'); } sub as_string { return $_[0]->field('message'); } sub as_html { my $self = shift; # these errors (exclusively?) come from trusted pages # which generate their content... don't ehtml because # we often die with translation strings, etc which # contain markup # # -- this solution sucks, but I'm not sure how to do it # better, and a less-than-perfect API is better than # having broken output all over. :-/ return $self->die_string; } # automatic type returned when something dies with a reference, but not # an LJ::Error package LJ::Error::DieObject; sub fields { qw(object) } sub die_object { return $_[0]->field('object'); } package LJ::Error::Multiple; sub fields { qw(errors); } # arrayref of errors sub as_bullets { my $self = shift; return join( '', map { $_->as_bullets } @{ $self->{errors} } ); } package LJ::Error::WithSubError; sub fields { qw(main suberr); } sub as_bullets { my $self = shift; return $self->{main}->as_bullets . ""; } sub as_string { my $self = shift; return $self->{main}->as_string . ", due to: " . $self->{suberr}->as_string; } 1;