ruby-grape / grape

An opinionated framework for creating REST-like APIs in Ruby.
http://www.ruby-grape.org
MIT License
9.88k stars 1.22k forks source link

Sending a single element in an array using XML format fails with the request being invalid #1662

Open ramkumar-kr opened 7 years ago

ramkumar-kr commented 7 years ago

Hi,

I have an API which accepts requests using XML. It accepts an array of products. However for a case where there is only one product in the array, validation errors occur. Sample request - `curl -X POST \ http://localhost:3000/v1/products \ -H 'cache-control: no-cache' \ -H 'content-type: application/xml' \ -d '<?xml version="1.0" encoding="UTF-8" ?>

1 100 '` This is because when there is a single element in an array, the product will be parsed to a hash instead of an array of hash objects. Expected - `{ admin: { products: [{ id: "1", price: "100" }] } }` Actual - `{ admin: { products: { id: "1", price: "100" } } }` the parameters are defined as ``` params do requires :admin, type: Hash do requires :products, type: Array do requires :id, type: String requires :price, type: String end end end ``` Currently to overcome this, I run the following `before_validation` block ``` before_validation do params[:admin][:products] = [params[:admin][:products]] if params[:admin][:products].is_a?(Hash) end ``` I was wondering since grape knows the request type (Array or Hash), can it convert the hash to an array before validating (similar to the before_validation block). Notes: Using Grape version : 1.0.0 Sample code - [https://github.com/ramkumar-kr/grape-xml-error](https://github.com/ramkumar-kr/grape-xml-error)
dblock commented 7 years ago

In your example you send a single product, it cannot guess that it's an array, but maybe you're right and it should infer and coerce it it because of what's in the params block. I say write a spec for this next and lets see if we can implement/fix this.

ramkumar-kr commented 7 years ago

Hi, Sorry for the late reply. I have created a Spec and have sent a pull request for it.

Smeedy commented 6 years ago

We also bumped into this one the other day. This is due to the XML parsing of the arrays using the default ActiveSupport::XmlMini. We noticed that Nokogiri handles the XML arrays correctly using some test XML on the irb.

So the less intrusive solution is using MultiXML and Nokogiri as the parser in the project and not rewrite the params in the before hook.

So the use of an initializer like

require 'multi_xml'

# Force all the XML parsing to be handled by Nokogiri
MultiXml.parser = :nokogiri
MultiXml.parser = MultiXml::Parsers::Nokogiri # Same as above

will set the definition. The use of MultiXml will be picked up by the Grape framework and Bob's your uncle.

See: https://github.com/ruby-grape/grape#json-and-xml-processors

dblock commented 6 years ago

@Smeedy Do you think you can turn the spec linked above into a passing one and add some documentation and PR the thing?