README.md

# Lather 🧼

[![Hex.pm](https://img.shields.io/hexpm/v/lather.svg)](https://hex.pm/packages/lather)
[![Documentation](https://img.shields.io/badge/docs-hexdocs-blue.svg)](https://hexdocs.pm/lather)
[![License](https://img.shields.io/hexpm/l/lather.svg)](https://github.com/markcotner/lather/blob/main/LICENSE)

**A comprehensive SOAP library for Elixir** that provides both client and server capabilities with modern web interfaces. Lather can work with any SOAP service without requiring service-specific implementations, using dynamic WSDL analysis and runtime operation building.

## ✨ Key Features

- 🌐 **Universal SOAP Client**: Works with any SOAP service using WSDL analysis
- πŸ–₯️ **Complete SOAP Server**: Build SOAP services with a clean DSL
- πŸ”„ **Dynamic Operations**: Automatically discovers and builds requests for any SOAP operation  
- πŸ›‘οΈ **Enterprise Security**: WS-Security, Basic Auth, SSL/TLS support
- ⚑ **High Performance**: Built on Finch with connection pooling and async support
- πŸ”§ **Phoenix Integration**: Seamless integration with Phoenix applications
- πŸ“ **Type Safety**: Dynamic type mapping and validation with struct generation
- 🚨 **Robust Error Handling**: Structured error types with SOAP fault parsing

## 🌟 Enhanced Features (v1.0.0+)

### Multi-Protocol SOAP & REST Support

Lather v1.0 introduces a **three-layer API architecture** that serves multiple protocol types from a single service:

```
β”Œβ”€ SOAP 1.1 (Top - Maximum Compatibility)    β”‚ Legacy systems, .NET Framework
β”œβ”€ SOAP 1.2 (Middle - Enhanced Features)     β”‚ Modern SOAP, better error handling  
└─ REST/JSON (Bottom - Modern Applications)  β”‚ Web apps, mobile, JavaScript
```

### Interactive Web Interface

Professional HTML5 testing interface similar to .NET Web Services:

- πŸ“ **Interactive Forms**: Test operations directly in your browser
- 🌐 **Multi-Protocol Examples**: See SOAP 1.1, SOAP 1.2, and JSON formats
- πŸ“± **Responsive Design**: Works on desktop and mobile
- πŸŒ™ **Dark Mode Support**: Automatically respects browser dark mode preference
- ⚑ **Real-time Validation**: Type-aware parameter validation

### Enhanced WSDL Generation

Generate comprehensive WSDL documents with multiple protocol bindings:

```elixir
# Standard WSDL (SOAP 1.1 only)  
wsdl = Lather.Server.WsdlGenerator.generate(service_info, base_url)

# Enhanced WSDL (multi-protocol)
enhanced_wsdl = Lather.Server.EnhancedWSDLGenerator.generate(service_info, base_url)

# Interactive web forms
forms = Lather.Server.FormGenerator.generate_service_overview(service_info, base_url)
```

### Flexible URL Structure

- `GET  /service` β†’ Interactive service overview with testing forms
- `GET  /service?wsdl` β†’ Standard WSDL download
- `GET  /service?wsdl&enhanced=true` β†’ Multi-protocol WSDL  
- `GET  /service?op=OperationName` β†’ Interactive operation testing form
- `POST /service` β†’ SOAP 1.1 endpoint (maximum compatibility)
- `POST /service/v1.2` β†’ SOAP 1.2 endpoint (enhanced features)
- `POST /service/api` β†’ JSON/REST endpoint (modern applications)

## πŸš€ Quick Start

### Installation

Add `lather` to your `mix.exs` dependencies:

```elixir
def deps do
  [
    {:lather, "~> 1.0.0"},
    # Optional: for JSON/REST endpoints in enhanced features
    {:jason, "~> 1.4"}
  ]
end
```

### SOAP Client

Connect to any SOAP service and start making calls:

```elixir
# Create a dynamic client from any WSDL
{:ok, client} = Lather.DynamicClient.new("http://example.com/service?wsdl")

# Call any operation defined in the WSDL  
{:ok, response} = Lather.DynamicClient.call(client, "GetUser", %{
  "userId" => "12345"
})

# With authentication
{:ok, client} = Lather.DynamicClient.new(wsdl_url, [
  basic_auth: {"username", "password"},
  timeout: 30_000
])
```

### SOAP Server

Define SOAP services with a clean, macro-based DSL:

```elixir
defmodule MyApp.UserService do
  use Lather.Server
  
  @service_name "UserService"
  @target_namespace "http://myapp.com/user"
  
  defoperation get_user,
    input: [user_id: :string],
    output: [user: %{name: :string, email: :string}] do
    
    user = MyApp.Users.get!(user_id)
    {:ok, %{user: %{name: user.name, email: user.email}}}
  end
end

# Add to your Phoenix router
pipe_through :api
post "/soap/user", Lather.Server.Plug, service: MyApp.UserService
```

### Enhanced Multi-Protocol Server (v1.0.0+)

```elixir
# Define a service that supports SOAP 1.1, SOAP 1.2, and JSON/REST
defmodule MyApp.UserService do
  use Lather.Server

  @namespace "http://myapp.com/users"
  @service_name "UserManagementService"

  soap_operation "GetUser" do
    description "Retrieve user information by ID"
    
    input do
      parameter "userId", :string, required: true, description: "User identifier"
      parameter "includeProfile", :boolean, required: false, description: "Include full profile"
    end
    
    output do
      parameter "user", "tns:User", description: "User information"
    end
    
    soap_action "#{@namespace}/GetUser"
  end

  def get_user(%{"userId" => user_id} = params) do
    include_profile = Map.get(params, "includeProfile", false)
    # Your business logic here
    {:ok, %{"user" => %{"id" => user_id, "name" => "John Doe"}}}
  end
end

# Phoenix router with enhanced features
scope "/api/users" do
  pipe_through :api
  
  # Multi-protocol endpoints
  match :*, "/", Lather.Server.EnhancedPlug, service: MyApp.UserService
  match :*, "/*path", Lather.Server.EnhancedPlug, service: MyApp.UserService
end

# Generate enhanced WSDL with multiple protocols
service_info = MyApp.UserService.__service_info__()
enhanced_wsdl = Lather.Server.EnhancedWSDLGenerator.generate(service_info, "https://myapp.com/api/users")

# Generate interactive web forms
overview_page = Lather.Server.FormGenerator.generate_service_overview(service_info, "https://myapp.com/api/users")
```

### Access Multiple Protocol Endpoints

Your service automatically exposes multiple endpoints:

```bash
# Interactive web interface
curl -X GET "https://myapp.com/api/users"

# Standard WSDL (SOAP 1.1)
curl -X GET "https://myapp.com/api/users?wsdl"

# Enhanced WSDL (multi-protocol)
curl -X GET "https://myapp.com/api/users?wsdl&enhanced=true"

# Interactive operation form
curl -X GET "https://myapp.com/api/users?op=GetUser"

# SOAP 1.1 request
curl -X POST "https://myapp.com/api/users" \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H "SOAPAction: http://myapp.com/users/GetUser" \
  -d '<soap:Envelope>...</soap:Envelope>'

# SOAP 1.2 request
curl -X POST "https://myapp.com/api/users/v1.2" \
  -H "Content-Type: application/soap+xml; charset=utf-8; action=\"http://myapp.com/users/GetUser\"" \
  -d '<soap:Envelope>...</soap:Envelope>'

# JSON/REST request
curl -X POST "https://myapp.com/api/users/api" \
  -H "Content-Type: application/json" \
  -d '{"operation": "GetUser", "parameters": {"userId": "123"}}'
```

## πŸ“š Documentation & Examples

Lather includes comprehensive interactive documentation via Livebooks:

- **Client Tutorial** - Complete guide to using SOAP clients
- **Server Tutorial** - Building SOAP services step-by-step  
- **Type System Guide** - Working with complex types and validation
- **Debugging Guide** - Troubleshooting SOAP integration issues
- **Enterprise Patterns** - Authentication, error handling, monitoring

[View all tutorials β†’](examples/)

## πŸ—οΈ Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Application   β”‚    β”‚   Lather.Server  β”‚    β”‚  Phoenix/Plug   β”‚
β”‚                 β”‚    β”‚                  β”‚    β”‚                 β”‚
β”‚ DynamicClient   │◄──►│ Service DSL      │◄──►│ HTTP Integrationβ”‚
β”‚ WSDL Analysis   β”‚    β”‚ WSDL Generation  β”‚    β”‚ Request Routing β”‚
β”‚ Type Mapping    β”‚    β”‚ Operation Dispatchβ”‚    β”‚ Middleware     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                       β”‚                       β”‚
         β–Ό                       β–Ό                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Core Infrastructure                          β”‚
β”‚                                                                 β”‚
β”‚  HTTP Transport  β”‚  XML Processing  β”‚  Error Handling  β”‚  Auth  β”‚
β”‚  (Finch)        β”‚  (SweetXML)     β”‚  (Structured)    β”‚  (WS-*) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## πŸ”§ Advanced Usage

### Enterprise Authentication

```elixir
# WS-Security with UsernameToken
username_token = Lather.Auth.WSSecurity.username_token("user", "pass", :digest)
security_header = Lather.Auth.WSSecurity.security_header(username_token)

{:ok, client} = Lather.DynamicClient.new(wsdl_url,
  soap_headers: [security_header],
  ssl_options: [verify: :verify_peer]
)
```

### Complex Data Structures

```elixir
{:ok, response} = Lather.DynamicClient.call(client, "CreateOrder", %{
  "order" => %{
    "customer" => %{
      "name" => "John Doe",
      "email" => "john@example.com"
    },
    "items" => [
      %{"sku" => "ITEM001", "quantity" => 2},
      %{"sku" => "ITEM002", "quantity" => 1}
    ],
    "shipping" => %{
      "method" => "express",
      "address" => %{
        "street" => "123 Main St",
        "city" => "Portland",
        "state" => "OR",
        "zip" => "97201"
      }
    }
  }
})
```

### Error Handling

```elixir
case Lather.DynamicClient.call(client, "Operation", params) do
  {:ok, response} ->
    handle_success(response)
    
  {:error, %{type: :soap_fault} = fault} ->
    Logger.error("SOAP Fault: #{fault.fault_string}")
    handle_soap_fault(fault)
    
  {:error, %{type: :http_error} = error} ->
    Logger.error("HTTP Error #{error.status}")
    handle_http_error(error)
    
  {:error, %{type: :transport_error} = error} ->
    if Lather.Error.recoverable?(error) do
      schedule_retry()
    else
      handle_fatal_error(error)
    end
end
```

## πŸ§ͺ Testing

```bash
# Run all tests (excludes external API tests by default)
mix test

# Run with external API tests (hits real SOAP services - use sparingly!)
mix test --include external_api

# Run with coverage
mix test --cover

# Run specific test files
mix test test/lather/xml/parser_test.exs
```

### External API Tests

By default, tests that call external SOAP services are excluded to avoid:
- Overloading public APIs
- Network-dependent test failures  
- Slow test runs

External API tests validate the library against real-world services like:
- National Weather Service (document/encoded style)
- Country Info Service (document/literal style)

**Use external API tests responsibly:**
- Only when making significant SOAP-related changes
- Before releases
- When investigating service-specific issues

```bash
# Enable external API tests (be considerate!)
mix test --include external_api
```

## πŸ”§ Configuration

```elixir
# config/config.exs
config :lather,
  default_timeout: 30_000,
  ssl_verify: :verify_peer,
  finch_pools: %{
    default: [size: 25, count: 1]
  }

# Configure Finch for optimal performance
config :lather, :finch,
  pools: %{
    "https://api.example.com" => [
      size: 25,
      protocols: [:http2, :http1]
    ]
  }
```

## 🀝 Contributing

We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Add tests for your changes
4. Ensure all tests pass (`mix test`)
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request

## πŸ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## πŸ™ Acknowledgments

- Built with [Finch](https://hex.pm/packages/finch) for HTTP transport
- XML parsing powered by [SweetXml](https://hex.pm/packages/sweet_xml)
- Inspired by the SOAP libraries of other ecosystems

## πŸ“ž Support

- πŸ“– [Documentation](https://hexdocs.pm/lather)
- πŸ› [Issues](https://github.com/markcotner/lather/issues)
- πŸ’¬ [Discussions](https://github.com/markcotner/lather/discussions)

## πŸ—ΊοΈ Roadmap

### v1.0.0 (Next Release)
- [ ] SOAP 1.2 support  
- [ ] Performance optimizations and benchmarking
- [ ] Enhanced WS-Security features (XML Signature, Encryption)
- [ ] Additional server examples and templates

### Future Releases  
- [ ] MTOM/XOP binary attachments
- [ ] WS-Addressing and WS-ReliableMessaging
- [ ] OpenAPI 3.0 SOAP extension support
- [ ] GraphQL-style query interface for SOAP
- [ ] Service mesh integration patterns

---

**Lather** - Making SOAP integration in Elixir as smooth as possible! 🧼✨