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?

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.