mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for errors in JSON format.
[#1956 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
This commit is contained in:
parent
793a9f122f
commit
797588543e
4 changed files with 83 additions and 26 deletions
|
@ -1,5 +1,7 @@
|
||||||
*Edge*
|
*Edge*
|
||||||
|
|
||||||
|
* Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
|
||||||
|
|
||||||
* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
|
* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
|
||||||
|
|
||||||
* More thorough SSL support. #2370 [Roy Nicholson]
|
* More thorough SSL support. #2370 [Roy Nicholson]
|
||||||
|
|
|
@ -185,7 +185,7 @@ module ActiveResource
|
||||||
#
|
#
|
||||||
# Active Resource supports validations on resources and will return errors if any of these validations fail
|
# Active Resource supports validations on resources and will return errors if any of these validations fail
|
||||||
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
||||||
# a response code of <tt>422</tt> and an XML representation of the validation errors. The save operation will
|
# a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
|
||||||
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
||||||
#
|
#
|
||||||
# ryan = Person.find(1)
|
# ryan = Person.find(1)
|
||||||
|
@ -194,10 +194,14 @@ module ActiveResource
|
||||||
#
|
#
|
||||||
# # When
|
# # When
|
||||||
# # PUT http://api.people.com:3000/people/1.xml
|
# # PUT http://api.people.com:3000/people/1.xml
|
||||||
|
# # or
|
||||||
|
# # PUT http://api.people.com:3000/people/1.json
|
||||||
# # is requested with invalid values, the response is:
|
# # is requested with invalid values, the response is:
|
||||||
# #
|
# #
|
||||||
# # Response (422):
|
# # Response (422):
|
||||||
# # <errors type="array"><error>First cannot be empty</error></errors>
|
# # <errors type="array"><error>First cannot be empty</error></errors>
|
||||||
|
# # or
|
||||||
|
# # {"errors":["First cannot be empty"]}
|
||||||
# #
|
# #
|
||||||
#
|
#
|
||||||
# ryan.errors.invalid?(:first) # => true
|
# ryan.errors.invalid?(:first) # => true
|
||||||
|
|
|
@ -7,11 +7,10 @@ module ActiveResource
|
||||||
# Active Resource validation is reported to and from this object, which is used by Base#save
|
# Active Resource validation is reported to and from this object, which is used by Base#save
|
||||||
# to determine whether the object in a valid state to be saved. See usage example in Validations.
|
# to determine whether the object in a valid state to be saved. See usage example in Validations.
|
||||||
class Errors < ActiveModel::Errors
|
class Errors < ActiveModel::Errors
|
||||||
# Grabs errors from the XML response.
|
# Grabs errors from an array of messages (like ActiveRecord::Validations)
|
||||||
def from_xml(xml)
|
def from_array(messages)
|
||||||
clear
|
clear
|
||||||
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
||||||
messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
|
||||||
messages.each do |message|
|
messages.each do |message|
|
||||||
attr_message = humanized_attributes.keys.detect do |attr_name|
|
attr_message = humanized_attributes.keys.detect do |attr_name|
|
||||||
if message[0, attr_name.size + 1] == "#{attr_name} "
|
if message[0, attr_name.size + 1] == "#{attr_name} "
|
||||||
|
@ -22,6 +21,18 @@ module ActiveResource
|
||||||
self[:base] << message if attr_message.nil?
|
self[:base] << message if attr_message.nil?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Grabs errors from the json response.
|
||||||
|
def from_json(json)
|
||||||
|
array = ActiveSupport::JSON.decode(json)['errors'] rescue []
|
||||||
|
from_array array
|
||||||
|
end
|
||||||
|
|
||||||
|
# Grabs errors from the XML response.
|
||||||
|
def from_xml(xml)
|
||||||
|
array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
||||||
|
from_array array
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Module to support validation and errors with Active Resource objects. The module overrides
|
# Module to support validation and errors with Active Resource objects. The module overrides
|
||||||
|
@ -56,7 +67,12 @@ module ActiveResource
|
||||||
save_without_validation
|
save_without_validation
|
||||||
true
|
true
|
||||||
rescue ResourceInvalid => error
|
rescue ResourceInvalid => error
|
||||||
|
case error.response['Content-Type']
|
||||||
|
when 'application/xml'
|
||||||
errors.from_xml(error.response.body)
|
errors.from_xml(error.response.body)
|
||||||
|
when 'application/json'
|
||||||
|
errors.from_json(error.response.body)
|
||||||
|
end
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,41 +4,62 @@ require "fixtures/person"
|
||||||
class BaseErrorsTest < Test::Unit::TestCase
|
class BaseErrorsTest < Test::Unit::TestCase
|
||||||
def setup
|
def setup
|
||||||
ActiveResource::HttpMock.respond_to do |mock|
|
ActiveResource::HttpMock.respond_to do |mock|
|
||||||
mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 422
|
mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml'}
|
||||||
|
mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json'}
|
||||||
end
|
end
|
||||||
@person = Person.new(:name => '', :age => '')
|
|
||||||
assert_equal @person.save, false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_should_mark_as_invalid
|
def test_should_mark_as_invalid
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
assert !@person.valid?
|
assert !@person.valid?
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_parse_xml_errors
|
def test_should_parse_xml_errors
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
assert_kind_of ActiveResource::Errors, @person.errors
|
assert_kind_of ActiveResource::Errors, @person.errors
|
||||||
assert_equal 4, @person.errors.size
|
assert_equal 4, @person.errors.size
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_parse_errors_to_individual_attributes
|
def test_should_parse_errors_to_individual_attributes
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
assert @person.errors[:name].any?
|
assert @person.errors[:name].any?
|
||||||
assert_equal ["can't be blank"], @person.errors[:age]
|
assert_equal ["can't be blank"], @person.errors[:age]
|
||||||
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
|
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
|
||||||
assert_equal ["Person quota full for today."], @person.errors[:base]
|
assert_equal ["Person quota full for today."], @person.errors[:base]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_iterate_over_errors
|
def test_should_iterate_over_errors
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
errors = []
|
errors = []
|
||||||
@person.errors.each { |attribute, message| errors << [attribute.to_s, message] }
|
@person.errors.each { |attribute, message| errors << [attribute, message] }
|
||||||
assert errors.include?(["name", "can't be blank"])
|
assert errors.include?([:name, "can't be blank"])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_should_iterate_over_full_errors
|
def test_should_iterate_over_full_errors
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
errors = []
|
errors = []
|
||||||
@person.errors.to_a.each { |message| errors << message }
|
@person.errors.to_a.each { |message| errors << message }
|
||||||
assert errors.include?("Name can't be blank")
|
assert errors.include?("Name can't be blank")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_format_full_errors
|
def test_should_format_full_errors
|
||||||
|
[ :json, :xml ].each do |format|
|
||||||
|
invalid_user_using_format(format) do
|
||||||
full = @person.errors.full_messages
|
full = @person.errors.full_messages
|
||||||
assert full.include?("Age can't be blank")
|
assert full.include?("Age can't be blank")
|
||||||
assert full.include?("Name can't be blank")
|
assert full.include?("Name can't be blank")
|
||||||
|
@ -46,3 +67,17 @@ class BaseErrorsTest < Test::Unit::TestCase
|
||||||
assert full.include?("Person quota full for today.")
|
assert full.include?("Person quota full for today.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def invalid_user_using_format(mime_type_reference)
|
||||||
|
previous_format = Person.format
|
||||||
|
Person.format = mime_type_reference
|
||||||
|
@person = Person.new(:name => '', :age => '')
|
||||||
|
assert_equal false, @person.save
|
||||||
|
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Person.format = previous_format
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue