# Part 2: WSDL and Service Contracts

## Reading a WSDL for the First Time

The first time I opened a WSDL file, I closed it immediately. It was over 2,000 lines of nested XML with namespaces I had never seen. I did what most developers do: searched for a tool to generate code from it and moved on without understanding what was inside.

That approach worked until it didn't. When the generated client behaved unexpectedly — passing values in the wrong order, serialising optional fields incorrectly — I had no idea where to look. I had to go back and actually read the WSDL.

Understanding WSDL is a skill worth building. It is the formal contract between you and the service. When something goes wrong, the WSDL is where the answer is.

## What is WSDL?

**WSDL** (Web Services Description Language) is an XML-based document that describes a SOAP web service. It answers:

* What **operations** does the service offer?
* What **input** does each operation expect?
* What **output** does each operation return?
* What **data types** are used?
* Where is the service **hosted** (endpoint URL)?
* What **protocol** does it use?

WSDL is to SOAP what OpenAPI (Swagger) is to REST — but with stricter enforcement. A well-written WSDL completely and unambiguously describes a service.

## WSDL Structure

A WSDL document is made up of six major sections. Each builds on the previous.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions
    name="InventoryService"
    targetNamespace="http://example.com/inventory"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://example.com/inventory"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

    <!-- 1. types: XSD schema definitions -->
    <types> ... </types>

    <!-- 2. message: input/output message structures -->
    <message name="GetProductRequest"> ... </message>
    <message name="GetProductResponse"> ... </message>

    <!-- 3. portType: abstract operation definitions -->
    <portType name="InventoryPortType"> ... </portType>

    <!-- 4. binding: protocol and style details for portType -->
    <binding name="InventoryBinding" type="tns:InventoryPortType"> ... </binding>

    <!-- 5. service: the actual endpoint address -->
    <service name="InventoryService">
        <port name="InventoryPort" binding="tns:InventoryBinding">
            <soap:address location="http://api.example.com/inventory"/>
        </port>
    </service>

</definitions>
```

### Section 1: `<types>`

This is where data types are defined using XSD (XML Schema Definition). It describes the structure of the XML elements that appear in requests and responses.

```xml
<types>
    <xsd:schema targetNamespace="http://example.com/inventory">

        <!-- Simple product lookup request -->
        <xsd:element name="GetProductRequest">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="ProductId" type="xsd:string" minOccurs="1" maxOccurs="1"/>
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>

        <!-- Product response with nested complex type -->
        <xsd:element name="GetProductResponse">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="Product" type="tns:Product" minOccurs="0" maxOccurs="1"/>
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>

        <!-- Reusable complex type -->
        <xsd:complexType name="Product">
            <xsd:sequence>
                <xsd:element name="ProductId" type="xsd:string"/>
                <xsd:element name="Name" type="xsd:string"/>
                <xsd:element name="Price" type="xsd:decimal"/>
                <xsd:element name="InStock" type="xsd:boolean"/>
                <xsd:element name="Tags" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>

    </xsd:schema>
</types>
```

Key XSD primitives you will encounter repeatedly:

| XSD Type           | Python equivalent | Notes                                   |
| ------------------ | ----------------- | --------------------------------------- |
| `xsd:string`       | `str`             |                                         |
| `xsd:integer`      | `int`             |                                         |
| `xsd:decimal`      | `Decimal`         | Do not use `float` for money            |
| `xsd:boolean`      | `bool`            | Values are `true` / `false` (lowercase) |
| `xsd:dateTime`     | `datetime`        | ISO 8601 format                         |
| `xsd:date`         | `date`            |                                         |
| `xsd:base64Binary` | `bytes`           | For file attachments                    |
| `xsd:anyType`      | `Any`             | Avoid: undefined structure              |

### Section 2: `<message>`

Messages define the input and output payloads for operations. Each message refers to elements defined in the `<types>` section.

```xml
<message name="GetProductRequest">
    <part name="parameters" element="tns:GetProductRequest"/>
</message>

<message name="GetProductResponse">
    <part name="parameters" element="tns:GetProductResponse"/>
</message>
```

The `element` attribute references an element defined in the `<types>` XSD. In Document/Literal style (the most common), each message has exactly one `part` with an `element` reference.

### Section 3: `<portType>`

The `portType` is the abstract service interface — it lists operations without specifying how or where they run. Think of it as a Python abstract base class.

```xml
<portType name="InventoryPortType">

    <operation name="GetProduct">
        <input message="tns:GetProductRequest"/>
        <output message="tns:GetProductResponse"/>
    </operation>

    <operation name="ListProducts">
        <input message="tns:ListProductsRequest"/>
        <output message="tns:ListProductsResponse"/>
    </operation>

    <operation name="UpdateStock">
        <input message="tns:UpdateStockRequest"/>
        <output message="tns:UpdateStockResponse"/>
        <fault name="StockUpdateFault" message="tns:StockUpdateFaultMessage"/>
    </operation>

</portType>
```

Operations can have:

* `input` only — one-way operation (fire and forget)
* `input` + `output` — standard request-response
* `input` + `output` + `fault` — request-response with explicit error type

### Section 4: `<binding>`

The binding maps the abstract `portType` to a concrete protocol (SOAP) and specifies the communication style and encoding.

```xml
<binding name="InventoryBinding" type="tns:InventoryPortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

    <operation name="GetProduct">
        <soap:operation soapAction="http://example.com/inventory/GetProduct"/>
        <input>
            <soap:body use="literal"/>
        </input>
        <output>
            <soap:body use="literal"/>
        </output>
    </operation>

</binding>
```

Key fields to read:

* `style="document"` — Document style (vs `rpc`)
* `use="literal"` — Literal encoding (vs `encoded`; always prefer `literal`)
* `soapAction` — The `SOAPAction` HTTP header value (SOAP 1.1 only)

### Section 5: `<service>`

The `service` element specifies the actual network address where the service is deployed.

```xml
<service name="InventoryService">
    <port name="InventoryPort" binding="tns:InventoryBinding">
        <soap:address location="https://api.example.com/inventory/v1"/>
    </port>
</service>
```

This is what `zeep` uses when you load a WSDL — it reads the `location` to know where to send requests.

## Reading a Real WSDL with zeep

Once you understand the structure, you can use zeep to inspect any WSDL programmatically.

```python
# inspect_wsdl.py
from zeep import Client
from zeep.wsdl.utils import etree_to_string

# Load a public WSDL
wsdl_url = "http://www.dneonline.com/calculator.asmx?WSDL"
client = Client(wsdl_url)

# Print all available services and operations
print("=== Services ===")
for service in client.wsdl.services.values():
    print(f"Service: {service.name}")
    for port in service.ports.values():
        print(f"  Port: {port.name}")
        print(f"  Binding: {port.binding.name}")
        print(f"  Location: {port.binding_options['address']}")
        print()

print("=== Operations ===")
for service in client.wsdl.services.values():
    for port in service.ports.values():
        for operation in port.binding._operations.values():
            print(f"  {operation.name}")
```

Output:

```
=== Services ===
Service: Calculator
  Port: CalculatorSoap
  Binding: {http://tempuri.org/}CalculatorSoap
  Location: http://www.dneonline.com/calculator.asmx

=== Operations ===
  Add
  Divide
  Multiply
  Subtract
```

## Inspecting Operation Signatures

Before calling any operation, I always check what the operation expects as input and what it returns.

```python
# inspect_operation.py
from zeep import Client

client = Client("http://www.dneonline.com/calculator.asmx?WSDL")

# Inspect the 'Add' operation
service = client.wsdl.services["Calculator"]
port = service.ports["CalculatorSoap"]
operation = port.binding._operations["Add"]

print("Input:")
print(f"  {operation.input.body.type}")

print("\nOutput:")
print(f"  {operation.output.body.type}")
```

For complex services, zeep also provides a helper to pretty-print the full type structure:

```python
# pretty_print_type.py
from zeep import Client
from zeep.helpers import serialize_object

client = Client("http://www.dneonline.com/calculator.asmx?WSDL")

# Show the type structure zeep expects for an operation
print(client.get_type("ns0:AddResponse"))
```

## Writing a Minimal WSDL From Scratch

When building a SOAP service with spyne (covered in Part 3), the WSDL is generated automatically. But understanding how to write one manually helps when working with systems that require a handcrafted contract or when debugging generated WSDLs.

Here is a complete, minimal WSDL for a simple greeting service:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions
    name="GreetingService"
    targetNamespace="http://example.com/greeting"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://example.com/greeting"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

    <types>
        <xsd:schema targetNamespace="http://example.com/greeting">

            <xsd:element name="SayHelloRequest">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="Name" type="xsd:string" minOccurs="1"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>

            <xsd:element name="SayHelloResponse">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="Message" type="xsd:string"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>

        </xsd:schema>
    </types>

    <message name="SayHelloRequest">
        <part name="parameters" element="tns:SayHelloRequest"/>
    </message>

    <message name="SayHelloResponse">
        <part name="parameters" element="tns:SayHelloResponse"/>
    </message>

    <portType name="GreetingPortType">
        <operation name="SayHello">
            <input message="tns:SayHelloRequest"/>
            <output message="tns:SayHelloResponse"/>
        </operation>
    </portType>

    <binding name="GreetingBinding" type="tns:GreetingPortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="SayHello">
            <soap:operation soapAction="http://example.com/greeting/SayHello"/>
            <input><soap:body use="literal"/></input>
            <output><soap:body use="literal"/></output>
        </operation>
    </binding>

    <service name="GreetingService">
        <port name="GreetingPort" binding="tns:GreetingBinding">
            <soap:address location="http://localhost:8000/greeting"/>
        </port>
    </service>

</definitions>
```

Save this as `greeting.wsdl` and load it with zeep to verify it is valid:

```python
# validate_wsdl.py
from zeep import Client

# Load from local file using the file:// URI format
client = Client("file:///path/to/greeting.wsdl")

print("WSDL loaded successfully")
for service in client.wsdl.services.values():
    print(f"Service: {service.name}")
    for port in service.ports.values():
        for op_name in port.binding._operations:
            print(f"  Operation: {op_name}")
```

## Common WSDL Patterns in the Wild

After working with several enterprise SOAP integrations, these are the patterns I encounter most frequently.

### Fault Messages in portType

Most enterprise services declare explicit fault types. Always check if the portType operations have `fault` elements — they tell you what error types to handle.

```xml
<operation name="ProcessPayment">
    <input message="tns:ProcessPaymentRequest"/>
    <output message="tns:ProcessPaymentResponse"/>
    <fault name="PaymentFault" message="tns:PaymentFaultMessage"/>
    <fault name="InsufficientFundsFault" message="tns:InsufficientFundsFaultMessage"/>
</operation>
```

### Imported XSD Schemas

Large WSDLs often separate type definitions into external `.xsd` files and import them:

```xml
<types>
    <xsd:schema>
        <xsd:import
            namespace="http://example.com/common-types"
            schemaLocation="http://example.com/schemas/common-types.xsd"/>
    </xsd:schema>
</types>
```

zeep handles imported schemas automatically. When loading WSDLs behind corporate firewalls, these imports can fail if the schema URLs are internal. In that case, download the schemas manually and update the `schemaLocation` to a local path.

### Multiple Ports

A WSDL service can define multiple ports — often one for SOAP 1.1 and one for SOAP 1.2:

```xml
<service name="InventoryService">
    <port name="InventoryPortSoap11" binding="tns:InventoryBinding11">
        <soap:address location="https://api.example.com/inventory"/>
    </port>
    <port name="InventoryPortSoap12" binding="tns:InventoryBinding12">
        <soap12:address location="https://api.example.com/inventory"/>
    </port>
</service>
```

When using zeep, specify which port to use:

```python
client = Client(wsdl_url)
service = client.create_service(
    binding_name="{http://example.com/inventory}InventoryBinding11",
    address="https://api.example.com/inventory"
)
```

## What's Next

You now understand:

* The six sections of a WSDL document
* How to read types, messages, portType, binding, and service
* How to inspect a WSDL with zeep
* How to write a minimal WSDL from scratch
* Common patterns in enterprise WSDLs

In [Part 3](https://blog.htunnthuthu.com/getting-started/programming/soap-101/part-3-building-soap-services-python), we build a working SOAP service from scratch using spyne — defining operations, modelling complex types, and serving the endpoint over HTTP.
