Code for a SOAP-API client

Hello

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", "xxxx.xxx.xxx"), ("to", "yyyy.yyy.yyy")]
        :param String: Returns the body of the API-response
    */

    pub async fn call(
        &self,
        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
            .post(&format!(
                "{url}/{service}?WSDL",
                url = self.url,
                service = service
            ))
            .header("Content-Type", "application/xml")
            .body(soap_body)
            .send()
            .await
            .expect("failed to get response")
            .text()
            .await
            .unwrap();

        response
    }

    // Convert the array from the command and params-array into WSDL/XML format
    async fn cmd_and_params_to_wsdl(
        &self,
        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
        );

        xml
    }

    // 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"?>

                <soap:Envelope
                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
                soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">

                    <soap:Header>
                        <authenticate_user xmlns="{ns}">
                          <username>{username}</username>
                          <password>{password}</password>
                        </authenticate_user>
                    </soap:Header>

                    <soap:Body>
                        {cmd}
                    </soap:Body>

                </soap:Envelope>"#,
            ns = self.ns,
            username = self.credentials.0,
            password = self.credentials.1,
            cmd = cmd_xml
        );

        wsdl
    }
}

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);
                    break;
                }
                _ => {}
            }
        }

        return_items
    }

    /*
        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();
            }
        }

        "".to_string()
    }
}

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

<item>
   <key>xxx</key>
   <value>yyy</value>
</item>
<item>
   <key>xxx</key>
   <value>yyy</value>
</item>
...

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?

https://lib.rs/search?q=XML

ETree seems suitable library for XML parsing with its XPath support

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.