Sending raw XML using Savon 2 - ruby

I'm trying to use Savon to send requests to a webservice. The service I'm consuming requires nested namespaces, and I haven't figured out yet how to provide them on a request.
I've tried to craft the request by hand (with nokogiri, actually) and send the resulting xml:
client.call(:some_op, :message=>{:"op"=>"<elem/>"})
But savon escapes the string and sends <elem/>
How can I send raw xml without escaping?

The call should look like this:
client.call(:some_op, xml: "<elem />")
Or if you just want to set one or multiple namespaces then create a client as follows (without WSDL):
client = Savon.client(
:endpoint => 'http://www.example.com',
:namespace => 'urn:core.example.com',
:namespaces => { 'ns1' => 'http://v1.example.com',
'ns2' => 'http://v2.example.com' },
:log => true,
:log_level => :debug,
:pretty_print_xml => true
)
The namespaces are a Hash parameter.

It looks like Savon internally uses the Gyoku Gem to convert ruby hashes to XML, and Gyoku will not escape hash keys ending with exclamation marks according to the documentation: https://github.com/savonrb/gyoku#special-characters
So this code works to get raw XML into the request while still using Savon to generate the envelope xml:
client.call(:some_op, :message=>{:"op!"=>"<elem/>"})

Related

Ruby to_yaml stringifies my json

I am trying to convert a ruby hash to yaml. I'd like part of the hash be valid json; however, when I try to serialize the json string, it is converted to yaml in quotes.
For example, when I just have a simple string, the ouput is as follows (note foo is not in quotations):
request = {}
request['body'] = 'foo'
request.to_yaml # outputs: body: foo
However, when I add something to the beginning of the string, like { foo the output for body gets quoted:
request['body'] = '{ foo'
request.to_yaml # outputs: body: '{ foo'
How can I get around this? I've tried JSON.parse and, though that make work, I can't be guaranteed that this input will actually be json (could be xml, etc...) -- I just want to give back whatever was given to me but not "stringified".
Basically, I want to give an object that looks like:
{ 'request' => {
'url' => '/posts',
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json'
]
},
'response' => {
'code' => 200,
'body' => '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
}
}
Which returns:
request:
url: /posts
method: GET
headers:
- Content-Type: application/json
response:
code: 200
body:
[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]
The reason being: right now, I can go from yaml to the correct ruby hash but I can't go the other way.
The method my_hash.to_yaml() just takes a hash and converts it to YAML without doing anything special to the values. The method does not care whether your string is JSON or XML, it just treats it as a string.
So why is my JSON being put into quotes when other strings aren't?
Good question! The reason is simple: curly braces are a valid part of YAML syntax.
This:
my_key: { sub: 1, keys: 2}
Is called flow mapping syntax in YAML, and it allows you make nested mappings in one line. To escape strings which have curly braces in them, YAML uses quotes:
my_key: "{ sub: 1, keys: 2}" # this is just a string
Of course, the quotes are optional for all strings:
my_key: "foo" #same as my_key: foo
Okay, but I want to_yaml() to find my JSON string and convert it to YAML mappings like the rest of the hash.
Well then, you need to convert your JSON string to a hash like the rest of your hash. to_yaml() converts a hash to YAML. It doesn't convert strings to YAML. The proper method for doing this is to use JSON.parse, as you mentioned:
request['body'] = JSON.parse( '{"id":"ef4b3a"}' )
But the string might not be JSON! It might be XML or some other smelly string.
This is exactly why to_yaml() doesn't convert strings. A wise programmer once told me: "Strings are strings. Strings are not data structures. Strings are strings."
If you want to convert a string into a data structure, you need to validate it and parse it. Because there's no guarantee that a string will be valid, it's your responsibility as a programmer to determine whether your data is JSON or XML or just bad, and to decide how you want to respond to each bit of data.
Since it looks like you're parsing web pages, you might want to consider using the same bit of data other web clients use to parse these things:
{ 'request' => {
'url' => '/posts',
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json' #<== this guy, right here!
]
},
'response' => {
'code' => 200,
'body' => '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
}
}
If the content-type doesn't agree with the body then you should throw an error because your input data is bad.
The reason '{ foo' requires quote is because this is part of the YAML specification 7.3.3 Plain Style.
Excerpt
Plain scalars must never contain the “: ” and “#” character combinations. Such combinations would cause ambiguity with mapping key: value pairs and comments. In addition, inside flow collections, or when used as implicit keys, plain scalars must
not contain the “[”, “]”, “{”, “}” and “,” characters. These characters would cause ambiguity with flow collection structures.
Based on the above even your stated "return" value is incorrect and the body is probably enclosed in single quotes e.g.
response:
code: 200
body: '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
Otherwise it would create ambiguity with "Flow Sequences" ([,]) and "Flow Mappings" ({,}).
If you would like result of the JSON, XML or other notation language to be represented appropriately (read objectively) then you will need to determine the correct parser (may be from the "Content-Type") and parse it before converting it YAML

Ruby: HTTP Put method

I am attempting to update the 'ip' parameter in a json object in an API.
I have the following case:
when "put"
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = Hash.new
jobj['ip'] = "1.1.1.1"
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', '/api/v1/address_data/1.json', data = jobj.to_s)
end
This does not work, but this does:
curl -X PUT http://ip:port/api/v1/address_data/1.json -d "ip=1.1.1.1"
How do I more accurately translate the curl into a Put request in Ruby? I have tried several methods I've found through google searching, but none of them have had successful results.
A few things:
You're not sending JSON in the Ruby example, it's a string representation of a Ruby hash which isn't the same. You need the JSON module or similar.
In the Ruby code you're attempting to send a JSON object (which would look like {"ip":"1.1.1.1"} and in the curl example you're sending it in application/x-www-form-urlencoded format, so they're currently not equivalent.
Also I'd look at the type of data the server expects from your requests: both Ruby and curl send a request header of Content-Type: application/x-www-form-urlencoded by default, and you're expecting to send JSON. This is why the curl example works: the data format you're using and the header matches. Note the .json in the URL shouldn't really make any difference; the header takes precedence.
Your call to send_request has you picking out the data parameter as a Python-style keyword argument. Ruby doesn't do that: what you're actually doing there is assigning a local variable in-line with the call.
So try something like this:
require 'json' # put this at the top of the file
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = {"ip" => "1.1.1.1"}
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', uri.path, JSON.dump(jobj),
{'Content-Type' => 'application/json'})
And just a friendly reminder, saying something "doesn't work" doesn't usually give enough information to people that might answer your question: try and remember to paste in error messages, stack traces, and things like that :)

how do I parse out this URL-encoded string with a JSON array using Ruby?

When I use a webhook with Mandrill and post to my Iron Worker, I get the following Raw (this is from RequestBin, as well) -- I didn't include the whole payload, just an example:
puts payload =>
mandrill_events=%5B%7B%22event%22%3A%22inbound%22%2C%22msg%22%3A%7B%22dkim%22%3A%7B%22signed%22%3Atrue%2C%22valid%22%3Atrue%7D%2C%22email%22%3A%22kaya%40hellokaya.com%22%2C%22from_email%22%3A%22example.sender%40mandrillapp.com%22%2C%22headers%22%3A%7B%22Content-Type%22%3A%22multipart%5C%2Falternative%3B+boundary%3D%5C%22_av-7r7zDhHxVEAo2yMWasfuFw%5C%22%22%2C%22Date%22%3A%22Fri%2C+10+May+2013+19%3A28%3A20+%2B0000%22%2C%22Dkim-Signature%22%3A%5B%22v%3D1%3B+a%3Drsa-
I tried to extract the value of the parameter mandrill_events using:
puts params = CGI::parse(#payload) =>
{"mandrill_events"=>["[{\"event\":\"inbound\",\"msg\":{\"dkim\":{\"signed\":true,\"valid\":true},\"email\":\"kaya#hellokaya.com\",\"from_email\":\"example.sender#mandrillapp.com\",\"headers\":{\"Content-Type\":\"multipart\\/alternative; boundary=\\\"_av-7r7zDhHxVEAo2yMWasfuFw\\\"\",\"Date\":\"Fri, 10 May 2013 19:28:20 +0000\",\"Dkim-Signature\":[\"v=1; a=rsa-sha1; c=relaxed\\/relaxed; s=mandrill; d=mail115.us4.mandrillapp.com; h=From:Sender:Subject:List-Unsubscribe:To:Message-Id:Date:MIME-Version:Content-Type; i=example.sender#mail115.us4.mandrillapp.com;
Then I am stuck. I want to extract the email value in the JSON.array.
I thought to try the following;
puts json_params = JSON.parse(params)
But I am now feeling there must be a better way....
How can I extract the elements from the JSON array in this URL-encoded string?

Ruby Sinatra and JSON objects from toodledo API 2.0

I have a small problem with receiving JSON objects. I'm using Ruby 1.9.3 and my goal is to receive my tasks from an API via RestClient and print them more or less pretty onto the page.
I created a route /test:
get '/test' do
json_ip_url = "http://api.toodledo.com/2/tasks/get.php?key=198196ae24792467eec09ac2191*****;modafter=1234567890;fields=folder,star,priority"
ip_details = RestClient.get(json_ip_url)
test = JSON.pretty_generate(ip_details) # => throws exception
end
The JSON#pretty_generate line throws an error, "only generation of JSON objects or arrays allowed". What am I doing wrong here?
Update:
I'am now able to output via pretty_generate, but what do I have to do, to get the elements of it. Here is the JSON Data, it seems to me its an Array with Objects inside of it?
[{"num":"18","total":"18"},{"id":"11980343","title":"Add some items to your todo list","modified":1391670256,"completed":0,"folder":"0","star":"0"},{"id":"11980345","title":"Visit the Settings section and configure your account","modified":1391670256,"completed":0,"folder":"0","star":"0"},{"id":"11980347","title":"Watch our tutorial videos in the Help section","modified":1391670256,"completed":0,"folder":"0","star":"1"},{"id":"12607789","title":"test","modified":1392285802,"completed":0,"folder":"0","star":"0"},{"id":"12636039","title":"My Task","modified":1392308705,"completed":0,"folder":"0","star":"0"},{"id":"12636041","title":"Another","modified":1392308705,"completed":0,"folder":"0","star":"1"},{"id":"12636143","title":"My Task","modified":1392308789,"completed":0,"folder":"0","star":"0"},{"id":"12636145","title":"Another","modified":1392308789,"completed":0,"folder":"0","star":"1"},{"id":"12636449","title":"My Task","modified":1392308950,"completed":0,"folder":"0","star":"0"},{"id":"12636451","title":"Another","modified":1392308950,"completed":0,"folder":"0","star":"1"},{"id":"12636621","title":"My Task","modified":1392309061,"completed":0,"folder":"0","star":"0"},{"id":"12636623","title":"Another","modified":1392309061,"completed":0,"folder":"0","star":"1"},{"id":"12636665","title":"My Task","modified":1392309085,"completed":0,"folder":"0","star":"0"},{"id":"12636667","title":"Another","modified":1392309085,"completed":0,"folder":"0","star":"1"},{"id":"12636733","title":"My Task","modified":1392309137,"completed":0,"folder":"0","star":"0"},{"id":"12636735","title":"Another","modified":1392309137,"completed":0,"folder":"0","star":"1"},{"id":"12637135","title":"My Task","modified":1392309501,"completed":0,"folder":"0","star":"0"},{"id":"12637137","title":"Another","modified":1392309501,"completed":0,"folder":"0","star":"1"}]
The Code I used for pretty_generate:
get '/save' do
jdata = params[:data]
response = RestClient.get 'http://api.toodledo.com/2/tasks/get.php?key=da21e24e2a00ba9d45008974aed00***;modafter=1234567890;fields=folder,star,priority', {:accept => :json}
test = JSON.parse(response)
test.to_json
output = JSON.pretty_generate(test)
puts output
RestClient#get returns the raw response as a string (and not a hash or array) when called without a block, so ip_details isn't a structure that JSON#pretty_generate knows how to handle. You need to use JSON#parse to turn the response into a hash or array first.

How to parse request data with ruby

I'm using sendgrid's event api which sends a codeblock like this to a postback url of my choice:
Array
(
[email] => fgdfg#gmail.com
[timestamp] => 1323698899
[smtp-id] => <4ee60acf8e3d1_55dd862cf147044#mbjoppa.mail>
[response] => 250 2.0.0 OK 1323698899 o30s15072o427yhl.103
[event] => delivered
)
They don't have XML or JSON and I need to extract the email and event parts of this block.
Any idea how I do this with rails? Basically this block is sent to a postback URL of my choice but I'm not sure how to use it from there.
I never used sendgrid but after a quick look it seems like they are just sending you a standard post request with parameters so you best bet is to define a simple action like the following and see what you get:
def sendgrid_event
Rails.logger.info(params)
# chances are that this will contains what you are looking for:
# params['email']
# params['event']
end

Resources