[libgdata/tasks-integration: 2/5] core: Various improvements and the beginnings of some unit tests



commit a506d7f994a6bea46fbc8b54bb7c01cbf3e71519
Author: Philip Withnall <philip tecnocode co uk>
Date:   Tue Aug 27 11:41:10 2013 -0600

    core: Various improvements and the beginnings of some unit tests
    
    This is not ready to be merged to master yet; the tests are
    incomplete and don’t pass yet. It’s only pushed here for
    exposition.

 README                   |    1 +
 gdata/GData-0.0.metadata |    3 +-
 gdata/gdata-entry.c      |   41 ++++--------
 gdata/gdata-feed.c       |   27 ++++----
 gdata/gdata-parsable.c   |  147 +++++++++++++++++++++++++++++++++++------
 gdata/gdata-parsable.h   |   12 +++-
 gdata/gdata-parser.c     |  117 ++++++++++++++++++++++++----------
 gdata/gdata-parser.h     |    6 +-
 gdata/gdata-private.h    |    8 +-
 gdata/gdata-service.c    |   24 ++++---
 gdata/gdata.symbols      |    2 +
 gdata/tests/common.c     |  161 ++++++++++++++++++++++++++++++++++++++++++++++
 gdata/tests/common.h     |   11 +++-
 gdata/tests/general.c    |   53 +++++++++++++++
 14 files changed, 494 insertions(+), 119 deletions(-)
---
diff --git a/README b/README
index adb667b..a30c7b9 100644
--- a/README
+++ b/README
@@ -18,6 +18,7 @@ Dependencies
  • gio-2.0 ≥ 2.17.3
  • libsoup-2.4 ≥ 2.37.91
  • liboauth ≥ 0.9.4
+ • json-glib ≥ 0.15.0
 
 If compiling with --enable-gnome (for GNOME support):
  • libsoup-gnome-2.4
diff --git a/gdata/GData-0.0.metadata b/gdata/GData-0.0.metadata
index 2995eda..9b5009a 100644
--- a/gdata/GData-0.0.metadata
+++ b/gdata/GData-0.0.metadata
@@ -1,6 +1,7 @@
 Color struct
 
 Parsable.get_xml#method skip
+Parsable.get_json#method skip
 Query.get_query_uri#method skip
 DOCUMENTS_PRESENTATION_* name="DOCUMENTS_PRESENTATION_(.+)" parent="GData.DocumentsPresentationFormat"
 DOCUMENTS_TEXT_* name="DOCUMENTS_TEXT_(.+)" parent="GData.DocumentsTextFormat"
@@ -8,4 +9,4 @@ DOCUMENTS_SPREADSHEET_* name="DOCUMENTS_SPREADSHEET_(.+)" parent="GData.Document
 CONTACTS_GENDER_* name="CONTACTS_GENDER_(.+)" parent="GData.ContactsGender"
 CONTACTS_GROUP_* name="CONTACTS_GROUP_(.+)" parent="GData.ContactsGroupType"
 CONTACTS_PRIORITY_* name="CONTACTS_PRIORITY_(.+)" parent="GData.ContactsPriority"
-CONTACTS_SENSITIVITY_* name="CONTACTS_SENSITIVITY_(.+)" parent="GData.ContactsSensitivity"
\ No newline at end of file
+CONTACTS_SENSITIVITY_* name="CONTACTS_SENSITIVITY_(.+)" parent="GData.ContactsSensitivity"
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 8401d6a..e9cba79 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -57,7 +57,6 @@ static void get_xml (GDataParsable *parsable, GString *xml_string);
 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
 static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
-static gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error);
 static void get_json (GDataParsable *parsable, GString *json_string);
 
 struct _GDataEntryPrivate {
@@ -117,9 +116,8 @@ gdata_entry_class_init (GDataEntryClass *klass)
        parsable_class->element_name = "entry";
 
        parsable_class->parse_json = parse_json;
-       parsable_class->post_parse_json = post_parse_json;
        parsable_class->get_json = get_json;
-       
+
        klass->get_entry_uri = get_entry_uri;
 
        /**
@@ -597,47 +595,36 @@ parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GEr
 {
        gboolean success;
        GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
-       
+
        /* selfLink in JSON is the same as 'content' entry in XML */
        if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT, &(priv->title), &success, 
error) == TRUE ||
            gdata_parser_string_from_json_member (reader, "id", P_DEFAULT, &(priv->id), &success, error) == 
TRUE ||
            gdata_parser_int64_time_from_json_member (reader, "updated", P_REQUIRED | P_NO_DUPES, 
&(priv->updated), &success, error) == TRUE) {
-                       return success;
-               } else if (strcmp (json_reader_get_member_name (reader), "selfLink") == 0) {
-                       priv->content = (gchar*) json_reader_get_string_value (reader);
-                       priv->content_is_uri = TRUE;
-                       return TRUE;
-               }
+               return success;
+       } else if (strcmp (json_reader_get_member_name (reader), "selfLink") == 0) {
+               priv->content = g_strdup (json_reader_get_string_value (reader));
+               priv->content_is_uri = TRUE;
+               return TRUE;
+       }
 
        return GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_json (parsable, reader, user_data, 
error);
 }
 
-static gboolean
-post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error)
-{
-       GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
-
-       /* Reverse our lists of stuff */
-       priv->categories = g_list_reverse (priv->categories);
-       priv->links = g_list_reverse (priv->links);
-       priv->authors = g_list_reverse (priv->authors);
-
-       return TRUE;
-}
-
 static void
 get_json (GDataParsable *parsable, GString *json_string)
 {
        GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
 
-       gdata_parser_string_append_escaped (json_string, "\"title\": \"", priv->title, "\",");
+       /* TODO: This is the wrong kind of escaping. */
+       gdata_parser_string_append_escaped (json_string, "\"title\": \"", priv->title, "\"");
 
-       if (priv->id != NULL)
-               gdata_parser_string_append_escaped (json_string, "\"id\": \"", priv->id, "\",");
+       if (priv->id != NULL) {
+               gdata_parser_string_append_escaped (json_string, ",\"id\": \"", priv->id, "\"");
+       }
 
        if (priv->updated != -1) {
                gchar *updated = gdata_parser_int64_to_iso8601 (priv->updated);
-               g_string_append_printf (json_string, "\"updated\": \"%s\",", updated);
+               g_string_append_printf (json_string, ",\"updated\": \"%s\"", updated);
                g_free (updated);
        }
 }
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index 536652f..05ac887 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -118,7 +118,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 
        parsable_class->parse_json = parse_json;
        parsable_class->post_parse_json = post_parse_json;
-       
+
        /**
         * GDataFeed:title:
         *
@@ -594,22 +594,23 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 static gboolean
 parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
 {
-       guint i;
+       gint i;
        GDataFeed *self = GDATA_FEED (parsable);
        ParseData *data = user_data;
-       
-       if (!strcmp (json_reader_get_member_name (reader), "items")) {
-               // loop trough elements array
+
+       if (strcmp (json_reader_get_member_name (reader), "items") == 0) {
+               /* Loop through the elements array. */
                for (i = 0; i < json_reader_count_elements (reader); i++) {
-                       json_reader_read_element (reader, i);
-                       
                        GDataEntry *entry;
                        GType entry_type;
 
+                       json_reader_read_element (reader, i);
+
                        /* Allow @data to be %NULL, and assume we're parsing a vanilla feed, so that we can 
test #GDataFeed in tests/general.c.
                         * A little hacky, but not too much so, and valuable for testing. */
                        entry_type = (data != NULL) ? data->entry_type : GDATA_TYPE_ENTRY;
-                       // passing reader cursor to object 
+
+                       /* Parse the node, passing it the reader cursor. */
                        entry = GDATA_ENTRY (_gdata_parsable_new_from_json_node (entry_type, reader, NULL, 
error));
                        if (entry == NULL)
                                return FALSE;
@@ -619,11 +620,10 @@ parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GEr
                                _gdata_feed_call_progress_callback (self, data, entry);
                        _gdata_feed_add_entry (self, entry);
                        g_object_unref (entry);
-                       
+
                        json_reader_end_element (reader);
                }
-       }
-       else {
+       } else {
                return GDATA_PARSABLE_CLASS (gdata_feed_parent_class)->parse_json (parsable, reader, 
user_data, error);
        }
 
@@ -635,11 +635,8 @@ post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error)
 {
        GDataFeedPrivate *priv = GDATA_FEED (parsable)->priv;
 
-       /* Reverse our lists of stuff */
+       /* Reverse our lists of stuff. */
        priv->entries = g_list_reverse (priv->entries);
-       priv->categories = g_list_reverse (priv->categories);
-       priv->links = g_list_reverse (priv->links);
-       priv->authors = g_list_reverse (priv->authors);
 
        return TRUE;
 }
diff --git a/gdata/gdata-parsable.c b/gdata/gdata-parsable.c
index 72484f3..f829c0b 100644
--- a/gdata/gdata-parsable.c
+++ b/gdata/gdata-parsable.c
@@ -184,15 +184,25 @@ real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer us
 static gboolean
 real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
 {
-       const gchar *name;
-       
-       /* Unhandled JSON */
-       /* FIXME Have to find a way how to extract any value in string format, now we get just name */
-       name = json_reader_get_member_name (reader);
-       
-       g_string_append (parsable->priv->extra_xml, name);
-       g_debug("Unhandled JSON in %s: %s", G_OBJECT_TYPE_NAME (parsable), name);
-       
+       gchar *json;
+       JsonNode *root;
+       JsonGenerator *generator;
+
+       /* Unhandled JSON. Save it to ->extra_xml so that it's not lost if we
+        * re-upload this Parsable to the server. */
+       generator = json_generator_new ();
+       g_object_get (G_OBJECT (reader), "root", &root, NULL);
+       json_generator_set_root (generator, root);
+       g_object_unref (root);
+
+       json = json_generator_to_data (generator, NULL);
+       g_string_append (parsable->priv->extra_xml, json);
+
+       g_debug ("Unhandled JSON in %s: %s", G_OBJECT_TYPE_NAME (parsable), json);
+
+       g_free (json);
+       g_object_unref (generator);
+
        return TRUE;
 }
 
@@ -338,6 +348,39 @@ _gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode *no
        return parsable;
 }
 
+/**
+ * gdata_parsable_new_from_json:
+ * @parsable_type: the type of the class represented by the JSON
+ * @json: the JSON for just the parsable object
+ * @length: the length of @json, or -1
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @json.
+ *
+ * An object of the given @parsable_type is created, and its <function>pre_parse_json</function>, 
<function>parse_json</function> and
+ * <function>post_parse_json</function> class functions called on the JSON node obtained from @json. 
<function>pre_parse_json</function> and
+ * <function>post_parse_json</function> are called once each on the root node, while 
<function>parse_json</function> is called for
+ * each of the child nodes. TODO: Check me.
+ *
+ * If @length is -1, @json will be assumed to be null-terminated.
+ *
+ * If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
+ *
+ * Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
+ *
+ * Since: UNRELEASED
+ */
+GDataParsable *
+gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, GError **error)
+{
+       g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
+       g_return_val_if_fail (json != NULL && *json != '\0', NULL);
+       g_return_val_if_fail (length >= -1, NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       return _gdata_parsable_new_from_json (parsable_type, json, length, NULL, error);
+}
+
 GDataParsable *
 _gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, gpointer user_data, 
GError **error)
 {
@@ -354,16 +397,17 @@ _gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint leng
                length = strlen (json);
 
        parser = json_parser_new ();
-       if (!json_parser_load_from_data (parser, json, length, error))
+       if (!json_parser_load_from_data (parser, json, length, error)) {
+               g_assert (error == NULL || *error != NULL); /* safety check */
                return NULL;
-       /* FIXME do we need explictly check if json has returned error correctly? */
-       
+       }
+
        reader = json_reader_new (json_parser_get_root (parser));
        parsable = _gdata_parsable_new_from_json_node (parsable_type, reader, user_data, error);
-       
+
        g_object_unref (reader);
        g_object_unref (parser);
-       
+
        return parsable;
 }
 
@@ -372,14 +416,14 @@ _gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, gpo
 {
        GDataParsable *parsable;
        GDataParsableClass *klass;
-       guint i;
-       
+       gint i;
+
        g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
        g_return_val_if_fail (reader != NULL, NULL);
        g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-       /* indicator property which allows distinguish between locally created and server based objects */
-       /* as it is used for non-xml tasks, and adding another one for json would be dublication */
+       /* Indicator property which allows distinguishing between locally created and server based objects
+        * as it is used for non-XML tasks, and adding another one for JSON would be a bit pointless. */
        parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);
 
        klass = GDATA_PARSABLE_GET_CLASS (parsable);
@@ -390,16 +434,15 @@ _gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, gpo
 
        g_assert (klass->element_name != NULL);
 
-       /* Parse each child element */
+       /* Parse each child member. This assumes the outermost node is an object. */
        for (i = 0; i < json_reader_count_members (reader); i++) {
                g_return_val_if_fail (json_reader_read_element (reader, i), NULL);
+
                if (klass->parse_json (parsable, reader, user_data, error) == FALSE) {
                        g_object_unref (parsable);
                        return NULL;
                }
-               /* get out of root object */
-               /* FIXME this is much slower than proper read_member usage */
-               /* can be replaced by count_members/list_members */
+
                json_reader_end_element (reader);
        }
 
@@ -530,6 +573,66 @@ _gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean decl
                g_string_append_printf (xml_string, "</%s>", klass->element_name);
 }
 
+/**
+ * gdata_parsable_get_json:
+ * @self: a #GDataParsable
+ *
+ * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted 
on the server. The JSON
+ * is valid for stand-alone use.
+ *
+ * TODO: How to enforce mutual exclusion between get_json and get_xml?
+ *
+ * Return value: the object's JSON; free with g_free()
+ *
+ * Since: UNRELEASED
+ */
+gchar *
+gdata_parsable_get_json (GDataParsable *self)
+{
+       GString *json_string;
+
+       g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
+
+       json_string = g_string_sized_new (1000);
+       _gdata_parsable_get_json (self, json_string);
+
+       return g_string_free (json_string, FALSE);
+}
+
+/*
+ * _gdata_parsable_get_json:
+ * @self: a #GDataParsable
+ * @json_string: a #GString to build the JSON in
+ *
+ * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted 
on the server.
+ *
+ * Return value: the object's JSON; free with g_free()
+ *
+ * Since: UNRELEASED
+ */
+void
+_gdata_parsable_get_json (GDataParsable *self, GString *json_string)
+{
+       GDataParsableClass *klass;
+
+       g_return_if_fail (GDATA_IS_PARSABLE (self));
+       g_return_if_fail (json_string != NULL);
+
+       klass = GDATA_PARSABLE_GET_CLASS (self);
+
+       g_string_append (json_string, "{");
+
+       /* Add the JSON. */
+       if (klass->get_json != NULL)
+               klass->get_json (self, json_string);
+
+       /* Any extra JSON? Note: The use of extra_xml is intended; the variable is re-used and hence is 
mis-named. */
+       if (self->priv->extra_xml != NULL && self->priv->extra_xml->str != NULL)
+               g_string_append (json_string, self->priv->extra_xml->str);
+
+       g_string_append (json_string, "}");
+}
+
 /*
  * _gdata_parsable_is_constructed_from_xml:
  * @self: a #GDataParsable
diff --git a/gdata/gdata-parsable.h b/gdata/gdata-parsable.h
index 3c5e516..8ac5b0f 100644
--- a/gdata/gdata-parsable.h
+++ b/gdata/gdata-parsable.h
@@ -74,10 +74,14 @@ typedef struct {
  * XML node to be added to @xml_string
  * @get_xml: a function to build an XML representation of the #GDataParsable in its current state, appending 
it to the provided #GString
  * @get_namespaces: a function to return a string containing the namespace declarations used by the 
@parsable when represented in XML form
+ * @parse_json: a function to parse a JSON representation of the #GDataParsable to set the properties of the 
@parsable
+ * @post_parse_json: a function called after parsing a JSON object, to allow the @parsable to validate the 
parsed properties
+ * @get_json: a function to build a JSON representation of the #GDataParsable in its current state, 
appending it to the provided #GString
  * @element_name: the name of the XML element which represents this parsable
  * @element_namespace: the prefix of the XML namespace used for the parsable
  *
- * The class structure for the #GDataParsable class.
+ * The class structure for the #GDataParsable class. Note that JSON and XML functions are mutually exclusive:
+ * a given implementation of #GDataParsable is represented as exactly one of JSON and XML.
  *
  * Since: 0.3.0
  **/
@@ -95,7 +99,7 @@ typedef struct {
        gboolean (*parse_json) (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError 
**error);
        gboolean (*post_parse_json) (GDataParsable *parsable, gpointer user_data, GError **error);
        void (*get_json) (GDataParsable *parsable, GString *json_string);
-       
+
        const gchar *element_name;
        const gchar *element_namespace;
 } GDataParsableClass;
@@ -106,6 +110,10 @@ GDataParsable *gdata_parsable_new_from_xml (GType parsable_type, const gchar *xm
                                             GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 gchar *gdata_parsable_get_xml (GDataParsable *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
+GDataParsable *gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length,
+                                             GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+gchar *gdata_parsable_get_json (GDataParsable *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
 G_END_DECLS
 
 #endif /* !GDATA_PARSABLE_H */
diff --git a/gdata/gdata-parser.c b/gdata/gdata-parser.c
index fc51907..5520b1a 100644
--- a/gdata/gdata-parser.c
+++ b/gdata/gdata-parser.c
@@ -27,7 +27,6 @@
 #include <libxml/parser.h>
 #include <json-glib/json-glib.h>
 
-
 #include "gdata-parser.h"
 #include "gdata-service.h"
 #include "gdata-private.h"
@@ -265,14 +264,13 @@ gdata_parser_int64_from_iso8601 (const gchar *date, gint64 *_time)
 gboolean
 gdata_parser_error_required_json_content_missing (JsonReader *reader, GError **error)
 {
-       gchar *element_string = (gchar*) json_reader_get_member_name (reader);
+       const gchar *element_string = json_reader_get_member_name (reader);
 
        /* Translators: the parameter is the name of an JSON element.
         *
         * For example:
         *  A 'title' element was missing required content. */
        g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A \'%s\' element was 
missing required content."), element_string);
-       g_free (element_string);
 
        return FALSE;
 }
@@ -280,14 +278,13 @@ gdata_parser_error_required_json_content_missing (JsonReader *reader, GError **e
 gboolean
 gdata_parser_error_duplicate_json_element (JsonReader *reader, GError **error)
 {
-       gchar *element_string = (gchar*) json_reader_get_member_name (reader);
+       const gchar *element_string = json_reader_get_member_name (reader);
 
        /* Translators: the parameter is the name of an JSON element.
         *
         * For example:
         *  A singleton element (title) was duplicated. */
        g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A singleton element 
(%s) was duplicated."), element_string);
-       g_free (element_string);
 
        return FALSE;
 }
@@ -295,7 +292,8 @@ gdata_parser_error_duplicate_json_element (JsonReader *reader, GError **error)
 gboolean
 gdata_parser_error_not_iso8601_format_json (JsonReader *reader, const gchar *actual_value, GError **error)
 {
-       gchar *element_string = (gchar*) json_reader_get_member_name (reader);
+       const gchar *element_string = json_reader_get_member_name (reader);
+
        g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
                     /* Translators: the first parameter is the name of an JSON element,
                      * and the second parameter is the erroneous value (which was not in ISO 8601 format).
@@ -303,7 +301,16 @@ gdata_parser_error_not_iso8601_format_json (JsonReader *reader, const gchar *act
                      * For example:
                      *  The content of a 'uploaded' element ("2009-05-06 26:30Z") was not in ISO 8601 
format. */
                     _("The content of a \'%s\' element (\"%s\") was not in ISO 8601 format."), 
element_string, actual_value);
-       g_free (element_string);
+
+       return FALSE;
+}
+
+static gboolean
+parser_error_from_json_error (JsonReader *reader, const GError *json_error, GError **error)
+{
+       g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+                    /* Translators: the parameter is an error message. */
+                    _("Invalid JSON was received from the server: %s"), json_error->message);
 
        return FALSE;
 }
@@ -748,13 +755,14 @@ gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, G
  *
  * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
  *
- * Since: 0.7.0
+ * Since: UNRELEASED
  */
 gboolean
 gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions 
options,
-                                  gchar **output, gboolean *success, GError **error)
+                                      gchar **output, gboolean *success, GError **error)
 {
-       gchar *text;
+       const gchar *text;
+       const GError *child_error = NULL;
 
        /* Check if there's such element */
        if (strcmp (json_reader_get_member_name (reader), member_name) != 0) {
@@ -767,19 +775,22 @@ gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_na
                return TRUE;
        }
 
-       /* Get the string and check it for NULLness or emptiness */
-       text = (gchar*) json_reader_get_string_value (reader);
-       if ((options & P_REQUIRED && text == NULL) || (options & P_NON_EMPTY && text != NULL && *text == 
'\0')) {
-               g_free (text);
+       /* Get the string and check it for NULLness or emptiness. Check for parser errors first. */
+       text = json_reader_get_string_value (reader);
+       child_error = json_reader_get_error (reader);
+       if (child_error != NULL) {
+               *success = parser_error_from_json_error (reader, child_error, error);
+               return TRUE;
+       } else if ((options & P_REQUIRED && text == NULL) || (options & P_NON_EMPTY && text != NULL && *text 
== '\0')) {
                *success = gdata_parser_error_required_json_content_missing (reader, error);
                return TRUE;
        } else if (options & P_DEFAULT && (text == NULL || *text == '\0')) {
-               text = (gchar*) g_strdup ("");
+               text = "";
        }
 
        /* Success! */
        g_free (*output);
-       *output = (gchar*) text;
+       *output = g_strdup (text);
        *success = TRUE;
 
        return TRUE;
@@ -812,14 +823,15 @@ gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_na
  *
  * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
  *
- * Since: 0.7.0
+ * Since: UNRELEASED
  */
 gboolean
 gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions 
options,
-                                      gint64 *output, gboolean *success, GError **error)
+                                          gint64 *output, gboolean *success, GError **error)
 {
-       gchar *text;
+       const gchar *text;
        GTimeVal time_val;
+       const GError *child_error = NULL;
 
        /* Check if there's such element */
        if (strcmp (json_reader_get_member_name (reader), member_name) != 0) {
@@ -832,10 +844,13 @@ gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *membe
                return TRUE;
        }
 
-       /* Get the string and check it for NULLness */
-       text = (gchar*) json_reader_get_string_value (reader);
-       if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
-               g_free (text);
+       /* Get the string and check it for NULLness. Check for errors first. */
+       text = json_reader_get_string_value (reader);
+       child_error = json_reader_get_error (reader);
+       if (child_error != NULL) {
+               *success = parser_error_from_json_error (reader, child_error, error);
+               return TRUE;
+       } else if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
                *success = gdata_parser_error_required_json_content_missing (reader, error);
                return TRUE;
        }
@@ -843,32 +858,66 @@ gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *membe
        /* Attempt to parse the string as a GTimeVal */
        if (g_time_val_from_iso8601 ((gchar*) text, &time_val) == FALSE) {
                *success = gdata_parser_error_not_iso8601_format_json (reader, text, error);
-               g_free (text);
                return TRUE;
        }
 
-       *output = time_val.tv_sec;
-
        /* Success! */
-       g_free (text);
+       *output = time_val.tv_sec;
        *success = TRUE;
 
        return TRUE;
 }
 
-/* FIXME API description */
+/*
+ * gdata_parser_boolean_from_json_member:
+ * @reader: #JsonReader cursor object to read the JSON node from
+ * @member_name: the name of the JSON object member to parse
+ * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
+ * @output: (out caller-allocates): the return location for the parsed boolean value
+ * @success: (out caller-allocates): the return location for a value which is %TRUE if the boolean was 
parsed successfully, %FALSE if an error was encountered,
+ * and undefined if @member_name was not found in the current object in @reader
+ * @error: (allow-none): a #GError, or %NULL
+ *
+ * Gets the boolean value of the @member_name member of the current object in the #JsonReader, subject to 
various checks specified by @options.
+ *
+ * If no member matching @member_name can be found in the current @reader JSON object, %FALSE will be 
returned, @error will be unset and @success will be unset. @output will be undefined.
+ *
+ * If @member_name is found but one of the checks specified by @options fails, %TRUE will be returned, 
@error will be set to a
+ * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE. @output will be undefined.
+ *
+ * If @member_name is found and all of the checks specified by @options pass, %TRUE will be returned, @error 
will be unset and
+ * @success will be set to %TRUE. @output will be set to the parsed value.
+ *
+ * The reason for returning the success of the parsing in @success is so that calls to 
gdata_parser_boolean_from_json_node() can be chained
+ * together in a large "or" statement based on their return values, for the purposes of determining whether 
any of the calls matched
+ * a given JSON node. If any of the calls to gdata_parser_boolean_from_json_node() return %TRUE, the value 
of @success can be examined.
+ *
+ * Return value: %TRUE if @member_name was found, %FALSE otherwise
+ *
+ * Since: UNRELEASED
+ */
 gboolean
 gdata_parser_boolean_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions 
options, gboolean *output, gboolean *success, GError **error)
 {
-       
-       /* Check if there's such element */
+       gboolean val;
+       const GError *child_error = NULL;
+
+       /* Check if there's such an element. */
        if (strcmp (json_reader_get_member_name (reader), member_name) != 0) {
                return FALSE;
        }
-       
-       /* Get the boolean */
-       /* FIXME check json reader error if it fails to read it*/
-       *output = json_reader_get_boolean_value (reader);
+
+       /* Get the boolean. Check for parse errors. */
+       val = json_reader_get_boolean_value (reader);
+       child_error = json_reader_get_error (reader);
+       if (child_error != NULL) {
+               *success = parser_error_from_json_error (reader, child_error, error);
+               return TRUE;
+       }
+
+       /* Success! */
+       *output = val;
+       *success = TRUE;
 
        return TRUE;
 }
diff --git a/gdata/gdata-parser.h b/gdata/gdata-parser.h
index a94a9b0..2c5ad2f 100644
--- a/gdata/gdata-parser.h
+++ b/gdata/gdata-parser.h
@@ -85,11 +85,11 @@ gboolean gdata_parser_object_from_element_setter (xmlNode *element, const gchar
 gboolean gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions 
options, GType object_type,
                                            gpointer /* GDataParsable ** */ _output, gboolean *success, 
GError **error);
 gboolean gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
-                                           gchar **output, gboolean *success, GError **error);
+                                               gchar **output, gboolean *success, GError **error);
 gboolean gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
-                                               gint64 *output, gboolean *success, GError **error);
+                                                   gint64 *output, gboolean *success, GError **error);
 gboolean gdata_parser_boolean_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
-                                               gboolean *output, gboolean *success, GError **error);
+                                                gboolean *output, gboolean *success, GError **error);
 
 void gdata_parser_string_append_escaped (GString *xml_string, const gchar *pre, const gchar 
*element_content, const gchar *post);
 gchar *gdata_parser_utf8_trim_whitespace (const gchar *s) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 0ffcd87..62ae7d0 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -80,11 +80,11 @@ G_GNUC_INTERNAL GDataParsable *_gdata_parsable_new_from_xml (GType parsable_type
 G_GNUC_INTERNAL GDataParsable *_gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode 
*node, gpointer user_data,
                                                                   GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;
 G_GNUC_INTERNAL GDataParsable *_gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint 
length, gpointer user_data,
-                                                             GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;         
+                                                              GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;
 G_GNUC_INTERNAL GDataParsable *_gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, 
gpointer user_data,
-                                                                  GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;
-                                                                  
+                                                                   GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;
 G_GNUC_INTERNAL void _gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean 
declare_namespaces);
+G_GNUC_INTERNAL void _gdata_parsable_get_json (GDataParsable *self, GString *json_string);
 G_GNUC_INTERNAL void _gdata_parsable_string_append_escaped (GString *xml_string, const gchar *pre, const 
gchar *element_content, const gchar *post);
 G_GNUC_INTERNAL gboolean _gdata_parsable_is_constructed_from_xml (GDataParsable *self);
 
@@ -95,7 +95,7 @@ G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_xml (GType feed_type, const gcha
                                                      GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_json (GType feed_type, const gchar *json, gint length, GType 
entry_type,
                                                      GDataQueryProgressCallback progress_callback, gpointer 
progress_user_data, gboolean is_async,
-                                                     GError **error) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;                                                     
+                                                     GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 G_GNUC_INTERNAL void _gdata_feed_add_entry (GDataFeed *self, GDataEntry *entry);
 G_GNUC_INTERNAL gpointer _gdata_feed_parse_data_new (GType entry_type, GDataQueryProgressCallback 
progress_callback, gpointer progress_user_data,
                                                      gboolean is_async);
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index f9b27a4..9d446c9 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -917,25 +917,29 @@ __gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, con
        GDataFeed *feed = NULL;
        SoupMessage *message;
        SoupMessageHeaders *headers;
-       gchar *content_type;
-       
+       const gchar *content_type;
+
        message = _gdata_service_query (self, domain, feed_uri, query, cancellable, error);
        if (message == NULL)
                return NULL;
 
        g_assert (message->response_body->data != NULL);
        klass = GDATA_SERVICE_GET_CLASS (self);
-       
+
        headers = message->response_headers;
-       content_type = (gchar*) soup_message_headers_get_content_type (headers, NULL);
-       if (strncmp (content_type, "application/json", 16) == 0) {
-               g_debug("JSON content type detected.\n");
+       content_type = soup_message_headers_get_content_type (headers, NULL);
+
+       if (content_type != NULL && strcmp (content_type, "application/json") == 0) {
+               /* Definitely JSON. */
+               g_debug("JSON content type detected.");
                feed = _gdata_feed_new_from_json (klass->feed_type, message->response_body->data, 
message->response_body->length, entry_type,
-                                                                               progress_callback, 
progress_user_data, is_async, error);
-       } else if (strncmp (content_type, "application/atom+xml", 20) == 0) {
-               g_debug("XML content type detected.\n");
+                                                 progress_callback, progress_user_data, is_async, error);
+       } else {
+               /* Potentially XML. Don't bother checking the Content-Type, since the parser
+                * will fail gracefully if the response body is not valid XML. */
+               g_debug("XML content type detected.");
                feed = _gdata_feed_new_from_xml (klass->feed_type, message->response_body->data, 
message->response_body->length, entry_type,
-                                                                               progress_callback, 
progress_user_data, is_async, error);
+                                                progress_callback, progress_user_data, is_async, error);
        }
 
        g_object_unref (message);
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index f073ad2..bc65405 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -308,6 +308,8 @@ gdata_access_rule_get_edited
 gdata_parsable_get_type
 gdata_parsable_new_from_xml
 gdata_parsable_get_xml
+gdata_parsable_new_from_json
+gdata_parsable_get_json
 gdata_calendar_feed_get_type
 gdata_calendar_feed_get_timezone
 gdata_calendar_feed_get_times_cleaned
diff --git a/gdata/tests/common.c b/gdata/tests/common.c
index 382ea73..72e4417 100644
--- a/gdata/tests/common.c
+++ b/gdata/tests/common.c
@@ -630,6 +630,167 @@ gdata_test_compare_xml (GDataParsable *parsable, const gchar *expected_xml, gboo
        return success;
 }
 
+static gboolean
+compare_json_nodes (JsonNode *node1, JsonNode *node2)
+{
+       if (node1 == node2)
+               return TRUE;
+
+       if (JSON_NODE_TYPE (node1) != JSON_NODE_TYPE (node2))
+               return FALSE;
+
+       switch (JSON_NODE_TYPE (node1)) {
+               case JSON_NODE_OBJECT: {
+                       JsonObject *object1, *object2;
+                       guint size1, size2;
+                       GList *members, *i;
+
+                       object1 = json_node_get_object (node1);
+                       object2 = json_node_get_object (node2);
+
+                       size1 = json_object_get_size (object1);
+                       size2 = json_object_get_size (object2);
+
+                       if (size1 != size2)
+                               return FALSE;
+
+                       /* Iterate over the first object, checking that every member is also present in the 
second object. */
+                       members = json_object_get_members (object1);
+
+                       for (i = members; i != NULL; i = i->next) {
+                               JsonNode *child_node1, *child_node2;
+
+                               child_node1 = json_object_get_member (object1, i->data);
+                               child_node2 = json_object_get_member (object2, i->data);
+
+                               g_assert (child_node1 != NULL);
+                               if (child_node2 == NULL) {
+                                       g_list_free (members);
+                                       return FALSE;
+                               }
+
+                               if (compare_json_nodes (child_node1, child_node2) == FALSE) {
+                                       g_list_free (members);
+                                       return FALSE;
+                               }
+                       }
+
+                       g_list_free (members);
+
+                       return TRUE;
+               }
+               case JSON_NODE_ARRAY: {
+                       JsonArray *array1, *array2;
+                       guint length1, length2, i;
+
+                       array1 = json_node_get_array (node1);
+                       array2 = json_node_get_array (node2);
+
+                       length1 = json_array_get_length (array1);
+                       length2 = json_array_get_length (array2);
+
+                       if (length1 != length2)
+                               return FALSE;
+
+                       /* Iterate over both arrays, checking the elements at each index are identical. */
+                       for (i = 0; i < length1; i++) {
+                               JsonNode *child_node1, *child_node2;
+
+                               child_node1 = json_array_get_element (array1, i);
+                               child_node2 = json_array_get_element (array2, i);
+
+                               if (compare_json_nodes (child_node1, child_node2) == FALSE)
+                                       return FALSE;
+                       }
+
+                       return TRUE;
+               }
+               case JSON_NODE_VALUE: {
+                       GType type1, type2;
+
+                       type1 = json_node_get_value_type (node1);
+                       type2 = json_node_get_value_type (node2);
+
+                       if (type1 != type2)
+                               return FALSE;
+
+                       switch (type1) {
+                               case G_TYPE_BOOLEAN:
+                                       return (json_node_get_boolean (node1) == json_node_get_boolean 
(node2)) ? TRUE : FALSE;
+                               case G_TYPE_DOUBLE:
+                                       /* Note: This doesn't need an epsilon-based comparison because we 
only want to return
+                                        * true if the string representation of the two values is equal — and 
if it is, their
+                                        * parsed values should be binary identical too. */
+                                       return (json_node_get_double (node1) == json_node_get_double (node2)) 
? TRUE : FALSE;
+                               case G_TYPE_INT64:
+                                       return (json_node_get_int (node1) == json_node_get_int (node2)) ? 
TRUE : FALSE;
+                               case G_TYPE_STRING:
+                                       return (g_strcmp0 (json_node_get_string (node1), json_node_get_string 
(node2)) == 0) ? TRUE : FALSE;
+                               default:
+                                       /* JSON doesn't support any other types. */
+                                       g_assert_not_reached ();
+                       }
+
+                       return TRUE;
+               }
+               case JSON_NODE_NULL:
+                       return TRUE;
+               default:
+                       g_assert_not_reached ();
+       }
+}
+
+gboolean
+gdata_test_compare_json_strings (const gchar *parsable_json, const gchar *expected_json, gboolean 
print_error)
+{
+       gboolean success;
+       JsonParser *parsable_parser, *expected_parser;
+       GError *child_error = NULL;
+
+       /* Parse both strings. */
+       parsable_parser = json_parser_new ();
+       expected_parser = json_parser_new ();
+
+       json_parser_load_from_data (parsable_parser, parsable_json, -1, &child_error);
+       if (child_error != NULL) {
+               if (print_error == TRUE) {
+                       g_message ("\n\nParsable: %s\n\nNot valid JSON: %s", parsable_json, 
child_error->message);
+               }
+
+               g_error_free (child_error);
+               return FALSE;
+       }
+
+       json_parser_load_from_data (expected_parser, expected_json, -1, &child_error);
+       g_assert_no_error (child_error); /* this really should never fail; or the test has encoded bad JSON */
+
+       /* Recursively compare the two JSON nodes. */
+       success = compare_json_nodes (json_parser_get_root (parsable_parser), json_parser_get_root 
(expected_parser));
+       if (success == FALSE && print_error == TRUE) {
+               /* The comparison has failed, so print out the two JSON strings for ease of debugging */
+               g_message ("\n\nParsable: %s\n\nExpected: %s\n\n", parsable_json, expected_json);
+       }
+
+       g_object_unref (expected_parser);
+       g_object_unref (parsable_parser);
+
+       return success;
+}
+
+gboolean
+gdata_test_compare_json (GDataParsable *parsable, const gchar *expected_json, gboolean print_error)
+{
+       gboolean success;
+       gchar *parsable_json;
+
+       /* Get a JSON string for the GDataParsable. */
+       parsable_json = gdata_parsable_get_json (parsable);
+       success = gdata_test_compare_json_strings (parsable_json, expected_json, print_error);
+       g_free (parsable_json);
+
+       return success;
+}
+
 gboolean
 gdata_test_compare_kind (GDataEntry *entry, const gchar *expected_term, const gchar *expected_label)
 {
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index 122608b..1159ef5 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -64,15 +64,24 @@ gboolean gdata_test_batch_operation_run_finish (GDataBatchOperation *operation,
 gboolean gdata_test_compare_xml_strings (const gchar *parsable_xml, const gchar *expected_xml, gboolean 
print_error);
 gboolean gdata_test_compare_xml (GDataParsable *parsable, const gchar *expected_xml, gboolean print_error);
 
+gboolean gdata_test_compare_json_strings (const gchar *parsable_json, const gchar *expected_json, gboolean 
print_error);
+gboolean gdata_test_compare_json (GDataParsable *parsable, const gchar *expected_json, gboolean print_error);
+
 gboolean gdata_test_compare_kind (GDataEntry *entry, const gchar *expected_term, const gchar 
*expected_label);
 
-/* Convenience macro */
+/* Convenience macros. */
 #define gdata_test_assert_xml(Parsable, XML) \
        G_STMT_START { \
                gboolean _test_success = gdata_test_compare_xml (GDATA_PARSABLE (Parsable), XML, TRUE); \
                g_assert (_test_success == TRUE); \
        } G_STMT_END
 
+#define gdata_test_assert_json(Parsable, JSON) \
+       G_STMT_START { \
+               gboolean _test_success = gdata_test_compare_json (GDATA_PARSABLE (Parsable), JSON, TRUE); \
+               g_assert (_test_success == TRUE); \
+       } G_STMT_END
+
 /* Common code for tests of async query functions that have progress callbacks */
 typedef struct {
     guint progress_destroy_notify_count;
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index f4b6d4e..7268771 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -456,6 +456,58 @@ test_entry_get_xml (void)
 }
 
 static void
+test_entry_get_json (void)
+{
+       gint64 updated, published, updated2, published2, updated3, published3;
+       GDataEntry *entry, *entry2;
+       GDataCategory *category;
+       GDataLink *_link; /* stupid unistd.h */
+       GDataAuthor *author;
+       gchar *json, *title, *summary, *id, *etag, *content, *content_uri, *rights;
+       gboolean is_inserted;
+       GList *list;
+       GError *error = NULL;
+
+       entry = gdata_entry_new (NULL);
+
+       /* Set the properties more conventionally */
+       gdata_entry_set_title (entry, "Testing title & 'escaping'");
+       gdata_entry_set_summary (entry, NULL);
+       gdata_entry_set_content (entry, "This is some sample content testing, amongst other things, <markup> 
& odd characters‽");
+       gdata_entry_set_rights (entry, NULL);
+
+       /* Check the generated JSON's OK */
+       gdata_test_assert_json (entry,
+               "{"
+                       "\"title\": \"Testing title & &apos;escaping&apos;\""
+               "}");
+
+       /* Check again by re-parsing the JSON to a GDataEntry. */
+       json = gdata_parsable_get_json (GDATA_PARSABLE (entry));
+       entry2 = GDATA_ENTRY (gdata_parsable_new_from_json (GDATA_TYPE_ENTRY, json, -1, &error));
+       g_assert_no_error (error);
+       g_assert (GDATA_IS_ENTRY (entry2));
+       g_clear_error (&error);
+       g_free (json);
+
+       g_assert_cmpstr (gdata_entry_get_title (entry), ==, gdata_entry_get_title (entry2));
+       g_assert_cmpstr (gdata_entry_get_id (entry), ==, gdata_entry_get_id (entry2)); /* should both be NULL 
*/
+       g_assert_cmpstr (gdata_entry_get_content (entry), ==, gdata_entry_get_content (entry2));
+       g_assert_cmpstr (gdata_entry_get_content_uri (entry), ==, gdata_entry_get_content_uri (entry2)); /* 
should both be NULL */
+
+       updated = gdata_entry_get_updated (entry);
+       updated2 = gdata_entry_get_updated (entry2);
+       g_assert_cmpuint (updated, ==, updated2);
+
+       published = gdata_entry_get_published (entry);
+       published2 = gdata_entry_get_published (entry2);
+       g_assert_cmpuint (published, ==, published2);
+
+       g_object_unref (entry);
+       g_object_unref (entry2);
+}
+
+static void
 test_entry_parse_xml (void)
 {
        GDataEntry *entry;
@@ -3994,6 +4046,7 @@ main (int argc, char *argv[])
        g_test_add_func ("/service/locale", test_service_locale);
 
        g_test_add_func ("/entry/get_xml", test_entry_get_xml);
+       g_test_add_func ("/entry/get_json", test_entry_get_json);
        g_test_add_func ("/entry/parse_xml", test_entry_parse_xml);
        g_test_add_func ("/entry/error_handling", test_entry_error_handling);
        g_test_add_func ("/entry/escaping", test_entry_escaping);



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