# 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 to_elements(&self) -> Vec {
vec![vec![
xmltree::Element::node("tickerSymbol").with_text(self.ticker_symbol.to_string())
]]
.drain(..)
.flatten()
.collect()
}
}
impl savon::gen::FromElement for TradePriceRequest {
fn from_element(element: &xmltree::Element) -> Result {
Ok(TradePriceRequest {
ticker_symbol: element.get_at_path(&["tickerSymbol"]).and_then(|e| {
e.get_text()
.map(|s| s.to_string())
.ok_or(savon::rpser::xml::Error::Empty)
})?,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct TradePrice {
pub price: f64,
}
impl savon::gen::ToElements for TradePrice {
fn to_elements(&self) -> Vec {
vec![vec![
xmltree::Element::node("price").with_text(self.price.to_string())
]]
.drain(..)
.flatten()
.collect()
}
}
impl savon::gen::FromElement for TradePrice {
fn from_element(element: &xmltree::Element) -> Result {
Ok(TradePrice {
price: element
.get_at_path(&["price"])
.map_err(savon::Error::from)
.and_then(|e| {
e.get_text()
.ok_or(savon::rpser::xml::Error::Empty)
.map_err(savon::Error::from)
.and_then(|s| s.parse().map_err(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 to_elements(&self) -> Vec {
self.0.to_elements()
}
}
impl savon::gen::FromElement for GetLastTradePriceOutput {
fn from_element(element: &xmltree::Element) -> Result {
TradePrice::from_element(element).map(GetLastTradePriceOutput)
}
}
#[derive(Clone, Debug, Default)]
pub struct GetLastTradePriceInput(pub TradePriceRequest);
impl savon::gen::ToElements for GetLastTradePriceInput {
fn to_elements(&self) -> Vec {
self.0.to_elements()
}
}
impl savon::gen::FromElement for GetLastTradePriceInput {
fn from_element(element: &xmltree::Element) -> Result {
TradePriceRequest::from_element(element).map(GetLastTradePriceInput)
}
}
}
#[allow(dead_code)]
impl StockQuoteService {
pub fn new(base_url: String) -> Self {
Self::with_client(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, savon::Error> {
savon::http::request_response(
&self.client,
&self.base_url,
"http://example.com/stockquote.wsdl",
"GetLastTradePrice",
&get_last_trade_price_input,
)
.await
}
}
```