savon, a SOAP client generator for Rust

savon generates code from a WSDL file, that you can then include in your project. It will generate serialization and deserialization code, along with an async HTTP client API (based on reqwest).

Usage

in Cargo.toml:

toml [dependencies] savon = "0.1"

in build.rs:

rust fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let s = savon::gen::gen_write("./assets/example.wsdl", &out_dir).unwrap(); }

Finally, in your code:

rust mod soap { include!(concat!(env!("OUT_DIR"), "/example.rs")); }

You can then use it as follows:

rust let client = soap::StockQuoteService::new("http://example.com".to_string()); let res = client.get_last_trade_price(soap::GetLastTradePriceInput(TradePriceRequest { ticker_symbol: "SOAP".to_string() })).await?;

Under the hood

If you use the following WSDL file as input:

```wsdl

My first service

```

It will generate this code:

```rust use savon::internal::xmltree; use savon::rpser::xml::*;

[derive(Clone, Debug, Default)]

pub struct TradePriceRequest { pub ticker_symbol: String, }

impl savon::gen::ToElements for TradePriceRequest { fn toelements(&self) -> Vec { vec![vec![ xmltree::Element::node("tickerSymbol").withtext(self.tickersymbol.tostring()) ]] .drain(..) .flatten() .collect() } }

impl savon::gen::FromElement for TradePriceRequest { fn fromelement(element: &xmltree::Element) -> Result { Ok(TradePriceRequest { tickersymbol: element.getatpath(&["tickerSymbol"]).andthen(|e| { e.gettext() .map(|s| s.tostring()) .okor(savon::rpser::xml::Error::Empty) })?, }) } }

[derive(Clone, Debug, Default)]

pub struct TradePrice { pub price: f64, }

impl savon::gen::ToElements for TradePrice { fn toelements(&self) -> Vec { vec![vec![ xmltree::Element::node("price").withtext(self.price.to_string()) ]] .drain(..) .flatten() .collect() } }

impl savon::gen::FromElement for TradePrice { fn fromelement(element: &xmltree::Element) -> Result { Ok(TradePrice { price: element .getatpath(&["price"]) .maperr(savon::Error::from) .andthen(|e| { e.gettext() .okor(savon::rpser::xml::Error::Empty) .maperr(savon::Error::from) .andthen(|s| s.parse().maperr(savon::Error::from)) })?, }) } }

pub struct StockQuoteService { pub base_url: String, pub client: savon::internal::reqwest::Client, }

pub mod messages { use super::*;

#[derive(Clone, Debug, Default)] pub struct GetLastTradePriceOutput(pub TradePrice);

impl savon::gen::ToElements for GetLastTradePriceOutput { fn toelements(&self) -> Vec { self.0.toelements() } }

impl savon::gen::FromElement for GetLastTradePriceOutput { fn fromelement(element: &xmltree::Element) -> Result { TradePrice::fromelement(element).map(GetLastTradePriceOutput) } }

[derive(Clone, Debug, Default)]

pub struct GetLastTradePriceInput(pub TradePriceRequest);

impl savon::gen::ToElements for GetLastTradePriceInput { fn toelements(&self) -> Vec { self.0.toelements() } }

impl savon::gen::FromElement for GetLastTradePriceInput { fn fromelement(element: &xmltree::Element) -> Result { TradePriceRequest::fromelement(element).map(GetLastTradePriceInput) } } }

[allow(dead_code)]

impl StockQuoteService { pub fn new(baseurl: String) -> Self { Self::withclient(base_url, savon::internal::reqwest::Client::new()) }

pub fn with_client(base_url: String, client: savon::internal::reqwest::Client) -> Self {
    StockQuoteService { base_url, client }
}

pub async fn get_last_trade_price(
    &self,
    get_last_trade_price_input: messages::GetLastTradePriceInput,
) -> Result<Result<messages::GetLastTradePriceOutput, ()>, savon::Error> {
    savon::http::request_response(
        &self.client,
        &self.base_url,
        "http://example.com/stockquote.wsdl",
        "GetLastTradePrice",
        &get_last_trade_price_input,
    )
    .await
}

} ```