plc-comm-hostlink-rust 0.1.3

Async Rust Host Link client based on plc-comm-hostlink-dotnet
Documentation
use crate::error::HostLinkError;
use encoding_rs::SHIFT_JIS;

pub fn build_frame(body: &str, append_lf: bool) -> Vec<u8> {
    let body = body.trim().as_bytes();
    let mut result = Vec::with_capacity(body.len() + 1 + usize::from(append_lf));
    result.extend_from_slice(body);
    result.push(b'\r');
    if append_lf {
        result.push(b'\n');
    }
    result
}

fn trim_response(raw: &[u8]) -> Result<&[u8], HostLinkError> {
    if raw.is_empty() {
        return Err(HostLinkError::protocol("Empty response"));
    }

    let mut len = raw.len();
    while len > 0 && matches!(raw[len - 1], b'\r' | b'\n') {
        len -= 1;
    }

    if len == 0 {
        return Err(HostLinkError::protocol("Malformed response frame"));
    }

    Ok(&raw[..len])
}

pub fn decode_response(raw: &[u8]) -> Result<String, HostLinkError> {
    let payload = trim_response(raw)?;
    let text = std::str::from_utf8(payload)
        .map_err(|_| HostLinkError::protocol("Response is not ASCII"))?;
    if !text.is_ascii() {
        return Err(HostLinkError::protocol("Response is not ASCII"));
    }
    Ok(text.to_owned())
}

pub fn decode_comment_response(raw: &[u8]) -> Result<String, HostLinkError> {
    let payload = trim_response(raw)?;
    if let Ok(text) = std::str::from_utf8(payload) {
        return Ok(text.to_owned());
    }

    let (text, _, had_errors) = SHIFT_JIS.decode(payload);
    if had_errors {
        return Err(HostLinkError::protocol(
            "Response could not be decoded as UTF-8 or Shift_JIS",
        ));
    }
    Ok(text.into_owned())
}

pub fn ensure_success(response_text: String) -> Result<String, HostLinkError> {
    if response_text.len() == 2
        && response_text.starts_with('E')
        && response_text.as_bytes()[1].is_ascii_digit()
    {
        let code = response_text.clone();
        return Err(HostLinkError::plc(code, response_text));
    }

    Ok(response_text)
}

pub fn split_data_tokens(response_text: &str) -> Vec<String> {
    response_text
        .split([' ', ','])
        .filter(|token| !token.is_empty())
        .map(ToOwned::to_owned)
        .collect()
}