Code for a SOAP-API client


I made my own client to interact with SOAP-API's. It seems to be working fine!
But I think my code can be improved a lot:

use xml::reader::{EventReader, XmlEvent};

// Basic SOAP-client to interact with API
// Note: This SOAP-client will only suffice for the sms use-case.
pub struct SoapClient {
    pub url: String,
    pub ns: String,
    pub credentials: (String, String),

// Basic XML Parser to interpet the response from the Tigron-API
// Note: This parser will only suffice for interpreting the response of the 'info' procedure.
struct XmlResponseParser;

impl SoapClient {
        Send a command to the API and retrieve XML
        :param service: Service of API to execute a command on. E.g: "sms"
        :param cmd: The command to execute. E.g: "send_sms"
        :param params: Parameters of the command. E.g: [("from", ""), ("to", "yyyy.yyy.yyy")]
        :param String: Returns the body of the API-response

    pub async fn call(
        service: &str,
        cmd: &str,
        params: Option<std::vec::Vec<(&str, &str)>>,
    ) -> String {
        let http = reqwest::Client::new();

        let params = match params {
            Some(params) => params,
            None => std::vec::Vec::new(),

        let cmd_xml = self.cmd_and_params_to_wsdl(cmd, params).await;
        let soap_body = self.soap_body(cmd_xml).await;

        let response = http
                url = self.url,
                service = service
            .header("Content-Type", "application/xml")
            .expect("failed to get response")


    // Convert the array from the command and params-array into WSDL/XML format
    async fn cmd_and_params_to_wsdl(
        cmd: &str,
        params: std::vec::Vec<(&str, &str)>,
    ) -> String {
        let mut xml = String::new();

        for param in params.iter() {
            let element = format!("<{key}>{value}</{key}>", key = param.0, value = param.1);
            xml = format!("{}{}", xml, element);

        xml = format!(
            "<{cmd} xmlns=\"{ns}\">{params}</{cmd}>",
            cmd = cmd,
            params = xml,
            ns = self.ns


    // Function to get the full WSDL for the call
    async fn soap_body(&self, cmd_xml: String) -> String {
        let wsdl = format!(
            r#"<?xml version="1.0"?>


                        <authenticate_user xmlns="{ns}">


            ns = self.ns,
            username = self.credentials.0,
            password = self.credentials.1,
            cmd = cmd_xml


impl XmlResponseParser {
        :param xml: Takes XML as input. E.g: <item><key>xxx</key><value>yyy</value></item>
        :return Vec<(String, String)>: Returns a vector of tuples (key, value)
    async fn parse(xml: &str) -> std::vec::Vec<(String, String)> {
        let mut return_items: std::vec::Vec<(String, String)> = std::vec::Vec::new();

        let parser = EventReader::from_str(xml);
        let mut start_reading = false;
        let mut read_key = false;
        let mut read_value = false;
        let mut key: String = String::new();
        for e in parser {
            match e {
                Ok(XmlEvent::StartElement { name, .. }) => {
                    if start_reading {
                        //    println!("<{}>", name);

                    read_key = false;
                    read_value = false;
                    if name.local_name == "key" {
                        read_key = true;
                    if name.local_name == "value" {
                        read_value = true;

                    if name.local_name == "return" {
                        start_reading = true;
                Ok(XmlEvent::EndElement { name }) => {
                    if name.local_name == "return" {
                        start_reading = false;

                    if start_reading {
                        //    println!("</{}>", name);
                Ok(XmlEvent::Characters(text)) => {
                    if start_reading {
                        //    println!("{}", text);

                    if read_key {
                        key = text.to_string();
                    if read_value {
                        return_items.push((key.to_string(), text.to_string()));
                Err(e) => {
                    //    println!("Error: {}", e);
                _ => {}


        Returns the value of the matching key
        :param items: Array of returned_items retrieved from API-response
        :param key: The key we want to retrieve the value from
        :return String: Returns the value matching the key
    async fn value(items: std::vec::Vec<(String, String)>, key: &str) -> String {
        for pair in items.iter() {
            if pair.0 == key {
                return pair.1.to_string();


Especially the XML-parser. I expect this kind of XML-response:


I did not find a Rust-library to parse XML easily so I had to hack a messy solution...

How would you improve this messy code?

I'm interested in trying your code and working on it. I'm new to Rust but I have done something similar in JavaScript (NodeJS) a few years ago and would be a good exercise. Would you like to share a GitHub repository we could work on it?

1 Like

Hi mate. Here you go:

I still have to clean-up the code though. Will maybe do that this week.

We have many libraries for parsing XML. Are you having specific difficulties with them?