[Templates] Error Handling
Jonas Liljegren
jonas@paranormal.se
Mon, 14 Aug 2000 21:17:37 +0200 (CEST)
I will describe elements of the program I'm working on right now, in
order to discuss diffrent ways of error handling with the Template
Toolkit.
My current project
==================
I have created "Yet another online news system". The result is this
site. (Maby we should compile a list of sites using TT?):
http://www.tradewinds.no/
The journalists are using the administration pages to construct the
news items. The administration pages uses TT to create the HTML forms
and display the result.
The resulting stories are static HTML pages generated by another set
of templates (and another template object).
The (admin) system consists of one program "livenews.mpar" to be run
with the mod_perl Apache::Registry (1638 lines), one configuration
file "tables.config" defining the table fields (281 lines) and a
little above 30 template files.
The design philosophy
=====================
The system is a simplified version of the atempt I did on a
"aplication framework" using TT. The philosofy was to devide
application construction i three parts:
1) The data model, describing all the information. It describes the
connection between tables, declare validation rules, primary keys,
lables, and more. It works with a DB system.
2) The driver program takes any data model and provides the general
functions for it. The goel is to not have any application
specific code at all.
3) The templats. The default templates is enough to provide an
interface to a database descibed by the data model. The diffrent
data types and connection cardinalities will descide the template
fragments used to construct the interface. But new templates can
override the default ones and give specialized presentation of the
data.
The ACTION / HANDLER divition
=============================
The client controls the application behaviour through the query
string, either by HTML forms or links. The two most important
variables are:
action: What is to be done with the information submitted from the
client?
handler: What do you want to see now? This is the template that
should handle the request for information.
These two variables constitutes the basic operation of the program.
Let's say you just have completed a form for a new entry in a customer
database. You push the "create" button and expect the program to 1)
store the record in the db, and 2) take you to the page for adding
information connected to the customer, such as a list of contact
persons for the customer.
Every page has a $result variable that will tell you what happend with
your requested action. If the record was successfully created, a note
will say so in the result part of the next page. But if you didn't
fill in a required field in the form, the access function will return
an error message and you will return to the same page instead of the
requested handler page.
The event loop
--------------
This is the layout of the main "event loop":
- Initialize
- Load tables.config
- Parse input
- Set up the $params
- do action if $action
- Set $handler
- Process template
The functions
-------------
The system consists of three types of functions/methods:
1) Template functions, called from within the handler, only to
retrieve and digest information for display.
2) Action functions, for taking actions on the submitted data. This
is generalized functions for create, edit, delete, records, based
on the data model.
3) Internal helper functions
The template
------------
Below is a part of a typical template in teh system. input() is a
template function used to create input forms. One feature of this
function is that it will use coulors to highlite the fields with
incorrect values after a submission, based on data from the last
action function validation of the fields.
<table>
<tr>
<td>Title</td>
<td>[% input( name="data/livenews/$rev/title"
value=news.title
size=60)
%]</td>
</tr>
<tr>
<td valign="top">Preamble</td>
<td>[% input( type="textarea"
cols=60
rows=5
wrap="virtual"
name="data/livenews/$rev/preamble"
value=news.preamble)
%]</td>
</tr>
</table>
The "data/livenews/$rev/title" names are the generalized way of
accessing data. It referes to the field 'title' defined in the data
model, as belonging to the 'livenews' table, with the primary key
$rev.
This is, as said, the simplified version. A previous version was
completley object oriented and automated the nameing of the input
field and especially the value of the field. The value could be a)
empty, b) determined by selections from the previous page, c) the
default value as specified in the data model, d) the present value in
the database record, e) a default value specified in the template or
f) the value previously enterd in this field. The truble of
automaticly making the right choise was one reason for the
simplified version.
Error handling
==============
The system was written with TT1. I have recently tried to change the
error handling. I will describe both methods.
Errors can come from 1) the action function or 2) the template
functions:
Action errors
a) DBI/SQL errors
b) Invalid data (form incomplete)
c) Other problems with the requested action
Template errors
a) DBI/SQL errors
b) Errors in the template syntax
c) Other errors from the template functions
TT v1 error handling
--------------------
All errors will generate a Template::Exception. The intresting part
is how the action function report the error to the handler template,
*before* the template is processed.
All pages has the "<b>$result</b>" part in them. This should either
report the result of the action, such as an "Record created" message,
or throw an error, cathed by the designated block:
[%- CATCH action %]
[% INCLUDE box_start border='red' %]
<h1>Action error</h1>
<p>$e.info
[% INCLUDE box_end %]
[% END -%]
This is the actual part of the program calling the action function and
sets the @result:
my $action = $q->param('action');
if( $action )
{
eval
{
no strict 'refs';
$result[0] = &{'do_'.$action};
### Other info is stored in $info
1;
} or $result[1] = &exception($@);
}
&exception($@) returns the correct Template::Exception object. The
result value is set like this:
$params =
{
...
'result' => sub {@result},
...
}
We decide upon which handler to use, based on the action result:
$handler = $q->param('previous') if $result[1];
$handler ||= ($q->param('handler')||'menu');
$handler .='.html';
Finaly, we process and output the result of the handler template.
This uses the "error.html" template to report any errors in the
processing of the template, and as a final resort, just dies with the
error information:
print $q->header;
$template->process($handler, $params)
or do
{
my $error = Template::Exception->new('template',
$template->error() );
$template->process('error.html',
{result=>sub{(undef,$error)}})
or die( "This is hopeless: ".$template->error()."\n");
};
That means that the processing of the $result variable in the template
will either display the action result or generate the correct
exception, resulting in the big eyecatching error response.
This workds, but it doesn't feel like the easiest possible solution.
TT v2 error handling
--------------------
I asked myself: "Why use the catch mechanism when I only want to
display a result message?"
But I still want the design of the response handled by templates. So I
substituted the "<b>$result</b>" part of the tempaltes with a "[%
result | eval %]" (in a header used by all handler).
The $result variable is now used to hold a generated template, amking
it possible to return multiple error messages combined with normal
action result messages:
my $action = $q->param('action');
if( $action )
{
eval
{
no strict 'refs';
$result .= &{'do_'.$action};
### Other info is stored in $info
1;
}
or do
{
$result .= &exception($@);
$error = 1;
}
}
The &exception($@) function calles my new &error function and returns
its template, instead of a Template::Exception. This is a shortend
version of the function:
sub error
{
my( $type, $message ) = @_;
$error_types ||=
{
'dbi' =>
{
'title' => 'Database error',
'border' => 'red',
'bg' => 'AAAAAA',
},
};
my $params = $error_types->{$type} || {};
$params->{'title'} ||= $type;
$params->{'message'} = $message;
my $param_str = "";
foreach my $key ( keys %$params )
{
# Escape value
my $value = $params->{$key};
$value =~ s/\\/\\\\/g;
$value =~ s/\"/\\\"/g;
$value =~ s/\[\%/\[ \%/g;
$value =~ s/\%\]/\% \]/g; # Cant have a %] in the string
$param_str .= qq($key="$value" );
}
return qq([% box( $param_str ) %]);
}
And it calles the box MACRO:
[% MACRO box BLOCK %]
[% DEFAULT border='black' %]
[% DEFAULT bg='#AAAAFF' %]
[% DEFAULT width=3 %]
[% DEFAULT message='Unexplained situation' %]
<table border=0 cellpadding=0 cellspacing=0 bgcolor="$border"><tr><td>
<table border="$width" cellpadding=10><tr><td bgcolor="$bg"><pre>
[% IF title %]<h1>$title</h1>[% END %]
$message
</pre></table></table>
[% END %]
Ok. This works too. But I'm still not happy whith it. I don't like to
construct a template and evaluate it. Would rather construct an result
object or something that the template processor could use to format
the result message.
The Question
============
And here it comes: How would you do this?
More precisly: What is a smart strategy of taking the result of all
sorts of actions into the template for report formatting?
(Just writing this gave me some new ideas, but I should not make this
any longer now. ;-)
--
/ Jonas - http://jonas.liljegren.org/myself/en/index.html