Sorry for the late reply. We create a custom plug that handles the parsing of the incoming soap requests and some utilities for sending a reply.
Using this plug builds a model from the wsdl at compile time, parses and validates incoming requests, and; similar to a controller; executes a function with the same name.
# Example module
defmodule YourCoolSoapService.ServicePlug do
# Usage
# wsdl_file: path to your wsdl file. xsd files should be relative to the files too
# prefix: this prefix will be
use SoapService.Soap.Plug, wsdl_file: "your/wsdl/file/here.wsdl", prefix: "abc"
# Imagine you have an operation called playMusic with
# the request/response types playMusicRequest and playMusicReply
def play_music(conn, abc_playMusicRequest() = params) do
# play the music
case play_the_music(params) do
{:ok, result} -> soap_resp(conn, abc_playMusicResponse())
{:error, error} -> client_fault(conn, "That was your fault")
end
end
end
# Generic soap error
defmodule SoapService.SoapFaultError do
defexception [:detail, :message]
end
defmodule SoapService.Soap.Plug do
defmacro __using__(config) do
quote location: :keep do
# extracts the records from the wsdl
use Erlsom.Records, unquote(config)
# Expose utilities
import SoapService.Soap.Plug
alias SoapService.SoapFaultError
# extracts records for the wsdl terms
for record <- [:wsdl, :"soap:detail", :"soap:Fault", :"soap:Body", :"soap:Header", :"soap:Envelope"] do
record_name = Atom.to_string(record) |> String.replace(":", "") |> String.to_atom()
Record.defrecord(record_name, record, Record.extract(record, from_lib: "detergent/include/detergent.hrl"))
end
def init(opts), do: opts
def call(conn, _opts) do
# read the body, parse it and validate it against the schema
{:ok, body, conn} = Plug.Conn.read_body(conn)
# erlsom_model defined at compiletime by using Erlsom.Records
model = erlsom_model()
case :erlsom.scan(body, model) do
# Valid wsdl operation
{:ok, result, _rest} ->
soap_body = soapEnvelope(result, :Body)
# Extract the requested operation
case soapBody(soap_body, :choice) do
[soap_op] ->
action = record_to_action(soap_op)
# Apply the matching function for the requested operation
try do
apply(__MODULE__, action, [conn, soap_op])
rescue
e in SoapFaultError ->
client_fault(conn, e.message, e.detail)
e ->
require Logger
Logger.error(Exception.format(:error, e, __STACKTRACE__))
client_fault(conn, e.message)
end
_ ->
client_fault(conn, "No such operation")
end
{:error, error} ->
client_fault(conn, error)
end
end
# Helper function that wraps the response with the proper soap terms
def soap_resp(conn, response) do
envelope = soapEnvelope(Header: soapHeader(), Body: soapBody(choice: [response]))
SoapService.Soap.Plug.soap_resp(conn, envelope, erlsom_model())
end
# Helper function to build non-raising error
def client_fault(conn, message, detail \\ nil) do
detail =
if detail do
soap_detail("#any": [detail])
else
:undefined
end
fault =
soapFault(
faultcode: {:qname, 'http://schemas.xmlsoap.org/soap/envelope/', 'Client', 'soap', ''},
faultstring: to_charlist(message),
detail: detail
)
soap_resp(conn, fault)
end
end
end
# Transform the result into a proper soap response and send it
def soap_resp(conn, envelope, model) do
{:ok, xml} = :erlsom.write(envelope, model)
conn
|> Plug.Conn.put_resp_content_type("text/xml")
|> Plug.Conn.send_resp(200, :erlsom_ucs.to_utf8(xml))
|> Plug.Conn.halt()
end
def record_to_action(tuple) do
tuple
|> elem(0)
|> Atom.to_string()
|> remove_prefix()
|> Macro.underscore()
|> String.to_existing_atom()
end
defp remove_prefix(string) do
if new_string = do_remove_prefix(string) do
new_string
else
string
end
end
defp do_remove_prefix(""), do: nil
defp do_remove_prefix(":" <> rest), do: rest
defp do_remove_prefix(<<_, rest::binary>>), do: do_remove_prefix(rest)
end