lirays 0.1.0

Rust client for LiRAYS-SCADA over WebSocket + Protobuf
Documentation
use crate::namespace::{
    NamespaceFolder, NamespaceNode, NamespaceSchema, NamespaceVariable, VarDataType,
};
use crate::types::errors::ClientError;

pub(crate) fn namespace_schema_from_json(json: &str) -> Result<NamespaceSchema, ClientError> {
    let value: serde_json::Value =
        serde_json::from_str(json).map_err(|_| ClientError::InvalidInput("invalid JSON"))?;
    let roots_obj = value
        .as_object()
        .ok_or(ClientError::InvalidInput("root JSON must be an object"))?;

    let mut roots = std::collections::HashMap::new();
    for (key, val) in roots_obj {
        roots.insert(key.clone(), build_namespace_node(val)?);
    }

    Ok(NamespaceSchema { roots })
}

fn build_namespace_node(value: &serde_json::Value) -> Result<NamespaceNode, ClientError> {
    if let Some(obj) = value.as_object() {
        if let Some(var_val) = obj.get("variable") {
            return Ok(NamespaceNode {
                node: Some(crate::namespace::namespace_node::Node::Variable(
                    build_namespace_variable(var_val)?,
                )),
            });
        }

        let mut children = std::collections::HashMap::new();
        for (k, v) in obj {
            children.insert(k.clone(), build_namespace_node(v)?);
        }

        return Ok(NamespaceNode {
            node: Some(crate::namespace::namespace_node::Node::Folder(
                NamespaceFolder { children },
            )),
        });
    }

    if let Some(dtype) = value.as_str() {
        return Ok(NamespaceNode {
            node: Some(crate::namespace::namespace_node::Node::Variable(
                NamespaceVariable {
                    var_d_type: string_to_dtype(dtype) as i32,
                    unit: None,
                    min: None,
                    max: None,
                    options: vec![],
                    max_len: None,
                },
            )),
        });
    }

    Err(ClientError::InvalidInput(
        "invalid namespace JSON structure",
    ))
}

fn build_namespace_variable(val: &serde_json::Value) -> Result<NamespaceVariable, ClientError> {
    let obj = val
        .as_object()
        .ok_or(ClientError::InvalidInput("variable must be an object"))?;

    let dtype_str = obj
        .get("var_d_type")
        .and_then(|v| v.as_str())
        .ok_or(ClientError::InvalidInput("variable.var_d_type missing"))?;

    Ok(NamespaceVariable {
        var_d_type: string_to_dtype(dtype_str) as i32,
        unit: obj
            .get("unit")
            .and_then(|v| v.as_str())
            .map(|s| s.to_string()),
        min: obj.get("min").and_then(|v| v.as_f64()),
        max: obj.get("max").and_then(|v| v.as_f64()),
        options: obj
            .get("options")
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|x| x.as_str().map(|s| s.to_string()))
                    .collect()
            })
            .unwrap_or_default(),
        max_len: obj.get("max_len").and_then(|v| v.as_u64()),
    })
}

fn string_to_dtype(s: &str) -> VarDataType {
    match s.to_ascii_lowercase().as_str() {
        "float" => VarDataType::Float,
        "integer" | "int" => VarDataType::Integer,
        "text" | "string" => VarDataType::Text,
        "boolean" | "bool" => VarDataType::Boolean,
        _ => VarDataType::Invalid,
    }
}