[libgdata/tasks-integration: 2/2] core: Various improvements and the beginnings of some unit tests
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgdata/tasks-integration: 2/2] core: Various improvements and the beginnings of some unit tests
- Date: Tue, 27 Aug 2013 17:43:49 +0000 (UTC)
commit 98c54f127a8e088881d3ca4b7e7bd1a23af8b826
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 & 'escaping'\""
+ "}");
+
+ /* 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]