SOAP in Elixir with soap
Lately I’ve had to integrate with a number of SOAP services and my language of choice has for some time now been Elixir. For sometime, the only libraries I knew of were detergent/detergentex for ErlangElixir respectively. They work relatively well but I ran into an issue working with WSDL files with multiple imported XSD files. At which point I cam across soap, a newer Erlang SOAP library. Here’s how I used in within my Elixir codebase.
Lets use this currency converter webservice as an example. First, add the library as a dependency and run mix deps.get.
{:soap, github: “bet365/soap”}
Also add soap and inets to you list of applications to ensure soap is built into our release and that we can use Erlang’s http client(httpc).
Now we need to generate a client stub using the wsdl url referenced above. This doesn’t currently work in the elixir shell so we have to drop into the erlang shell. Run this to load it and the project’s dependencies.
erl -pa deps/*/ebin
Next,manually start inets.
application:start(inets).
And then run the soap:wsdl2erlang function with the wsdl url to generate the client code. Choose the following options for now
soap:wsdl2erlang("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL").
1: client
2: yes, client only
2: inets
1: CurrencyConvertorSoap
2: P0
Move the .hrl file into an include directory and the .erl files into an src directory within the Elixir project to have mix build them by default.
Now the fun begins, lets implement an Elixir interface into the client. Create an Elixir module at lib/currency_converter.ex, lets call it SoapTutoarial.CurrencyConverter.
Looking at the src/CurrencyConvertor.asmx?WSDL_client.erl file, we can see that the ConversionRate function expects a Soap_body and a list of Soap_headers and Options. The Soap_body is of type P0:ConversionRate which we can find within the .hrl file and is defined as a record with two values, a FromCurrency and a ToCurrency. Lets define this within our Elixir module, using the Record macros.
defmodule SoapTutorial.CurrencyConverter do
require Record
import Record, only: [defrecord: 3, extract: 2]defrecord :conversion_rate_req, :"P0:ConversionRate", extract(:"P0:ConversionRate", from: "include/CurrencyConvertor.asmx?WSDL.hrl")
end
We use the defrecord macro to define a record named conversion_rate_req with a custom tag :”P0:ConversionRate” that matches the actual record’s name. We can now construct instances of the record like so:
iex(1)> require SoapTutorial.CurrencyConverter
nil
iex(2)> body = SoapTutorial.CurrencyConverter.conversion_rate_req(FromCurrency: "KES", ToCurrency: "GBP")
{:"P0:ConversionRate", "KES", "GBP"}
If we dont use the tag, our record will be named :”P0:ConversionRate”, which I havn’t found a way to construct outside the shell
With this setup, we can make the SOAP call with the record
:"CurrencyConvertor.asmx?WSDL_client"."ConversionRate"(body, [], [])
This will give us back a response with a record of type :”P0:ConversionRateResponse”. To make it easier tow work with, you should define the record as well and use it to process the results.