Jeff Schmidt wrote:
> Hello again:
>
> It sounds like we're thinking more alike than I had originally
> guessed. Please see comments below.
>
Snipping and answering the questions -- see previous messages in the
thread to get the whole context.
>
> On Oct 23, 2008, at 10:35 PM, Craig McClanahan wrote:
>
>> Jeff Schmidt wrote:
>>>
>>> On Oct 23, 2008, at 6:22 PM, Craig McClanahan wrote:
>>>
>
...
>>>> * Passing error messages in a consistent format that the client can
>>>> rely on, and
>>>> parse to good effect. For example, if your API offers a POST URI
>>>> to create a new
>>>> resource, but several of the input fields in the entity have
>>>> incorrect or missing values,
>>>> it would be nice to have a data structure that lets me include zero
>>>> or more error
>>>> messages, optionally tagged to the corresponding field names (so
>>>> that a client side
>>>> UI can present them nicely).
>>>
>>> I would like to return some entity to help out the client. My
>>> thought was to provide the entity as a class instance added to the
>>> exception constructor (FaultInfo). The code that detects the problem
>>> can populate this class to better assist the client. I would like
>>> for this entity to be marshaled in the same manner a proper response
>>> would be by JAX-RS, using @Produces/_at_Consumed to return the right
>>> format. Starting with something from the SOAP side of things, I
>>> defined this class:
>>>
>>> @XmlRootElement(name = "Error")
>>> @XmlType(name = "ErrorType")
>>> @XmlAccessorType(XmlAccessType.FIELD)
>>> public class FaultInfo {
>>>
>>> @XmlElement(name = "Code")
>>> protected ErrorCode _errorCode;
>>> @XmlElement(name = "Message")
>>> protected String _message;
>>> @XmlElement(name = "Resource")
>>> protected String _resource;
>>> @XmlElement(name = "DevInfo")
>>> protected String _devInfo;
>>>
>>> public FaultInfo() {
>>> }
>>>
>>> public FaultInfo(ErrorCode errorCode) {
>>> _errorCode = errorCode;
>>> _message = errorCode.getMessage();
>>> }
>>>
>>> public FaultInfo(ErrorCode errorCode, String resource) {
>>> this(errorCode);
>>> _resource = resource;
>>> }
>>>
>>> public FaultInfo(ErrorCode errorCode, String resource, String devInfo) {
>>> this(errorCode, resource);
>>> _devInfo = devInfo;
>>> }
>>>
>>> public ErrorCode getErrorCode() {
>>> return _errorCode;
>>> }
>>>
>>> public String getResource() {
>>> return _resource;
>>> }
>>> }
>>>
Hmm ... I think we rapidly reach a point where a fixed set of fields
describing an error message will always seem like overkill for some use
cases, and not enough for others. It'll be interesting to consider
whether we can generalize the set of information carried along in
interesting ways.
Leveraging the providers (produces/consumes) for error-specific entity
data might be an interesting approach, but I'm betting we'd end up
wanting multipart message formats so that the client who receives this
would be able to parse it more easily.
...
>> The properties I've currently included in a Message bean (what
>> actually gets serialized is a collection of such beans, so you can
>> have zero or more messages) are:
>> * text -- Localized text for this message (required)
>> * code -- String identifier to look a particular error up in some
>> catalog (optional)
>> * field -- Name of the field this error is associated wth (optional;
>> default interpretation is "global error")
> Does the field refer to an input parameter (@PathParam, @QueryParam)
> as well as some XML element or attribute for any uploaded entity? I'm
> not sure how general you can make this.
>
I didn't specifically define what "field" means, but the intuitive
concept is that it identifies an individual chunk of data from the
request. For an HTTP form post, that would literally be the field
name. For an XML or JSON representation, it would be the name of an XML
element (or a JSON field). The important thing is that the client needs
to know the mappings for *this* particular request type ... but it
should generally be pretty obvious.
>> * stackTrace -- If error caused by an exception, a place to include a
>> stack trace
>> (although our best practices recommend *not* including this for
>> messages visible
>> to a third party client, because stack traces can reveal a lot
>> about your internal architecture)
> I agree. Being able to switch this on for development mode would be
> very helpful. But, in production, this stuff must not get out.
>
Yep.
>> * hint -- Mechanism for the server to describe potential workarounds
>> that might
>> be displayed by a client UI (optional)
> Do you see that as text to be more or less directly displayed by the UI.
Yes.
> Would it be localized too?
>
Yep ... it should also be localized. And, by "localized", I'm referring
to doing so based on the *client* settings (specified by the
Accept-Language header), not by the server domain.
...
>>>
>>> This all does seem a bit awkward, and as you can see only a single
>>> fault is being reported.
>> My validation exception class has a property of type
>> Map<String,List<String>> where the key is the field name (empty
>> string for "global" validation errors) and the value is zero or more
>> localized messages related to this particular field.
> That sounds more generally useful then what I've provided above. :)
>
Julio Faerman raises a different scenario on the mailing list ... what
if you're doing multiple transactions in a batch processing style? You
might really want to report multiple "exceptions". As I mentioned in my
response to him, I haven't run into that use case in our server code,
but it's certainly something interesting to think about.
>>>> The library would provide an exception mapper that enforces our
>>>> "best practice" policy to use an HTTP 404 "Not Found" status for
>>>> this condition, and package an appropriate message body containing
>>>> the message text, in a standard XML or JSON format.
>>>
>>> I'm very interested in seeing what you have in mind. I don't know if
>>> you'll get it into Jersey to meet my timeframe, but I am interested
>>> in learning about your approach and perhaps implementing something
>>> similar for now, and using it directly in Jersey 1.1 or whenever
>>> you've integrated it.
>>>
...
>> Cool ... I'll take a crack at this fairly soon, and we can spend some
>> time refining it. If you're using Maven to build, you'll be able to
>> declare dependencies on the snapshots if you're willing to deal with
>> that ... they will get published by the Hudson builds.
> Great! Our project is still based on Ant, no Maven yet. I guess I can
> use Maven to build your snapshots and then use the resulting jars in
> our Ant build.
>
Yep. The Maven project also provides a set of Ant tasks that can do the
"download the right version from the right repository" trick, and still
fit things into an Ant build environment. I also understand that Ivy
can do this sort of thing directly.
Craig