# Lather API Documentation
This document provides detailed API reference for the Lather SOAP library.
## Modules Overview
**Core Modules:**
- `Lather` - Main module and entry point for the library
- `Lather.Application` - OTP application supervision and startup
- `Lather.Client` - Low-level SOAP client for custom implementations
- `Lather.DynamicClient` - High-level client for any SOAP service
- `Lather.Error` - Comprehensive error handling and SOAP fault parsing
**Server Modules:**
- `Lather.Server` - SOAP server implementation and configuration
- `Lather.Server.DSL` - Domain-specific language for defining SOAP services
- `Lather.Server.Plug` - Plug integration for hosting SOAP endpoints
- `Lather.Server.EnhancedPlug` - Advanced Plug with additional features
- `Lather.Server.WSDLGenerator` - Generates WSDL documents from service definitions
- `Lather.Server.EnhancedWSDLGenerator` - Extended WSDL generation with complex types
- `Lather.Server.FormGenerator` - Generates HTML forms for testing SOAP operations
- `Lather.Server.Handler` - Request handling and operation dispatching
- `Lather.Server.RequestParser` - Parses incoming SOAP requests
- `Lather.Server.ResponseBuilder` - Builds SOAP responses from handler results
**SOAP Processing:**
- `Lather.Soap.Envelope` - SOAP envelope construction and manipulation
- `Lather.Soap.Body` - SOAP body element handling
- `Lather.Soap.Header` - SOAP header element handling
- `Lather.Operation.Builder` - Dynamic SOAP request building
- `Lather.Wsdl.Analyzer` - WSDL parsing and analysis utilities
**HTTP & Transport:**
- `Lather.Http.Transport` - HTTP transport layer for SOAP requests
- `Lather.Http.Pool` - Connection pool management via Finch
**Authentication:**
- `Lather.Auth.Basic` - Basic HTTP authentication
- `Lather.Auth.WSSecurity` - WS-Security authentication with username tokens
**XML Processing:**
- `Lather.Xml.Builder` - XML document construction from Elixir data
- `Lather.Xml.Parser` - XML document parsing to Elixir structures
**Types:**
- `Lather.Types.Mapper` - Type conversion between XML and Elixir
- `Lather.Types.Generator` - Dynamic struct generation from WSDL types
**MTOM/Attachments:**
- `Lather.Mtom.Attachment` - Binary attachment handling for SOAP messages
- `Lather.Mtom.Builder` - Builds MTOM-encoded multipart messages
- `Lather.Mtom.Mime` - MIME type handling and multipart parsing
## Lather.Application
OTP application supervisor for Lather. Starts and manages the Finch HTTP client pool used for SOAP requests.
### Supervision Tree
The application starts a supervision tree with the following children:
- `Finch` - HTTP client pool (named `Lather.Finch`)
The supervisor uses a `:one_for_one` strategy, meaning if the Finch pool crashes, only it will be restarted.
### Automatic Startup
Lather is configured as an OTP application, so the supervision tree starts automatically when your application starts. No manual intervention is required.
### Manual Startup
If you need to start Lather manually (e.g., in a script or test):
```elixir
{:ok, _pid} = Application.ensure_all_started(:lather)
```
## Lather.DynamicClient
The main interface for working with SOAP services dynamically.
### Functions
#### new/2
Creates a new dynamic client from a WSDL URL.
```elixir
@spec new(String.t(), keyword()) :: {:ok, t()} | {:error, term()}
```
**Parameters:**
- `wsdl_url` - URL to the WSDL document
- `options` - Client configuration options
**Options:**
- `:basic_auth` - Basic authentication `{username, password}`
- `:ssl_options` - SSL/TLS configuration
- `:timeout` - Request timeout in milliseconds
- `:headers` - Additional HTTP headers
- `:namespace_aware` - Enable namespace-aware parsing
**Example:**
```elixir
{:ok, client} = Lather.DynamicClient.new(
"https://example.com/service?wsdl",
basic_auth: {"user", "pass"},
timeout: 30_000
)
```
#### call/4
Calls a SOAP operation with the given parameters.
```elixir
@spec call(t(), String.t(), map(), keyword()) :: {:ok, map()} | {:error, term()}
```
**Parameters:**
- `client` - The dynamic client instance
- `operation_name` - Name of the operation to call
- `parameters` - Map of operation parameters
- `options` - Call-specific options
**Options:**
- `:soap_action` - Override SOAPAction header
- `:validate` - Enable/disable parameter validation (default: true)
- `:timeout` - Override timeout for this call
- `:headers` - Additional headers for this request
**Example:**
```elixir
{:ok, response} = Lather.DynamicClient.call(
client,
"GetUser",
%{"userId" => "12345"},
timeout: 60_000
)
```
#### list_operations/1
Lists all available operations from the WSDL.
```elixir
@spec list_operations(t()) :: [String.t()]
```
**Example:**
```elixir
operations = Lather.DynamicClient.list_operations(client)
# => ["GetUser", "CreateUser", "UpdateUser", "DeleteUser"]
```
#### get_operation_info/2
Gets detailed information about a specific operation.
```elixir
@spec get_operation_info(t(), String.t()) :: {:ok, map()} | {:error, term()}
```
**Example:**
```elixir
{:ok, info} = Lather.DynamicClient.get_operation_info(client, "GetUser")
# => %{
# name: "GetUser",
# input_parts: [%{name: "userId", type: "string", required: true}],
# output_parts: [%{name: "user", type: "User"}],
# soap_action: "http://example.com/GetUser"
# }
```
#### validate_parameters/3
Validates parameters against operation requirements.
```elixir
@spec validate_parameters(t(), String.t(), map()) :: :ok | {:error, term()}
```
**Example:**
```elixir
case Lather.DynamicClient.validate_parameters(client, "GetUser", %{"userId" => "123"}) do
:ok ->
# Parameters are valid
{:error, error} ->
# Handle validation error
end
```
## Lather.Client
Low-level SOAP client for custom implementations.
### Functions
#### new/2
Creates a new SOAP client.
```elixir
@spec new(String.t(), keyword()) :: t()
```
#### post/3
Sends a SOAP request to the endpoint.
```elixir
@spec post(t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()}
```
## Lather.Wsdl.Analyzer
WSDL parsing and analysis utilities.
### Functions
#### analyze/2
Analyzes a WSDL document and extracts service information.
```elixir
@spec analyze(String.t(), keyword()) :: {:ok, map()} | {:error, term()}
```
**Returns a map with:**
- `:operations` - List of available operations
- `:types` - Complex type definitions
- `:bindings` - SOAP binding information
- `:services` - Service endpoints
- `:namespaces` - Namespace declarations
#### extract_operations/1
Extracts operation definitions from parsed WSDL.
```elixir
@spec extract_operations(map()) :: [map()]
```
#### parse_complex_type/1
Parses complex type definitions.
```elixir
@spec parse_complex_type(map()) :: map()
```
## Lather.Operation.Builder
Dynamic SOAP request building.
### Functions
#### build_request/3
Builds a SOAP request for any operation.
```elixir
@spec build_request(map(), map(), keyword()) :: {:ok, String.t()} | {:error, term()}
```
#### validate_parameters/2
Validates operation parameters.
```elixir
@spec validate_parameters(map(), map()) :: :ok | {:error, term()}
```
#### parse_response/3
Parses SOAP response into Elixir data structures.
```elixir
@spec parse_response(map(), map(), keyword()) :: {:ok, map()} | {:error, term()}
```
## Lather.Soap.Envelope
SOAP envelope construction utilities.
### Functions
#### build/3
Builds a complete SOAP envelope.
```elixir
@spec build(map(), String.t(), keyword()) :: String.t()
```
**Parameters:**
- `body` - SOAP body content
- `namespace` - Target namespace
- `options` - Envelope options
**Options:**
- `:soap_version` - SOAP version (`:soap11` or `:soap12`)
- `:headers` - SOAP headers to include
- `:prefix` - Namespace prefix
#### wrap_body/2
Wraps content in a SOAP body.
```elixir
@spec wrap_body(map(), keyword()) :: map()
```
## Lather.Soap.Body
SOAP body utilities for creating and managing SOAP body content, including parameter serialization and response parsing.
### Functions
#### create/3
Creates a SOAP body element for the given operation and parameters.
```elixir
@spec create(atom() | String.t(), map(), keyword()) :: map()
```
**Parameters:**
- `operation` - Operation name (atom or string)
- `params` - Operation parameters (map)
- `options` - Body options
**Options:**
- `:namespace` - Target namespace for the operation
- `:namespace_prefix` - Prefix for the target namespace
**Example:**
```elixir
Lather.Soap.Body.create(:get_user, %{id: 123}, namespace: "http://example.com")
# => %{
# "get_user" => %{
# "@xmlns" => "http://example.com",
# "id" => 123
# }
# }
```
#### serialize_params/1
Serializes Elixir data structures to XML-compatible format.
```elixir
@spec serialize_params(any()) :: any()
```
Handles various Elixir types including maps, lists, atoms, booleans, DateTime, Date, Time, and strings, converting them to XML-safe representations.
#### validate_params/2
Validates parameters against expected types and constraints.
```elixir
@spec validate_params(map(), map()) :: :ok | {:error, [String.t()]}
```
**Parameters:**
- `params` - Parameters to validate
- `schema` - Validation schema (map)
**Schema Format:**
```elixir
%{
"id" => [:required, :integer],
"name" => [:required, :string, {:max_length, 50}],
"email" => [:optional, :string, :email]
}
```
## Lather.Soap.Header
SOAP header utilities for creating and managing SOAP headers, including authentication headers and custom header elements.
### Functions
#### username_token/3
Creates a WS-Security UsernameToken header.
```elixir
@spec username_token(String.t(), String.t(), keyword()) :: map()
```
**Parameters:**
- `username` - Username for authentication
- `password` - Password for authentication
- `options` - Header options
**Options:**
- `:password_type` - `:text` or `:digest` (default: `:text`)
- `:include_nonce` - Whether to include a nonce (default: `true` for digest)
- `:include_created` - Whether to include timestamp (default: `true`)
**Example:**
```elixir
Lather.Soap.Header.username_token("user", "pass")
# => %{
# "wsse:Security" => %{
# "@xmlns:wsse" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
# "wsse:UsernameToken" => %{...}
# }
# }
```
#### timestamp/1
Creates a WS-Security timestamp header.
```elixir
@spec timestamp(keyword()) :: map()
```
**Options:**
- `:ttl` - Time to live in seconds (default: 300)
#### username_token_with_timestamp/3
Creates a combined WS-Security header with both UsernameToken and Timestamp.
```elixir
@spec username_token_with_timestamp(String.t(), String.t(), keyword()) :: map()
```
**Parameters:**
- `username` - Username for authentication
- `password` - Password for authentication
- `options` - Combined options for both UsernameToken and Timestamp
#### session/2
Creates a session header for maintaining session state.
```elixir
@spec session(String.t(), keyword()) :: map()
```
**Parameters:**
- `session_id` - The session ID
- `options` - Additional options
**Options:**
- `:header_name` - Custom header name (default: "SessionId")
- `:namespace` - Custom namespace
**Example:**
```elixir
Lather.Soap.Header.session("session_12345")
# => %{"SessionId" => "session_12345"}
```
#### custom/3
Creates a custom header element.
```elixir
@spec custom(String.t(), map() | String.t(), map()) :: map()
```
**Parameters:**
- `name` - Header element name
- `content` - Header content (map or string)
- `attributes` - Element attributes
**Example:**
```elixir
Lather.Soap.Header.custom("MyHeader", %{"value" => "test"}, %{"xmlns" => "http://example.com"})
# => %{"MyHeader" => %{"@xmlns" => "http://example.com", "value" => "test"}}
```
#### merge_headers/1
Merges multiple header elements into a single header map.
```elixir
@spec merge_headers([map()]) :: map()
```
**Example:**
```elixir
header1 = Lather.Soap.Header.session("session_123")
header2 = Lather.Soap.Header.custom("MyApp", "v1.0")
Lather.Soap.Header.merge_headers([header1, header2])
# => %{"SessionId" => "session_123", "MyApp" => "v1.0"}
```
## Lather.Http.Transport
HTTP transport layer for SOAP requests.
### Functions
#### post/3
Sends an HTTP POST request.
```elixir
@spec post(String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()}
```
**Parameters:**
- `url` - Request URL
- `body` - Request body
- `options` - HTTP options
**Options:**
- `:timeout` - Request timeout
- `:headers` - HTTP headers
- `:soap_action` - SOAPAction header
- `:ssl_options` - SSL configuration
- `:basic_auth` - Basic authentication
#### validate_url/1
Validates a URL for SOAP requests.
```elixir
@spec validate_url(String.t()) :: :ok | {:error, :invalid_url}
```
#### ssl_options/1
Creates SSL options for secure connections.
```elixir
@spec ssl_options(keyword()) :: keyword()
```
## Lather.Http.Pool
Connection pool configuration for HTTP transport. Provides configuration and utilities for managing Finch connection pools optimized for SOAP requests.
### Functions
#### default_config/0
Returns the default pool configuration for SOAP clients.
```elixir
@spec default_config() :: keyword()
```
Optimized for typical SOAP usage patterns with reasonable defaults for connection pooling, timeouts, and SSL settings.
**Default Configuration:**
```elixir
[
pool_timeout: 5_000,
pool_max_idle_time: 30_000,
http2_max_concurrent_streams: 1000,
transport_opts: [
verify: :verify_peer,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]
```
#### config_for_endpoint/2
Creates a pool configuration for a specific endpoint.
```elixir
@spec config_for_endpoint(String.t(), keyword()) :: keyword()
```
**Parameters:**
- `endpoint` - The SOAP endpoint URL
- `overrides` - Configuration overrides (optional)
Allows customization of pool settings per endpoint, useful for services with different performance characteristics.
**Example:**
```elixir
config = Lather.Http.Pool.config_for_endpoint(
"https://api.example.com/soap",
pool_timeout: 10_000
)
```
#### validate_config/1
Validates pool configuration options.
```elixir
@spec validate_config(keyword()) :: :ok | {:error, String.t()}
```
## Lather.Xml.Builder
XML document construction.
### Functions
#### build/1
Builds an XML document from Elixir data structures.
```elixir
@spec build(map()) :: {:ok, String.t()} | {:error, any()}
```
**Example:**
```elixir
xml = Lather.Xml.Builder.build(%{
"GetUser" => %{
"@xmlns" => "http://example.com",
"userId" => "12345"
}
})
# => "<GetUser xmlns=\"http://example.com\"><userId>12345</userId></GetUser>"
```
#### escape/1
Escapes XML content.
```elixir
@spec escape(String.t()) :: String.t()
```
## Lather.Xml.Parser
XML document parsing.
### Functions
#### parse/2
Parses XML content into Elixir data structures.
```elixir
@spec parse(String.t(), keyword()) :: {:ok, map()} | {:error, term()}
```
**Options:**
- `:namespace_aware` - Enable namespace handling
- `:custom_parsers` - Custom type parsers
#### extract_namespaces/1
Extracts namespace declarations from XML.
```elixir
@spec extract_namespaces(String.t()) :: map()
```
## Lather.Types.Mapper
Type conversion between XML and Elixir.
### Functions
#### xml_to_elixir/3
Converts XML data to Elixir types.
```elixir
@spec xml_to_elixir(map(), map(), keyword()) :: {:ok, term()} | {:error, term()}
```
#### elixir_to_xml/3
Converts Elixir data to XML representation.
```elixir
@spec elixir_to_xml(term(), map(), keyword()) :: {:ok, map()} | {:error, term()}
```
#### validate_type/3
Validates data against type definitions.
```elixir
@spec validate_type(term(), map(), keyword()) :: :ok | {:error, term()}
```
## Lather.Types.Generator
Dynamic struct generation from WSDL types.
### Functions
#### generate_structs/2
Generates Elixir struct modules from WSDL types.
```elixir
@spec generate_structs(map(), String.t()) :: {:ok, [module()]} | {:error, term()}
```
#### create_struct_instance/3
Creates a struct instance with type validation.
```elixir
@spec create_struct_instance(module(), map(), keyword()) :: {:ok, struct()} | {:error, term()}
```
## Lather.Auth.Basic
Basic HTTP authentication.
### Functions
#### header/2
Creates a Basic authentication header.
```elixir
@spec header(String.t(), String.t()) :: {String.t(), String.t()}
```
## Lather.Auth.WSSecurity
WS-Security authentication.
### Functions
#### username_token/3
Creates a WS-Security username token.
```elixir
@spec username_token(String.t(), String.t(), keyword()) :: map()
```
**Parameters:**
- `username` - The username for authentication
- `password` - The password for authentication
- `options` - Keyword options for token configuration
**Options:**
- `:password_type` - Password type (`:text` or `:digest`, default: `:text`)
- `:nonce` - Custom nonce value (auto-generated if not provided)
- `:created` - Custom created timestamp (auto-generated if not provided)
**Example:**
```elixir
# Username token with password digest
username_token = Lather.Auth.WSSecurity.username_token("user", "pass", password_type: :digest)
```
## MTOM Modules
MTOM (Message Transmission Optimization Mechanism) modules enable efficient binary data transmission in SOAP messages using XOP (XML-binary Optimized Packaging).
### Lather.Mtom.Attachment
Defines the structure for binary attachments in MTOM messages and provides utilities for creating, validating, and managing attachments.
#### Types
```elixir
@type t :: %Lather.Mtom.Attachment{
id: String.t(),
content_type: String.t(),
content_transfer_encoding: String.t(),
data: binary(),
content_id: String.t(),
size: non_neg_integer()
}
```
#### Functions
##### new/3
Creates a new attachment from binary data and content type.
```elixir
@spec new(binary(), String.t(), keyword()) :: t()
```
**Parameters:**
- `data` - Binary data for the attachment
- `content_type` - MIME content type (e.g., "application/pdf")
- `options` - Additional options
**Options:**
- `:content_id` - Custom Content-ID (auto-generated if not provided)
- `:content_transfer_encoding` - Transfer encoding (default: "binary")
- `:validate` - Whether to validate the attachment (default: true)
**Example:**
```elixir
attachment = Lather.Mtom.Attachment.new(pdf_data, "application/pdf")
attachment = Lather.Mtom.Attachment.new(image_data, "image/jpeg",
content_id: "custom-id-123"
)
```
##### from_file/2
Creates an attachment from a file path.
```elixir
@spec from_file(String.t(), keyword()) :: {:ok, t()} | {:error, term()}
```
**Example:**
```elixir
{:ok, attachment} = Lather.Mtom.Attachment.from_file("document.pdf")
{:ok, attachment} = Lather.Mtom.Attachment.from_file("image.jpg", content_type: "image/jpeg")
```
##### validate/1
Validates an attachment structure and content.
```elixir
@spec validate(t()) :: :ok | {:error, atom()}
```
**Example:**
```elixir
:ok = Lather.Mtom.Attachment.validate(attachment)
{:error, :attachment_too_large} = Lather.Mtom.Attachment.validate(huge_attachment)
```
##### content_id_header/1
Generates a Content-ID header value for the attachment.
```elixir
@spec content_id_header(t()) :: String.t()
```
##### cid_reference/1
Generates a CID reference for XOP includes.
```elixir
@spec cid_reference(t()) :: String.t()
```
**Example:**
```elixir
cid_ref = Lather.Mtom.Attachment.cid_reference(attachment)
# "cid:attachment123@lather.soap"
```
##### xop_include/1
Creates an XOP Include element for the attachment.
```elixir
@spec xop_include(t()) :: map()
```
##### is_attachment?/1
Checks if a parameter value represents an attachment.
```elixir
@spec is_attachment?(any()) :: boolean()
```
**Example:**
```elixir
Lather.Mtom.Attachment.is_attachment?({:attachment, data, "application/pdf"}) # true
Lather.Mtom.Attachment.is_attachment?("regular string") # false
```
##### from_tuple/1
Converts an attachment tuple to an Attachment struct.
```elixir
@spec from_tuple(tuple()) :: {:ok, t()} | {:error, term()}
```
**Example:**
```elixir
{:ok, attachment} = Lather.Mtom.Attachment.from_tuple({:attachment, data, "application/pdf"})
{:ok, attachment} = Lather.Mtom.Attachment.from_tuple({:attachment, data, "image/jpeg", [content_id: "img1"]})
```
### Lather.Mtom.Builder
Constructs MTOM multipart SOAP messages by extracting binary attachments from parameters, replacing them with XOP Include references, and packaging everything into a multipart/related MIME message.
#### Functions
##### build_mtom_message/3
Builds a complete MTOM message with SOAP envelope and binary attachments.
```elixir
@spec build_mtom_message(atom() | String.t(), map(), keyword()) ::
{:ok, {String.t(), binary()}} | {:error, term()}
```
**Parameters:**
- `operation` - SOAP operation name (atom or string)
- `parameters` - Parameters map potentially containing attachment tuples
- `options` - SOAP envelope building options
**Options:**
- `:namespace` - Target namespace for the operation
- `:headers` - SOAP headers to include
- `:version` - SOAP version (`:v1_1` or `:v1_2`)
- `:boundary` - Custom MIME boundary (auto-generated if not provided)
- `:enable_mtom` - Force MTOM even without attachments (default: auto-detect)
**Example:**
```elixir
params = %{
"document" => {:attachment, pdf_data, "application/pdf"},
"metadata" => %{"title" => "Report"}
}
{:ok, {content_type, body}} = Lather.Mtom.Builder.build_mtom_message(
:UploadDocument,
params,
namespace: "http://example.com/upload"
)
```
##### process_parameters/1
Processes parameters to extract attachments and replace with XOP includes.
```elixir
@spec process_parameters(map()) :: {:ok, {map(), [Attachment.t()]}} | {:error, term()}
```
**Example:**
```elixir
params = %{"file" => {:attachment, data, "application/pdf"}}
{:ok, {new_params, [attachment]}} = Lather.Mtom.Builder.process_parameters(params)
# new_params contains XOP Include reference instead of binary data
```
##### has_attachments?/1
Checks if parameters contain any attachment tuples.
```elixir
@spec has_attachments?(map()) :: boolean()
```
**Example:**
```elixir
Lather.Mtom.Builder.has_attachments?(%{"file" => {:attachment, data, "pdf"}}) # true
Lather.Mtom.Builder.has_attachments?(%{"name" => "John"}) # false
```
##### validate_attachments/1
Validates that all attachment tuples in parameters are properly formatted.
```elixir
@spec validate_attachments(map()) :: :ok | {:error, term()}
```
##### estimate_message_size/2
Estimates the total size of a message including all attachments.
```elixir
@spec estimate_message_size(map(), non_neg_integer()) :: non_neg_integer()
```
### Lather.Mtom.Mime
Provides functions for building and parsing multipart/related MIME messages used in MTOM.
#### Functions
##### generate_boundary/0
Generates a unique boundary string for multipart messages.
```elixir
@spec generate_boundary() :: String.t()
```
**Example:**
```elixir
boundary = Lather.Mtom.Mime.generate_boundary()
# "uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```
##### build_multipart_message/3
Builds a complete multipart/related MIME message with SOAP envelope and attachments.
```elixir
@spec build_multipart_message(binary(), [Attachment.t()], keyword()) ::
{String.t(), binary()}
```
**Parameters:**
- `soap_envelope` - The SOAP envelope XML as binary
- `attachments` - List of Attachment structs
- `options` - Additional options
**Options:**
- `:boundary` - Custom boundary (auto-generated if not provided)
- `:soap_content_type` - SOAP part content type (default: "application/xop+xml")
- `:soap_charset` - SOAP part charset (default: "UTF-8")
**Example:**
```elixir
{content_type, body} = Lather.Mtom.Mime.build_multipart_message(soap_xml, attachments)
```
##### parse_multipart_message/3
Parses a multipart/related MIME message.
```elixir
@spec parse_multipart_message(String.t(), binary(), keyword()) ::
{:ok, {binary(), [map()]}} | {:error, term()}
```
**Example:**
```elixir
{:ok, {soap_xml, attachments}} = Lather.Mtom.Mime.parse_multipart_message(content_type, body)
```
##### extract_boundary/1
Extracts the boundary parameter from a Content-Type header.
```elixir
@spec extract_boundary(String.t()) :: {:ok, String.t()} | {:error, atom()}
```
**Example:**
```elixir
{:ok, boundary} = Lather.Mtom.Mime.extract_boundary("multipart/related; boundary=\"uuid:123\"")
```
##### parse_headers/1
Parses MIME headers from a header section.
```elixir
@spec parse_headers(binary()) :: map()
```
**Example:**
```elixir
headers = Lather.Mtom.Mime.parse_headers("Content-Type: application/pdf\r\nContent-ID: <att1>")
# %{"content-type" => "application/pdf", "content-id" => "<att1>"}
```
##### build_content_type_header/3
Builds a Content-Type header for multipart/related messages.
```elixir
@spec build_content_type_header(String.t(), String.t(), String.t()) :: String.t()
```
##### validate_content_type/1
Validates a multipart/related Content-Type header.
```elixir
@spec validate_content_type(String.t()) :: :ok | {:error, atom()}
```
## Server Modules
Lather provides a complete SOAP server implementation for building web services in Elixir.
### Lather.Server
The main module for creating SOAP service modules. Use `use Lather.Server` to define a SOAP service.
#### Macros
##### __using__/1
Sets up a module as a SOAP service with automatic WSDL generation.
```elixir
defmodule MyApp.UserService do
use Lather.Server, namespace: "http://example.com/users", service_name: "UserService"
# Define operations using @soap_operation attribute or DSL macros
end
```
#### Functions
##### soap_fault/3
Creates a SOAP fault response.
```elixir
@spec soap_fault(String.t(), String.t(), term() | nil) :: {:soap_fault, map()}
```
**Example:**
```elixir
soap_fault("Client", "User not found", %{user_id: "123"})
```
##### validate_required_params/2
Validates that required operation parameters are present.
```elixir
@spec validate_required_params(map(), map()) :: :ok | {:error, String.t()}
```
##### validate_param_types/2
Validates parameter types according to operation definition.
```elixir
@spec validate_param_types(map(), map()) :: :ok | {:error, String.t()}
```
##### format_response/2
Formats operation response according to SOAP conventions.
```elixir
@spec format_response(term(), map()) :: {:ok, map()} | {:soap_fault, map()}
```
---
### Lather.Server.DSL
Domain Specific Language for defining SOAP operations and types with a declarative syntax.
#### Macros
##### soap_operation/2
Defines a SOAP operation with metadata for WSDL generation.
```elixir
soap_operation "GetUser" do
description "Retrieves a user by ID"
input do
parameter "userId", :string, required: true, description: "User identifier"
parameter "includeDetails", :boolean, required: false, default: false
end
output do
parameter "user", "User", description: "User information"
end
soap_action "http://example.com/GetUser"
end
def get_user(params) do
# Implementation
end
```
##### soap_type/2
Defines a complex type for use in operations.
```elixir
soap_type "User" do
type_description "User information"
element "id", :string, required: true
element "name", :string, required: true
element "email", :string, required: false
element "created_at", :dateTime, required: true
end
```
##### soap_auth/1
Defines authentication requirements for operations.
```elixir
soap_auth do
basic_auth realm: "SOAP Service"
# or
ws_security required: true
# or
custom_auth handler: MyApp.CustomAuth
end
```
##### input/1, output/1
Defines input and output parameter blocks within an operation.
##### parameter/3
Defines a parameter within an input or output block.
```elixir
parameter "userId", :string, required: true, description: "User ID", min_occurs: 1, max_occurs: 1
```
##### element/3
Defines an element within a complex type.
```elixir
element "name", :string, required: true, description: "User name"
```
##### description/1, type_description/1
Sets descriptions for operations and types.
##### basic_auth/1, ws_security/1, custom_auth/1
Authentication configuration macros for use within `soap_auth` blocks.
---
### Lather.Server.Plug
Plug implementation for SOAP server endpoints. Requires the `:plug` dependency.
#### Usage
```elixir
# In Phoenix router
scope "/soap" do
pipe_through :api
post "/users", Lather.Server.Plug, service: MyApp.UserService
end
# As standalone Plug
plug Lather.Server.Plug, service: MyApp.UserService
```
#### Options
- `:service` - The SOAP service module (required)
- `:path` - Base path for WSDL generation (default: `"/"`)
- `:auth_handler` - Custom authentication handler module
- `:validate_params` - Enable parameter validation (default: `true`)
- `:generate_wsdl` - Enable WSDL generation endpoint (default: `true`)
#### Functions
##### init/1
Initializes the Plug with options.
```elixir
@spec init(keyword()) :: map()
```
##### call/2
Handles incoming HTTP requests (GET for WSDL, POST for SOAP operations).
```elixir
@spec call(Plug.Conn.t(), map()) :: Plug.Conn.t()
```
---
### Lather.Server.EnhancedPlug
Enhanced Plug implementation with web form interface and multi-protocol support.
#### Features
- Interactive web forms for testing operations
- SOAP 1.1, SOAP 1.2, and JSON protocol support
- Enhanced WSDL generation with multi-protocol bindings
- Service overview with complete operation documentation
#### URL Patterns
- `GET /service` - Service overview with operations list
- `GET /service?wsdl` - Standard WSDL download
- `GET /service?wsdl&enhanced=true` - Multi-protocol WSDL
- `GET /service?op=OperationName` - Interactive operation form
- `POST /service` - SOAP 1.1 endpoint
- `POST /service/v1.2` - SOAP 1.2 endpoint
- `POST /service/api` - JSON/REST endpoint
#### Usage
```elixir
# In Phoenix router
scope "/soap" do
pipe_through :api
match :*, "/users", Lather.Server.EnhancedPlug, service: MyApp.UserService
match :*, "/users/*path", Lather.Server.EnhancedPlug, service: MyApp.UserService
end
```
#### Options
- `:service` - The SOAP service module (required)
- `:base_path` - Base path for service (default: `"/soap"`)
- `:enable_forms` - Enable web form interface (default: `true`)
- `:enable_json` - Enable JSON endpoints (default: `true`)
- `:auth_handler` - Custom authentication handler
- `:validate_params` - Enable parameter validation (default: `true`)
#### Functions
##### init/1
```elixir
@spec init(keyword()) :: map()
```
##### call/2
```elixir
@spec call(Plug.Conn.t(), map()) :: Plug.Conn.t()
```
---
### Lather.Server.Handler
Generic HTTP handler for SOAP server endpoints without requiring Plug. Works with any HTTP server.
#### Usage
```elixir
# In Phoenix controller
defmodule MyAppWeb.SOAPController do
use MyAppWeb, :controller
def handle_soap(conn, _params) do
case Lather.Server.Handler.handle_request(
conn.method,
conn.request_path,
conn.req_headers,
conn.assigns.raw_body,
MyApp.UserService
) do
{:ok, status, headers, body} ->
conn |> put_status(status) |> text(body)
{:error, status, headers, body} ->
conn |> put_status(status) |> text(body)
end
end
end
```
#### Functions
##### handle_request/6
Handles a SOAP HTTP request.
```elixir
@spec handle_request(String.t(), String.t(), [{String.t(), String.t()}], String.t(), module(), keyword()) ::
{:ok, integer(), [{String.t(), String.t()}], String.t()} |
{:error, integer(), [{String.t(), String.t()}], String.t()}
```
**Parameters:**
- `method` - HTTP method (`"GET"` or `"POST"`)
- `path` - Request path
- `headers` - Request headers
- `body` - Request body
- `service` - SOAP service module
- `opts` - Options (`:validate_params`, `:generate_wsdl`, `:base_url`)
---
### Lather.Server.RequestParser
Parses incoming SOAP requests and extracts operation details and parameters.
#### Functions
##### parse/1
Parses a SOAP request XML and extracts the operation name and parameters.
```elixir
@spec parse(String.t()) :: {:ok, %{operation: String.t(), params: map()}} | {:error, {:parse_error, String.t()}}
```
**Example:**
```elixir
{:ok, %{operation: "GetUser", params: %{"userId" => "123"}}} =
Lather.Server.RequestParser.parse(soap_xml)
```
---
### Lather.Server.ResponseBuilder
Builds SOAP response XML from operation results.
#### Functions
##### build_response/2
Builds a SOAP response envelope containing the operation result.
```elixir
@spec build_response(term(), map()) :: String.t()
```
**Example:**
```elixir
xml = Lather.Server.ResponseBuilder.build_response(
%{"user" => %{"id" => "123", "name" => "John"}},
%{name: "GetUser"}
)
```
##### build_fault/1
Builds a SOAP fault response.
```elixir
@spec build_fault(map() | nil) :: String.t()
```
**Example:**
```elixir
xml = Lather.Server.ResponseBuilder.build_fault(%{
fault_code: "Client",
fault_string: "User not found",
detail: %{user_id: "123"}
})
```
---
### Lather.Server.WSDLGenerator
Generates WSDL files from SOAP service definitions.
#### Functions
##### generate/2
Generates a complete WSDL document for a SOAP service.
```elixir
@spec generate(map(), String.t()) :: String.t()
```
**Parameters:**
- `service_info` - Service metadata from `__soap_service__/0`
- `base_url` - Base URL for the service endpoint
**Example:**
```elixir
service_info = MyApp.UserService.__soap_service__()
wsdl = Lather.Server.WSDLGenerator.generate(service_info, "http://example.com/soap")
```
---
### Lather.Server.EnhancedWSDLGenerator
Enhanced WSDL generator with multi-protocol support (SOAP 1.1, SOAP 1.2, HTTP/REST).
#### Functions
##### generate/3
Generates a comprehensive multi-protocol WSDL document.
```elixir
@spec generate(map(), String.t(), keyword()) :: String.t()
```
**Options:**
- `:protocols` - List of protocols to include (default: `[:soap_1_1, :soap_1_2, :http]`)
- `:base_path` - Base path for REST endpoints (default: `"/api"`)
- `:include_json` - Include JSON content type support (default: `true`)
**Example:**
```elixir
service_info = MyApp.UserService.__soap_service__()
wsdl = Lather.Server.EnhancedWSDLGenerator.generate(
service_info,
"http://example.com",
protocols: [:soap_1_1, :soap_1_2]
)
```
---
### Lather.Server.FormGenerator
Generates HTML forms and documentation pages for SOAP operations, similar to .NET Web Services.
#### Functions
##### generate_operation_page/4
Generates a complete HTML page for an operation with testing forms and protocol examples.
```elixir
@spec generate_operation_page(map(), map(), String.t(), keyword()) :: String.t()
```
**Parameters:**
- `service_info` - Service metadata
- `operation` - Operation metadata
- `base_url` - Base URL for the service
- `options` - Additional options
##### generate_service_overview/3
Generates a service overview page with all operations listed.
```elixir
@spec generate_service_overview(map(), String.t(), keyword()) :: String.t()
```
**Example:**
```elixir
service_info = MyApp.UserService.__soap_service__()
html = Lather.Server.FormGenerator.generate_service_overview(
service_info,
"http://example.com/soap"
)
```
---
### Complete Server Example
```elixir
defmodule MyApp.CalculatorService do
use Lather.Server,
namespace: "http://example.com/calculator",
service_name: "Calculator"
# Define a complex type
soap_type "CalculationResult" do
type_description "Result of a calculation"
element "value", :decimal, required: true
element "operation", :string, required: true
element "timestamp", :dateTime, required: true
end
# Define an operation
soap_operation "Add" do
description "Adds two numbers"
input do
parameter "a", :decimal, required: true, description: "First number"
parameter "b", :decimal, required: true, description: "Second number"
end
output do
parameter "result", "CalculationResult"
end
soap_action "http://example.com/calculator/Add"
end
def add(%{"a" => a, "b" => b}) do
result = Decimal.add(Decimal.new(a), Decimal.new(b))
{:ok, %{
"result" => %{
"value" => Decimal.to_string(result),
"operation" => "add",
"timestamp" => DateTime.utc_now() |> DateTime.to_iso8601()
}
}}
end
end
# Mount in Phoenix router
scope "/soap" do
pipe_through :api
match :*, "/calculator", Lather.Server.EnhancedPlug, service: MyApp.CalculatorService
match :*, "/calculator/*path", Lather.Server.EnhancedPlug, service: MyApp.CalculatorService
end
```
## Lather.Error
Comprehensive error handling.
### Types
#### soap_fault
SOAP fault information.
```elixir
@type soap_fault :: %{
fault_code: String.t(),
fault_string: String.t(),
fault_actor: String.t() | nil,
detail: map() | nil
}
```
#### transport_error
Transport layer errors.
```elixir
@type transport_error :: %{
type: :transport_error,
reason: atom() | String.t(),
details: map()
}
```
#### http_error
HTTP-level errors.
```elixir
@type http_error :: %{
type: :http_error,
status: integer(),
body: String.t(),
headers: [{String.t(), String.t()}]
}
```
#### validation_error
Parameter validation errors.
```elixir
@type validation_error :: %{
type: :validation_error,
field: String.t(),
reason: atom(),
details: map()
}
```
### Functions
#### parse_soap_fault/2
Parses SOAP fault from response.
```elixir
@spec parse_soap_fault(String.t(), keyword()) :: {:ok, soap_fault()} | {:error, term()}
```
#### transport_error/2
Creates a transport error.
```elixir
@spec transport_error(term(), map()) :: transport_error()
```
#### http_error/3
Creates an HTTP error.
```elixir
@spec http_error(integer(), String.t(), [{String.t(), String.t()}]) :: http_error()
```
#### validation_error/3
Creates a validation error.
```elixir
@spec validation_error(String.t(), atom(), map()) :: validation_error()
```
#### format_error/2
Formats errors for display.
```elixir
@spec format_error(term(), keyword()) :: String.t()
```
#### recoverable?/1
Checks if an error is recoverable.
```elixir
@spec recoverable?(term()) :: boolean()
```
#### extract_debug_context/1
Extracts debugging information from errors.
```elixir
@spec extract_debug_context(term()) :: map()
```
## Configuration
### Application Configuration
```elixir
# config/config.exs
config :lather,
# Default timeout for all requests
default_timeout: 30_000,
# SSL verification mode
ssl_verify: :verify_peer,
# Connection pool settings
finch_pools: %{
default: [size: 25, count: 1]
},
# WSDL caching
cache_wsdl: true,
cache_ttl: 3600,
# Telemetry events
telemetry_enabled: true
```
### Runtime Configuration
```elixir
# Override configuration at runtime
Application.put_env(:lather, :default_timeout, 60_000)
```
## Telemetry Events
Lather emits telemetry events for monitoring:
- `[:lather, :request, :start]` - SOAP request started
- `[:lather, :request, :stop]` - SOAP request completed
- `[:lather, :request, :error]` - SOAP request failed
- `[:lather, :wsdl, :parse, :start]` - WSDL parsing started
- `[:lather, :wsdl, :parse, :stop]` - WSDL parsing completed
### Telemetry Example
```elixir
:telemetry.attach_many(
"lather-handler",
[
[:lather, :request, :start],
[:lather, :request, :stop],
[:lather, :request, :error]
],
&MyApp.Telemetry.handle_event/4,
nil
)
```
## Error Codes
| Code | Type | Description |
|------|------|-------------|
| `operation_not_found` | validation | Operation not defined in WSDL |
| `missing_required_parameter` | validation | Required parameter not provided |
| `invalid_parameter_type` | validation | Parameter type mismatch |
| `unsupported_encoding` | validation | Unsupported SOAP encoding |
| `invalid_soap_response` | validation | Malformed SOAP response |
| `transport_error` | transport | Network/connection error |
| `http_error` | http | HTTP status error |
| `wsdl_error` | wsdl | WSDL parsing error |
## Best Practices
1. **Reuse Clients**: Create clients once and reuse them across requests
2. **Handle Errors**: Always handle different error types appropriately
3. **Set Timeouts**: Configure appropriate timeouts for your use case
4. **Use SSL**: Always use HTTPS in production environments
5. **Cache WSDL**: Enable WSDL caching for better performance
6. **Monitor Operations**: Use telemetry for monitoring and debugging
7. **Validate Parameters**: Use built-in validation to catch errors early
8. **Connection Pooling**: Configure Finch pools for optimal performance