From 60481f879ef3b95254f76347f425041b26b5363c Mon Sep 17 00:00:00 2001
From: Dylan Smith <Dylan.Smith@shopify.com>
Date: Sun, 22 Apr 2012 02:10:29 -0400
Subject: [PATCH 4/4] Handle errors for json format.

---
 src/activeresource.py            |   59 +++++++++++++++++++++++++++++++-------
 src/tests/activeresource_test.py |   17 +++++++++++
 2 files changed, 66 insertions(+), 10 deletions(-)

diff --git a/src/activeresource.py b/src/activeresource.py
index 23a65b1..0b58f9d 100755
--- a/src/activeresource.py
+++ b/src/activeresource.py
@@ -75,6 +75,25 @@ class Errors(object):
         """
         self.errors = {}
 
+    def from_array(self, messages):
+        attribute_keys = self.base.attributes.keys()
+        for message in messages:
+            attr_name = message.split()[0]
+            key = util.underscore(attr_name)
+            if key in attribute_keys:
+                self.add(key, message[len(attr_name)+1:])
+            else:
+                self.add_to_base(message)
+
+    def from_hash(self, messages):
+        attribute_keys = self.base.attributes.keys()
+        for key, errors in messages.iteritems():
+            for message in errors:
+                if key in attribute_keys:
+                    self.add(key, message)
+                else:
+                    self.add_to_base(message)
+
     def from_xml(self, xml_string):
         """Grab errors from an XML response.
 
@@ -83,21 +102,38 @@ class Errors(object):
         Returns:
             None
         """
-        attribute_keys = self.base.attributes.keys()
         try:
-            messages = util.xml_to_dict(
-                    xml_string)['errors']['error']
+            messages = util.xml_to_dict(xml_string)['errors']['error']
             if not isinstance(messages, list):
                 messages = [messages]
         except util.Error:
             messages = []
-        for message in messages:
-            attr_name = message.split()[0]
-            key = util.underscore(attr_name)
-            if key in attribute_keys:
-                self.add(key, message[len(attr_name)+1:])
+        self.from_array(messages)
+
+    def from_json(self, json_string):
+        """Grab errors from a JSON response.
+
+        Args:
+            json_string: An json errors object (e.g. "{ 'errors': {} }")
+        Returns:
+            None
+        """
+        try:
+            decoded = util.json_to_dict(json_string)
+        except ValueError:
+            decoded = {}
+        if not decoded:
+            decoded = {}
+        if isinstance(decoded, dict) and (decoded.has_key('errors') or len(decoded) == 0):
+            errors = decoded.get('errors', {})
+            if isinstance(errors, list):
+                # Deprecated in ActiveResource
+                self.from_array(errors)
             else:
-                self.add_to_base(message)
+                self.from_hash(errors)
+        else:
+            # Deprecated in ActiveResource
+            self.from_hash(decoded)
 
     def on(self, attribute):
         """Return the errors for the given attribute.
@@ -791,7 +827,10 @@ class ActiveResource(object):
                 if new_id:
                     self.id = new_id
         except connection.ResourceInvalid, err:
-            self.errors.from_xml(err.response.body)
+            if self.klass.format == formats.XMLFormat:
+                self.errors.from_xml(err.response.body)
+            elif self.klass.format == formats.JSONFormat:
+                self.errors.from_json(err.response.body)
             return False
         try:
             attributes = self.klass.format.decode(response.body)
diff --git a/src/tests/activeresource_test.py b/src/tests/activeresource_test.py
index 6a1ca6c..64edbd6 100644
--- a/src/tests/activeresource_test.py
+++ b/src/tests/activeresource_test.py
@@ -275,6 +275,23 @@ class ActiveResourceTest(unittest.TestCase):
       store.save()
       self.assertEqual(0, store.errors.size)
 
+    def test_save_with_errors(self):
+        self.http.respond_to('POST', '/stores.xml', self.xml_headers,
+                '''<errors>
+                    <error>Name already exists</error>
+                </errors>''', 422)
+        store = self.store(self.store_new)
+        self.assertEqual(False, store.save())
+        self.assertEqual({ 'name': ['already exists'] }, store.errors.errors)
+
+    def test_save_with_json_errors(self):
+        self.http.respond_to('POST', '/stores.json', self.json_headers,
+                '''{"errors":{"name":["already exists"]}}''', 422)
+        self.store.format = formats.JSONFormat
+        store = self.store(self.store_new)
+        self.assertEqual(False, store.save())
+        self.assertEqual({ 'name': ['already exists'] }, store.errors.errors)
+
     def test_class_get(self):
         self.http.respond_to('GET', '/people/retrieve.xml?name=Matz',
                              {}, self.matz_array)
-- 
1.7.10

