// SOAP services, still the way into banks, carriers, customs, ERPs and government systems: a
// WSDL read for what it offers, operations called by name (SOAP 1.1 and 1.2), WS-Security for
// the ones that need a login, faults read, and a route answering SOAP itself.
//
//   SOAP_WSDL=https://example.com/Service.asmx?WSDL SOAP_URL=https://example.com/Service.asmx quillo run soap.qwe
//   then open http://localhost:8080/wsdl, /add, /balance, /envelope and /serve
//
// Without SOAP_WSDL the example reads soap_sample.wsdl beside it.

use "soap.qwer"

let WSDL = env("SOAP_WSDL", "soap_sample.wsdl")
let URL = env("SOAP_URL", "http://localhost:8080/calculator")
let NS = "http://example.com/calculator/"

route GET /wsdl {
    let w = soap_wsdl("soap_sample.wsdl", {})?
    {
        services: w.services | map((s) => { name: s.name, ports: s.ports | map((p) => `${p.name} SOAP ${p.version}`) }),
        operations: w.operations | filter((o) => o.version == "1.1") | map((o) => { name: o.name, action: o.action, params: o.input.params | map((p) => `${p.name}: ${p.type}`), doc: o.documentation }),
        from_server: soap_wsdl(WSDL, {})?.services[0].ports[0].address,
    }
}

// By name, from the WSDL: the endpoint, action and namespace come from it.
route GET /add = {
    soap11: soap_invoke(WSDL, "Add", { intA: 2, intB: 3 }, { endpoint: URL })?.result.AddResult,
    soap12: soap_invoke(WSDL, "Add", { intA: 40, intB: 2 }, { endpoint: URL, version: "1.2" })?.result.AddResult,
    divide: soap_invoke(WSDL, "Divide", { dividend: 1, divisor: 8 }, { endpoint: URL })?.result.DivideResult,
    fault11: soap_invoke(WSDL, "Divide", { dividend: 1, divisor: 0 }, { endpoint: URL }).error,
    fault12: soap_invoke(WSDL, "Divide", { dividend: 1, divisor: 0 }, { endpoint: URL, version: "1.2" }).error,
    missing: soap_invoke(WSDL, "Multiply", {}, { endpoint: URL }).error,
}

// WS-Security: a UsernameToken with a password digest, made fresh for each call.
route GET /balance {
    let security = { username: "ada", password: "s3cret", timestamp: 300 }
    let r = soap_call(URL, { operation: "GetBalance", namespace: NS, params: { account: "NL91-0001", currency: ["EUR", "USD"] }, security }, {})?
    {
        account: r.result.account,
        balances: r.result.balance | map((b) => `${b.amount} ${b.currency}`),
        as_text: soap_invoke(WSDL, "GetBalance", { account: "NL91-0001", currency: "GBP" }, { endpoint: URL, version: "1.2", security: { username: "ada", password: "s3cret", digest: false } })?.result.balance,
        wrong_password: soap_call(URL, { operation: "GetBalance", namespace: NS, params: { account: "NL91-0001" }, security: { username: "ada", password: "guess" } }, {}).error,
    }
}

// An envelope to look at: with a fixed nonce and time, the digest is always the same.
route GET /envelope = soap_envelope({
    operation: "GetBalance", namespace: NS, params: { account: "NL91-0001", "@ref": "q1", note: null },
    security: { username: "ada", password: "s3cret", nonce: "MTIzNDU2Nzg5MGFiY2RlZg==", created: "2026-10-30T12:00:00Z" },
})?

// Serving SOAP: the answer is an envelope too, or a fault.
fn serve(xml) {
    let m = soap_parse(xml)
    if is_err(m) { return soap_fault("Client", m.error, "1.1")? }
    match m {
        { operation: "Ping", result: { message: str as text } } => soap_envelope({ operation: "PingResponse", namespace: NS, version: m.version, params: { echo: text, at: "2026-10-30T12:00:00Z" } })?
        { operation } => soap_fault(m.version == "1.2" ? "Sender" : "Client", `no operation ${operation}`, m.version)?
    }
}

route POST /soap = respond(200, serve(request.body), { "content-type": "text/xml; charset=utf-8" })

route GET /serve = {
    ping: soap_parse(serve(soap_envelope({ operation: "Ping", namespace: NS, params: { message: "hi" } })?))?.result,
    other: soap_parse(serve(soap_envelope({ operation: "Pong", namespace: NS, version: "1.2" })?))?.fault,
}
