Patchwork [02/11] qerror: expose a function to format an error

login
register
mail settings
Submitter Anthony Liguori
Date March 11, 2011, 9 p.m.
Message ID <1299877249-13433-3-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/86452/
State New
Headers show

Comments

Anthony Liguori - March 11, 2011, 9 p.m.
This will let Error share the QError human formatting.  This is only used for
HMP.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Anthony Liguori - March 11, 2011, 9:08 p.m.
On 03/11/2011 03:00 PM, Anthony Liguori wrote:
> This will let Error share the QError human formatting.  This is only used for
> HMP.
>
> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>
> diff --git a/qerror.c b/qerror.c
> index 4855604..13d53c9 100644
> --- a/qerror.c
> +++ b/qerror.c
> @@ -326,12 +326,18 @@ QError *qerror_from_info(const char *file, int linenr, const char *func,
>       return qerr;
>   }
>
> -static void parse_error(const QError *qerror, int c)
> +static void parse_error(const QErrorStringTable *entry, int c)
>   {
> -    qerror_abort(qerror, "expected '%c' in '%s'", c, qerror->entry->desc);
> +#if 0
> +    qerror_abort(qerror, "expected '%c' in '%s'", c, entry->desc);
> +#else
> +    fprintf(stderr, "expected '%c' in '%s'", c, entry->desc);
> +    abort();
> +#endif
>   }

Err, I shouldn't have left these #if 0's in here.  Please ignore them.

Regards,

Anthony Liguori

> -static const char *append_field(QString *outstr, const QError *qerror,
> +static const char *append_field(QDict *error, QString *outstr,
> +                                const QErrorStringTable *entry,
>                                   const char *start)
>   {
>       QObject *obj;
> @@ -339,24 +345,31 @@ static const char *append_field(QString *outstr, const QError *qerror,
>       QString *key_qs;
>       const char *end, *key;
>
> -    if (*start != '%')
> -        parse_error(qerror, '%');
> +    if (*start != '%') {
> +        parse_error(entry, '%');
> +    }
>       start++;
> -    if (*start != '(')
> -        parse_error(qerror, '(');
> +    if (*start != '(') {
> +        parse_error(entry, '(');
> +    }
>       start++;
>
>       end = strchr(start, ')');
> -    if (!end)
> -        parse_error(qerror, ')');
> +    if (!end) {
> +        parse_error(entry, ')');
> +    }
>
>       key_qs = qstring_from_substr(start, 0, end - start - 1);
>       key = qstring_get_str(key_qs);
>
> -    qdict = qobject_to_qdict(qdict_get(qerror->error, "data"));
> +    qdict = qobject_to_qdict(qdict_get(error, "data"));
>       obj = qdict_get(qdict, key);
>       if (!obj) {
> +#if 0
>           qerror_abort(qerror, "key '%s' not found in QDict", key);
> +#else
> +        abort();
> +#endif
>       }
>
>       switch (qobject_type(obj)) {
> @@ -367,41 +380,66 @@ static const char *append_field(QString *outstr, const QError *qerror,
>               qstring_append_int(outstr, qdict_get_int(qdict, key));
>               break;
>           default:
> +#if 0
>               qerror_abort(qerror, "invalid type '%c'", qobject_type(obj));
> +#else
> +            abort();
> +#endif
>       }
>
>       QDECREF(key_qs);
>       return ++end;
>   }
>
> -/**
> - * qerror_human(): Format QError data into human-readable string.
> - *
> - * Formats according to member 'desc' of the specified QError object.
> - */
> -QString *qerror_human(const QError *qerror)
> +static QString *qerror_format_desc(QDict *error,
> +                                   const QErrorStringTable *entry)
>   {
> -    const char *p;
>       QString *qstring;
> +    const char *p;
>
> -    assert(qerror->entry != NULL);
> +    assert(entry != NULL);
>
>       qstring = qstring_new();
>
> -    for (p = qerror->entry->desc; *p != '\0';) {
> +    for (p = entry->desc; *p != '\0';) {
>           if (*p != '%') {
>               qstring_append_chr(qstring, *p++);
>           } else if (*(p + 1) == '%') {
>               qstring_append_chr(qstring, '%');
>               p += 2;
>           } else {
> -            p = append_field(qstring, qerror, p);
> +            p = append_field(error, qstring, entry, p);
>           }
>       }
>
>       return qstring;
>   }
>
> +QString *qerror_format(const char *fmt, QDict *error)
> +{
> +    const QErrorStringTable *entry = NULL;
> +    int i;
> +
> +    for (i = 0; qerror_table[i].error_fmt; i++) {
> +        if (strcmp(qerror_table[i].error_fmt, fmt) == 0) {
> +            entry =&qerror_table[i];
> +            break;
> +        }
> +    }
> +
> +    return qerror_format_desc(error, entry);
> +}
> +
> +/**
> + * qerror_human(): Format QError data into human-readable string.
> + *
> + * Formats according to member 'desc' of the specified QError object.
> + */
> +QString *qerror_human(const QError *qerror)
> +{
> +    return qerror_format_desc(qerror->error, qerror->entry);
> +}
> +
>   /**
>    * qerror_print(): Print QError data
>    *
> diff --git a/qerror.h b/qerror.h
> index f732d45..fd63ee9 100644
> --- a/qerror.h
> +++ b/qerror.h
> @@ -42,6 +42,7 @@ void qerror_report_internal(const char *file, int linenr, const char *func,
>   #define qerror_report(fmt, ...) \
>       qerror_report_internal(__FILE__, __LINE__, __func__, fmt, ## __VA_ARGS__)
>   QError *qobject_to_qerror(const QObject *obj);
> +QString *qerror_format(const char *fmt, QDict *error);
>
>   /*
>    * QError class list
Luiz Capitulino - March 14, 2011, 7:17 p.m.
On Fri, 11 Mar 2011 15:08:38 -0600
Anthony Liguori <aliguori@us.ibm.com> wrote:

> On 03/11/2011 03:00 PM, Anthony Liguori wrote:
> > This will let Error share the QError human formatting.  This is only used for
> > HMP.
> >
> > Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> >
> > diff --git a/qerror.c b/qerror.c
> > index 4855604..13d53c9 100644
> > --- a/qerror.c
> > +++ b/qerror.c
> > @@ -326,12 +326,18 @@ QError *qerror_from_info(const char *file, int linenr, const char *func,
> >       return qerr;
> >   }
> >
> > -static void parse_error(const QError *qerror, int c)
> > +static void parse_error(const QErrorStringTable *entry, int c)
> >   {
> > -    qerror_abort(qerror, "expected '%c' in '%s'", c, qerror->entry->desc);
> > +#if 0
> > +    qerror_abort(qerror, "expected '%c' in '%s'", c, entry->desc);
> > +#else
> > +    fprintf(stderr, "expected '%c' in '%s'", c, entry->desc);
> > +    abort();
> > +#endif
> >   }
> 
> Err, I shouldn't have left these #if 0's in here.  Please ignore them.

But you're going to keep qerror_abort() usage, right?
 
> 
> Regards,
> 
> Anthony Liguori
> 
> > -static const char *append_field(QString *outstr, const QError *qerror,
> > +static const char *append_field(QDict *error, QString *outstr,
> > +                                const QErrorStringTable *entry,
> >                                   const char *start)
> >   {
> >       QObject *obj;
> > @@ -339,24 +345,31 @@ static const char *append_field(QString *outstr, const QError *qerror,
> >       QString *key_qs;
> >       const char *end, *key;
> >
> > -    if (*start != '%')
> > -        parse_error(qerror, '%');
> > +    if (*start != '%') {
> > +        parse_error(entry, '%');
> > +    }
> >       start++;
> > -    if (*start != '(')
> > -        parse_error(qerror, '(');
> > +    if (*start != '(') {
> > +        parse_error(entry, '(');
> > +    }
> >       start++;
> >
> >       end = strchr(start, ')');
> > -    if (!end)
> > -        parse_error(qerror, ')');
> > +    if (!end) {
> > +        parse_error(entry, ')');
> > +    }
> >
> >       key_qs = qstring_from_substr(start, 0, end - start - 1);
> >       key = qstring_get_str(key_qs);
> >
> > -    qdict = qobject_to_qdict(qdict_get(qerror->error, "data"));
> > +    qdict = qobject_to_qdict(qdict_get(error, "data"));
> >       obj = qdict_get(qdict, key);
> >       if (!obj) {
> > +#if 0
> >           qerror_abort(qerror, "key '%s' not found in QDict", key);
> > +#else
> > +        abort();
> > +#endif
> >       }
> >
> >       switch (qobject_type(obj)) {
> > @@ -367,41 +380,66 @@ static const char *append_field(QString *outstr, const QError *qerror,
> >               qstring_append_int(outstr, qdict_get_int(qdict, key));
> >               break;
> >           default:
> > +#if 0
> >               qerror_abort(qerror, "invalid type '%c'", qobject_type(obj));
> > +#else
> > +            abort();
> > +#endif
> >       }
> >
> >       QDECREF(key_qs);
> >       return ++end;
> >   }
> >
> > -/**
> > - * qerror_human(): Format QError data into human-readable string.
> > - *
> > - * Formats according to member 'desc' of the specified QError object.
> > - */
> > -QString *qerror_human(const QError *qerror)
> > +static QString *qerror_format_desc(QDict *error,
> > +                                   const QErrorStringTable *entry)
> >   {
> > -    const char *p;
> >       QString *qstring;
> > +    const char *p;
> >
> > -    assert(qerror->entry != NULL);
> > +    assert(entry != NULL);
> >
> >       qstring = qstring_new();
> >
> > -    for (p = qerror->entry->desc; *p != '\0';) {
> > +    for (p = entry->desc; *p != '\0';) {
> >           if (*p != '%') {
> >               qstring_append_chr(qstring, *p++);
> >           } else if (*(p + 1) == '%') {
> >               qstring_append_chr(qstring, '%');
> >               p += 2;
> >           } else {
> > -            p = append_field(qstring, qerror, p);
> > +            p = append_field(error, qstring, entry, p);
> >           }
> >       }
> >
> >       return qstring;
> >   }
> >
> > +QString *qerror_format(const char *fmt, QDict *error)
> > +{
> > +    const QErrorStringTable *entry = NULL;
> > +    int i;
> > +
> > +    for (i = 0; qerror_table[i].error_fmt; i++) {
> > +        if (strcmp(qerror_table[i].error_fmt, fmt) == 0) {
> > +            entry =&qerror_table[i];
> > +            break;
> > +        }
> > +    }
> > +
> > +    return qerror_format_desc(error, entry);
> > +}
> > +
> > +/**
> > + * qerror_human(): Format QError data into human-readable string.
> > + *
> > + * Formats according to member 'desc' of the specified QError object.
> > + */
> > +QString *qerror_human(const QError *qerror)
> > +{
> > +    return qerror_format_desc(qerror->error, qerror->entry);
> > +}
> > +
> >   /**
> >    * qerror_print(): Print QError data
> >    *
> > diff --git a/qerror.h b/qerror.h
> > index f732d45..fd63ee9 100644
> > --- a/qerror.h
> > +++ b/qerror.h
> > @@ -42,6 +42,7 @@ void qerror_report_internal(const char *file, int linenr, const char *func,
> >   #define qerror_report(fmt, ...) \
> >       qerror_report_internal(__FILE__, __LINE__, __func__, fmt, ## __VA_ARGS__)
> >   QError *qobject_to_qerror(const QObject *obj);
> > +QString *qerror_format(const char *fmt, QDict *error);
> >
> >   /*
> >    * QError class list
>
Anthony Liguori - March 14, 2011, 7:27 p.m.
On 03/14/2011 02:17 PM, Luiz Capitulino wrote:
> On Fri, 11 Mar 2011 15:08:38 -0600
> Anthony Liguori<aliguori@us.ibm.com>  wrote:
>
>> On 03/11/2011 03:00 PM, Anthony Liguori wrote:
>>> This will let Error share the QError human formatting.  This is only used for
>>> HMP.
>>>
>>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>>>
>>> diff --git a/qerror.c b/qerror.c
>>> index 4855604..13d53c9 100644
>>> --- a/qerror.c
>>> +++ b/qerror.c
>>> @@ -326,12 +326,18 @@ QError *qerror_from_info(const char *file, int linenr, const char *func,
>>>        return qerr;
>>>    }
>>>
>>> -static void parse_error(const QError *qerror, int c)
>>> +static void parse_error(const QErrorStringTable *entry, int c)
>>>    {
>>> -    qerror_abort(qerror, "expected '%c' in '%s'", c, qerror->entry->desc);
>>> +#if 0
>>> +    qerror_abort(qerror, "expected '%c' in '%s'", c, entry->desc);
>>> +#else
>>> +    fprintf(stderr, "expected '%c' in '%s'", c, entry->desc);
>>> +    abort();
>>> +#endif
>>>    }
>> Err, I shouldn't have left these #if 0's in here.  Please ignore them.
> But you're going to keep qerror_abort() usage, right?

No, qerror_abort() needs to go away.

It's too tied to QError and this patch is making the formatting code 
work outside of of QEMU.

Once this whole series is completely merged, QError goes away entirely 
and this pretty formatting is replaced with something much simpler.

Regards,

Anthony Liguori
Luiz Capitulino - March 14, 2011, 7:37 p.m.
On Mon, 14 Mar 2011 14:27:30 -0500
Anthony Liguori <anthony@codemonkey.ws> wrote:

> On 03/14/2011 02:17 PM, Luiz Capitulino wrote:
> > On Fri, 11 Mar 2011 15:08:38 -0600
> > Anthony Liguori<aliguori@us.ibm.com>  wrote:
> >
> >> On 03/11/2011 03:00 PM, Anthony Liguori wrote:
> >>> This will let Error share the QError human formatting.  This is only used for
> >>> HMP.
> >>>
> >>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> >>>
> >>> diff --git a/qerror.c b/qerror.c
> >>> index 4855604..13d53c9 100644
> >>> --- a/qerror.c
> >>> +++ b/qerror.c
> >>> @@ -326,12 +326,18 @@ QError *qerror_from_info(const char *file, int linenr, const char *func,
> >>>        return qerr;
> >>>    }
> >>>
> >>> -static void parse_error(const QError *qerror, int c)
> >>> +static void parse_error(const QErrorStringTable *entry, int c)
> >>>    {
> >>> -    qerror_abort(qerror, "expected '%c' in '%s'", c, qerror->entry->desc);
> >>> +#if 0
> >>> +    qerror_abort(qerror, "expected '%c' in '%s'", c, entry->desc);
> >>> +#else
> >>> +    fprintf(stderr, "expected '%c' in '%s'", c, entry->desc);
> >>> +    abort();
> >>> +#endif
> >>>    }
> >> Err, I shouldn't have left these #if 0's in here.  Please ignore them.
> > But you're going to keep qerror_abort() usage, right?
> 
> No, qerror_abort() needs to go away.
> 
> It's too tied to QError and this patch is making the formatting code 
> work outside of of QEMU.

qerror_abort() only exists for debugging purposes. I won't say its perfect,
but it's better than nothing and has already saved some time when writing
new errors.

I'm fine dropping it as long as there's a better replacement, which is
not the case here. There's even a hunk that replaces qerror_abort() for
a plain abort().

> Once this whole series is completely merged, QError goes away entirely 
> and this pretty formatting is replaced with something much simpler.
> 
> Regards,
> 
> Anthony Liguori
>
Anthony Liguori - March 14, 2011, 7:45 p.m.
On 03/14/2011 02:37 PM, Luiz Capitulino wrote:
>
> qerror_abort() only exists for debugging purposes. I won't say its perfect,
> but it's better than nothing and has already saved some time when writing
> new errors.
>
> I'm fine dropping it as long as there's a better replacement, which is
> not the case here. There's even a hunk that replaces qerror_abort() for
> a plain abort().

Yes, that's the replacement.

Regards,

Anthony Liguori
Luiz Capitulino - March 14, 2011, 8:22 p.m.
On Mon, 14 Mar 2011 14:45:13 -0500
Anthony Liguori <aliguori@us.ibm.com> wrote:

> On 03/14/2011 02:37 PM, Luiz Capitulino wrote:
> >
> > qerror_abort() only exists for debugging purposes. I won't say its perfect,
> > but it's better than nothing and has already saved some time when writing
> > new errors.
> >
> > I'm fine dropping it as long as there's a better replacement, which is
> > not the case here. There's even a hunk that replaces qerror_abort() for
> > a plain abort().
> 
> Yes, that's the replacement.

It's not a good one: it makes the current code a bit worse and we don't know
how and when the error classes are going to be replaced.

Maybe a better merge plan would be to work on errors first. Completely drop
qerror according to qapi needs, and then put the rest of the stuff on top.
Anthony Liguori - March 14, 2011, 8:41 p.m.
On 03/14/2011 03:22 PM, Luiz Capitulino wrote:
> On Mon, 14 Mar 2011 14:45:13 -0500
> Anthony Liguori<aliguori@us.ibm.com>  wrote:
>
>> On 03/14/2011 02:37 PM, Luiz Capitulino wrote:
>>> qerror_abort() only exists for debugging purposes. I won't say its perfect,
>>> but it's better than nothing and has already saved some time when writing
>>> new errors.
>>>
>>> I'm fine dropping it as long as there's a better replacement, which is
>>> not the case here. There's even a hunk that replaces qerror_abort() for
>>> a plain abort().
>> Yes, that's the replacement.
> It's not a good one: it makes the current code a bit worse and we don't know
> how and when the error classes are going to be replaced.

Yes, we do, before 0.15.0.

> Maybe a better merge plan would be to work on errors first. Completely drop
> qerror according to qapi needs, and then put the rest of the stuff on top.

Can't be done until we introduce new QMP commands to get rid of the old 
HMP commands because there are HMP commands without equivalents that 
make use of qerror_report().

There are just a handful of these left in my QAPI branch so once I can 
start adding these QMP commands, they'll be gone quickly.  I don't want 
to introduce a bunch of new QMP commands without this stuff getting 
merged upstream first though.

Regards,

Anthony Liguori
Luiz Capitulino - March 14, 2011, 8:48 p.m.
On Mon, 14 Mar 2011 15:41:49 -0500
Anthony Liguori <aliguori@us.ibm.com> wrote:

> On 03/14/2011 03:22 PM, Luiz Capitulino wrote:
> > On Mon, 14 Mar 2011 14:45:13 -0500
> > Anthony Liguori<aliguori@us.ibm.com>  wrote:
> >
> >> On 03/14/2011 02:37 PM, Luiz Capitulino wrote:
> >>> qerror_abort() only exists for debugging purposes. I won't say its perfect,
> >>> but it's better than nothing and has already saved some time when writing
> >>> new errors.
> >>>
> >>> I'm fine dropping it as long as there's a better replacement, which is
> >>> not the case here. There's even a hunk that replaces qerror_abort() for
> >>> a plain abort().
> >> Yes, that's the replacement.
> > It's not a good one: it makes the current code a bit worse and we don't know
> > how and when the error classes are going to be replaced.
> 
> Yes, we do, before 0.15.0.

Very optimistic :) I don't doubt you can post patches quickly, but we're
likely going to have fun discussions, respins, tests etc. And all the QAPI
stuff in parallel.

> > Maybe a better merge plan would be to work on errors first. Completely drop
> > qerror according to qapi needs, and then put the rest of the stuff on top.
> 
> Can't be done until we introduce new QMP commands to get rid of the old 
> HMP commands because there are HMP commands without equivalents that 
> make use of qerror_report().
> 
> There are just a handful of these left in my QAPI branch so once I can 
> start adding these QMP commands, they'll be gone quickly.  I don't want 
> to introduce a bunch of new QMP commands without this stuff getting 
> merged upstream first though.
> 
> Regards,
> 
> Anthony Liguori
>
Anthony Liguori - March 14, 2011, 9:03 p.m.
On 03/14/2011 03:48 PM, Luiz Capitulino wrote:
> On Mon, 14 Mar 2011 15:41:49 -0500
> Anthony Liguori<aliguori@us.ibm.com>  wrote:
>
>> On 03/14/2011 03:22 PM, Luiz Capitulino wrote:
>>> On Mon, 14 Mar 2011 14:45:13 -0500
>>> Anthony Liguori<aliguori@us.ibm.com>   wrote:
>>>
>>>> On 03/14/2011 02:37 PM, Luiz Capitulino wrote:
>>>>> qerror_abort() only exists for debugging purposes. I won't say its perfect,
>>>>> but it's better than nothing and has already saved some time when writing
>>>>> new errors.
>>>>>
>>>>> I'm fine dropping it as long as there's a better replacement, which is
>>>>> not the case here. There's even a hunk that replaces qerror_abort() for
>>>>> a plain abort().
>>>> Yes, that's the replacement.
>>> It's not a good one: it makes the current code a bit worse and we don't know
>>> how and when the error classes are going to be replaced.
>> Yes, we do, before 0.15.0.
> Very optimistic :) I don't doubt you can post patches quickly, but we're
> likely going to have fun discussions, respins, tests etc. And all the QAPI
> stuff in parallel.

Let's have those discussions then because that's what's important.

I split this out because I didn't want to have a 40 patch series so I 
tried to split into two logical series.

But the goal is here QAPI.  That's what's important to get merged.  I'm 
not terribly interested in merging these changes until we're ready to 
merge the first round of QAPI.

It's a big change, and it's optimistic, but that's what makes it worth 
trying to do.

Regards,

Anthony Liguori

Patch

diff --git a/qerror.c b/qerror.c
index 4855604..13d53c9 100644
--- a/qerror.c
+++ b/qerror.c
@@ -326,12 +326,18 @@  QError *qerror_from_info(const char *file, int linenr, const char *func,
     return qerr;
 }
 
-static void parse_error(const QError *qerror, int c)
+static void parse_error(const QErrorStringTable *entry, int c)
 {
-    qerror_abort(qerror, "expected '%c' in '%s'", c, qerror->entry->desc);
+#if 0
+    qerror_abort(qerror, "expected '%c' in '%s'", c, entry->desc);
+#else
+    fprintf(stderr, "expected '%c' in '%s'", c, entry->desc);
+    abort();
+#endif
 }
 
-static const char *append_field(QString *outstr, const QError *qerror,
+static const char *append_field(QDict *error, QString *outstr,
+                                const QErrorStringTable *entry,
                                 const char *start)
 {
     QObject *obj;
@@ -339,24 +345,31 @@  static const char *append_field(QString *outstr, const QError *qerror,
     QString *key_qs;
     const char *end, *key;
 
-    if (*start != '%')
-        parse_error(qerror, '%');
+    if (*start != '%') {
+        parse_error(entry, '%');
+    }
     start++;
-    if (*start != '(')
-        parse_error(qerror, '(');
+    if (*start != '(') {
+        parse_error(entry, '(');
+    }
     start++;
 
     end = strchr(start, ')');
-    if (!end)
-        parse_error(qerror, ')');
+    if (!end) {
+        parse_error(entry, ')');
+    }
 
     key_qs = qstring_from_substr(start, 0, end - start - 1);
     key = qstring_get_str(key_qs);
 
-    qdict = qobject_to_qdict(qdict_get(qerror->error, "data"));
+    qdict = qobject_to_qdict(qdict_get(error, "data"));
     obj = qdict_get(qdict, key);
     if (!obj) {
+#if 0
         qerror_abort(qerror, "key '%s' not found in QDict", key);
+#else
+        abort();
+#endif
     }
 
     switch (qobject_type(obj)) {
@@ -367,41 +380,66 @@  static const char *append_field(QString *outstr, const QError *qerror,
             qstring_append_int(outstr, qdict_get_int(qdict, key));
             break;
         default:
+#if 0
             qerror_abort(qerror, "invalid type '%c'", qobject_type(obj));
+#else
+            abort();
+#endif
     }
 
     QDECREF(key_qs);
     return ++end;
 }
 
-/**
- * qerror_human(): Format QError data into human-readable string.
- *
- * Formats according to member 'desc' of the specified QError object.
- */
-QString *qerror_human(const QError *qerror)
+static QString *qerror_format_desc(QDict *error,
+                                   const QErrorStringTable *entry)
 {
-    const char *p;
     QString *qstring;
+    const char *p;
 
-    assert(qerror->entry != NULL);
+    assert(entry != NULL);
 
     qstring = qstring_new();
 
-    for (p = qerror->entry->desc; *p != '\0';) {
+    for (p = entry->desc; *p != '\0';) {
         if (*p != '%') {
             qstring_append_chr(qstring, *p++);
         } else if (*(p + 1) == '%') {
             qstring_append_chr(qstring, '%');
             p += 2;
         } else {
-            p = append_field(qstring, qerror, p);
+            p = append_field(error, qstring, entry, p);
         }
     }
 
     return qstring;
 }
 
+QString *qerror_format(const char *fmt, QDict *error)
+{
+    const QErrorStringTable *entry = NULL;
+    int i;
+
+    for (i = 0; qerror_table[i].error_fmt; i++) {
+        if (strcmp(qerror_table[i].error_fmt, fmt) == 0) {
+            entry = &qerror_table[i];
+            break;
+        }
+    }
+
+    return qerror_format_desc(error, entry);
+}
+
+/**
+ * qerror_human(): Format QError data into human-readable string.
+ *
+ * Formats according to member 'desc' of the specified QError object.
+ */
+QString *qerror_human(const QError *qerror)
+{
+    return qerror_format_desc(qerror->error, qerror->entry);
+}
+
 /**
  * qerror_print(): Print QError data
  *
diff --git a/qerror.h b/qerror.h
index f732d45..fd63ee9 100644
--- a/qerror.h
+++ b/qerror.h
@@ -42,6 +42,7 @@  void qerror_report_internal(const char *file, int linenr, const char *func,
 #define qerror_report(fmt, ...) \
     qerror_report_internal(__FILE__, __LINE__, __func__, fmt, ## __VA_ARGS__)
 QError *qobject_to_qerror(const QObject *obj);
+QString *qerror_format(const char *fmt, QDict *error);
 
 /*
  * QError class list