lirays 0.1.0

Rust client for LiRAYS-SCADA over WebSocket + Protobuf
Documentation
use tokio_tungstenite::tungstenite::{
    client::IntoClientRequest,
    http::{Request, header::AUTHORIZATION},
};
use url::Url;

use crate::types::errors::ClientError;

#[derive(Clone, Debug)]
pub struct ConnectionOptions {
    pub host: String,
    pub port: i64,
    pub tls: bool,
    pub pat_token: Option<String>,
}

impl ConnectionOptions {
    pub fn new(host: impl Into<String>, port: i64, tls: bool, pat_token: Option<String>) -> Self {
        Self {
            host: host.into(),
            port,
            tls,
            pat_token,
        }
    }

    pub fn ws_url(&self) -> Result<String, ClientError> {
        build_ws_url(&self.host, self.port, self.tls)
    }
}

pub(crate) fn build_ws_request(options: &ConnectionOptions) -> Result<Request<()>, ClientError> {
    let url = options.ws_url()?;
    let mut request = url
        .into_client_request()
        .map_err(|_| ClientError::InvalidInput("invalid host/port"))?;

    if let Some(token) = options.pat_token.as_deref() {
        let value = format!("Bearer {token}");
        let header_value = value
            .parse()
            .map_err(|_| ClientError::InvalidInput("invalid PAT token format"))?;
        request.headers_mut().insert(AUTHORIZATION, header_value);
    }

    Ok(request)
}

fn build_ws_url(host: &str, port: i64, tls: bool) -> Result<String, ClientError> {
    let scheme = if tls { "wss" } else { "ws" };
    let base = format!("{scheme}://{host}:{port}/ws");
    let url = Url::parse(&base).map_err(|_| ClientError::InvalidInput("invalid host/port"))?;
    Ok(url.into())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn request_includes_authorization_header_when_pat_is_set() {
        let options =
            ConnectionOptions::new("127.0.0.1", 8245, false, Some("pat_test.token".to_string()));
        let request = build_ws_request(&options).expect("request should build");
        let auth = request
            .headers()
            .get(AUTHORIZATION)
            .expect("authorization header missing")
            .to_str()
            .expect("header should be valid utf8");
        assert_eq!(auth, "Bearer pat_test.token");
    }

    #[test]
    fn request_omits_authorization_header_when_pat_is_not_set() {
        let options = ConnectionOptions::new("127.0.0.1", 8245, false, None);
        let request = build_ws_request(&options).expect("request should build");
        assert!(request.headers().get(AUTHORIZATION).is_none());
    }
}