[gtk/wip/otte/json: 37/38] jsonparser: Improve error handling




commit 17d481f392b46a304db0acfa192e3e7dd412c299
Author: Benjamin Otte <otte redhat com>
Date:   Sat Dec 4 20:51:11 2021 +0100

    jsonparser: Improve error handling
    
     * Add a custom GError domain with our own error values
     * Add API so external code can emit errors
     * Add location information to errors
     * Allow API to query errors

 gtk/json/gtkjsonparser.c        | 259 ++++++++++++++++++++++++++++++++++++----
 gtk/json/gtkjsonparserprivate.h |  27 ++++-
 2 files changed, 261 insertions(+), 25 deletions(-)
---
diff --git a/gtk/json/gtkjsonparser.c b/gtk/json/gtkjsonparser.c
index 885c5990c3..78dcb339ae 100644
--- a/gtk/json/gtkjsonparser.c
+++ b/gtk/json/gtkjsonparser.c
@@ -45,6 +45,8 @@ struct _GtkJsonParser
   const guchar *end; /* pointer after end of data we're reading */
 
   GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */
+  const guchar *error_start; /* start of error location */
+  const guchar *error_end; /* end of error location */
 
   GtkJsonBlock *block; /* current block */
   GtkJsonBlock *blocks; /* blocks array */
@@ -54,16 +56,17 @@ struct _GtkJsonParser
 
 typedef enum {
   WHITESPACE     = (1 << 4),
-  STRING_ELEMENT = (1 << 5),
-  STRING_MARKER  = (1 << 6),
+  NEWLINE        = (1 << 5),
+  STRING_ELEMENT = (1 << 6),
+  STRING_MARKER  = (1 << 7),
 } JsonCharacterType;
 
 #define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1)
 
 static const guchar json_character_table[256] = {
   ['\t'] = WHITESPACE,
-  ['\r'] = WHITESPACE,
-  ['\n'] = WHITESPACE,
+  ['\r'] = WHITESPACE | NEWLINE,
+  ['\n'] = WHITESPACE | NEWLINE,
   [' ']  = WHITESPACE | STRING_ELEMENT,
   ['!']  = STRING_ELEMENT,
   ['"']  = GTK_JSON_STRING | STRING_MARKER,
@@ -177,6 +180,21 @@ json_skip_characters (const guchar      *start,
   return s;
 }
 
+static const guchar *
+json_skip_characters_until (const guchar      *start,
+                            const guchar      *end,
+                            JsonCharacterType  type)
+{
+  const guchar *s;
+
+  for (s = start; s < end; s++)
+    {
+      if (json_character_table[*s] & type)
+        break;
+    }
+  return s;
+}
+
 static const guchar *
 json_find_character (const guchar      *start,
                      JsonCharacterType  type)
@@ -191,45 +209,155 @@ json_find_character (const guchar      *start,
   return s;
 }
 
+GQuark
+gtk_json_error_quark (void)
+{
+  return g_quark_from_static_string ("gtk-json-error-quark");
+}
+
 static void
-gtk_json_parser_value_error (GtkJsonParser *self,
-                             const char  *format,
-                             ...) G_GNUC_PRINTF(2, 3);
+gtk_json_parser_take_error (GtkJsonParser *self,
+                            const guchar  *start_location,
+                            const guchar  *end_location,
+                            GError        *error)
+{
+  g_assert (start_location < end_location);
+  g_assert (g_bytes_get_data (self->bytes, NULL) <= (gconstpointer) start_location);
+  g_assert (end_location < self->end);
+
+  if (self->error)
+    {
+      g_error_free (error);
+      return;
+    }
+
+  self->error = error;
+  self->error_start = start_location;
+  self->error_end = end_location;
+}
+
 static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...)
+{
+  va_list args;
+  const guchar *error_end;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  for (error_end = self->reader;
+       error_end < self->end && g_ascii_isalnum (*error_end);
+       error_end++)
+    ;
+  if (error_end == self->reader &&
+      g_utf8_get_char_validated ((const char *) error_end, self->end - error_end))
+    {
+      error_end = (const guchar *) g_utf8_next_char (error_end);
+    }
+
+  gtk_json_parser_take_error (self,
+                              self->reader,
+                              error_end,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_TYPE,
+                                                  format, args));
+  va_end (args);
+}
+
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+                            const char  *format,
+                            ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+                            const char  *format,
+                            ...)
+{
+  const guchar *start_location;
+  va_list args;
+
+  if (self->error)
+    return;
+
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
+  va_start (args, format);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_TYPE,
+                                                  format, args));
+  va_end (args);
+}
+
+void
 gtk_json_parser_value_error (GtkJsonParser *self,
                              const char  *format,
                              ...)
 {
+  const guchar *start_location;
   va_list args;
 
   if (self->error)
     return;
 
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
   va_start (args, format);
-  self->error = g_error_new_valist (G_FILE_ERROR,
-                                    G_FILE_ERROR_FAILED,
-                                    format, args);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_VALUE,
+                                                  format, args));
   va_end (args);
 }
 
-static void
-gtk_json_parser_syntax_error (GtkJsonParser *self,
-                              const char  *format,
-                              ...) G_GNUC_PRINTF(2, 3);
-static void
-gtk_json_parser_syntax_error (GtkJsonParser *self,
+void
+gtk_json_parser_schema_error (GtkJsonParser *self,
                               const char  *format,
                               ...)
 {
+  const guchar *start_location;
   va_list args;
 
   if (self->error)
     return;
 
+  if (self->block->member_name)
+    start_location = self->block->member_name;
+  if (self->block->value)
+    start_location = self->block->value;
+  else if (self->block != self->blocks)
+    start_location = self->block[-1].value;
+  else
+    start_location = g_bytes_get_data (self->bytes, NULL);
+
   va_start (args, format);
-  self->error = g_error_new_valist (G_FILE_ERROR,
-                                    G_FILE_ERROR_FAILED,
-                                    format, args);
+  gtk_json_parser_take_error (self,
+                              start_location,
+                              self->reader,
+                              g_error_new_valist (GTK_JSON_ERROR,
+                                                  GTK_JSON_ERROR_SCHEMA,
+                                                  format, args));
   va_end (args);
 }
 
@@ -896,6 +1024,89 @@ gtk_json_parser_get_error (GtkJsonParser *self)
   return self->error;
 }
 
+void
+gtk_json_parser_get_error_offset (GtkJsonParser *self,
+                                  gsize         *start,
+                                  gsize         *end)
+{
+  const guchar *data;
+
+  if (self->error == NULL)
+    {
+      if (start)
+        *start = 0;
+      if (end)
+        *end = 0;
+      return;
+    }
+
+  data = g_bytes_get_data (self->bytes, NULL);
+  if (start)
+    *start = self->error_start - data;
+  if (end)
+    *end = self->error_end - data;
+}
+
+void
+gtk_json_parser_get_error_location (GtkJsonParser *self,
+                                    gsize         *start_line,
+                                    gsize         *start_line_bytes,
+                                    gsize         *end_line,
+                                    gsize         *end_line_bytes)
+{
+  const guchar *s, *line_start;
+  gsize lines;
+
+  if (self->error == NULL)
+    {
+      if (start_line)
+        *start_line = 0;
+      if (start_line_bytes)
+        *start_line_bytes = 0;
+      if (end_line)
+        *end_line = 0;
+      if (end_line_bytes)
+        *end_line_bytes = 0;
+      return;
+    }
+
+  line_start = g_bytes_get_data (self->bytes, NULL);
+  lines = 0;
+
+  for (s = json_skip_characters_until (line_start, self->error_start, NEWLINE);
+       s < self->error_start;
+       s = json_skip_characters_until  (line_start, self->error_start, NEWLINE))
+    {
+      if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+        s++;
+      lines++;
+      line_start = s + 1;
+    }
+
+  if (start_line)
+    *start_line = lines;
+  if (start_line_bytes)
+    *start_line_bytes = s - line_start;
+
+  if (end_line == NULL && end_line_bytes == NULL)
+    return;
+
+  for (s = json_skip_characters_until (s, self->error_end, NEWLINE);
+       s < self->error_end;
+       s = json_skip_characters_until (line_start, self->error_end, NEWLINE))
+    {
+      if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+        s++;
+      lines++;
+      line_start = s + 1;
+    }
+
+  if (end_line)
+    *end_line = lines;
+  if (end_line_bytes)
+    *end_line_bytes = s - line_start;
+}
+
 static gboolean
 gtk_json_parser_has_member (GtkJsonParser *self)
 {
@@ -990,7 +1201,7 @@ gtk_json_parser_get_boolean (GtkJsonParser *self)
   else if (*self->block->value == 'f')
     return FALSE;
 
-  gtk_json_parser_value_error (self, "Expected a boolean value");
+  gtk_json_parser_type_error (self, "Expected a boolean value");
   return FALSE;
 }
 
@@ -1007,7 +1218,7 @@ gtk_json_parser_get_number (GtkJsonParser *self)
 
   if (!strchr ("-0123456789", *self->block->value))
     {
-      gtk_json_parser_value_error (self, "Expected a number");
+      gtk_json_parser_type_error (self, "Expected a number");
       return 0;
     }
 
@@ -1053,7 +1264,7 @@ gtk_json_parser_get_string (GtkJsonParser *self)
 
   if (*self->block->value != '"')
     {
-      gtk_json_parser_value_error (self, "Expected a string");
+      gtk_json_parser_type_error (self, "Expected a string");
       return g_strdup ("");
     }
 
@@ -1068,7 +1279,7 @@ gtk_json_parser_start_object (GtkJsonParser *self)
 
   if (!gtk_json_parser_try_char (self, '{'))
     {
-      gtk_json_parser_value_error (self, "Expected an object");
+      gtk_json_parser_type_error (self, "Expected an object");
       return FALSE;
     }
 
@@ -1109,7 +1320,7 @@ gtk_json_parser_start_array (GtkJsonParser *self)
 
   if (!gtk_json_parser_try_char (self, '['))
     {
-      gtk_json_parser_value_error (self, "Expected an array");
+      gtk_json_parser_type_error (self, "Expected an array");
       return FALSE;
     }
 
diff --git a/gtk/json/gtkjsonparserprivate.h b/gtk/json/gtkjsonparserprivate.h
index f3ff43fd88..fe737fb719 100644
--- a/gtk/json/gtkjsonparserprivate.h
+++ b/gtk/json/gtkjsonparserprivate.h
@@ -35,8 +35,19 @@ typedef enum {
   GTK_JSON_ARRAY
 } GtkJsonNode;
 
+typedef enum {
+  GTK_JSON_ERROR_FAILED,
+  GTK_JSON_ERROR_SYNTAX,
+  GTK_JSON_ERROR_TYPE,
+  GTK_JSON_ERROR_VALUE,
+  GTK_JSON_ERROR_SCHEMA,
+} GtkJsonError;
+
 typedef struct _GtkJsonParser GtkJsonParser;
 
+#define GTK_JSON_ERROR (gtk_json_error_quark ())
+GQuark                  gtk_json_error_quark                    (void);
+
 GtkJsonParser *         gtk_json_parser_new_for_bytes           (GBytes                 *bytes);
 GtkJsonParser *         gtk_json_parser_new_for_string          (const char             *string,
                                                                  gssize                  size);
@@ -45,7 +56,6 @@ void                    gtk_json_parser_free                    (GtkJsonParser
 
 gboolean                gtk_json_parser_next                    (GtkJsonParser          *self);
 GtkJsonNode             gtk_json_parser_get_node                (GtkJsonParser          *self);
-const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
 char *                  gtk_json_parser_get_member_name         (GtkJsonParser          *self);
 gssize                  gtk_json_parser_select_member           (GtkJsonParser          *self,
                                                                  const char * const     *options);
@@ -60,6 +70,21 @@ gboolean                gtk_json_parser_start_object            (GtkJsonParser
 gboolean                gtk_json_parser_start_array             (GtkJsonParser          *self);
 gboolean                gtk_json_parser_end                     (GtkJsonParser          *self);
 
+const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
+void                    gtk_json_parser_get_error_offset        (GtkJsonParser          *self,
+                                                                 gsize                  *start,
+                                                                 gsize                  *end);
+void                    gtk_json_parser_get_error_location      (GtkJsonParser          *self,
+                                                                 gsize                  *start_line,
+                                                                 gsize                  *start_line_bytes,
+                                                                 gsize                  *end_line,
+                                                                 gsize                  *end_line_bytes);
+void                    gtk_json_parser_value_error             (GtkJsonParser          *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_json_parser_schema_error            (GtkJsonParser          *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
 
 G_END_DECLS
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]