meshviewer-models 1.0.0

structs of freifunks meshviewer json
Documentation
use std::{
    collections::HashMap,
    cmp::Ordering
};
use chrono::{DateTime,Utc};
use serde::{Deserialize, Serialize};

#[derive(Default,Serialize, Deserialize)]
pub struct Meshviewer {
    pub timestamp: Option<DateTime<Utc>>,
    pub nodes: Vec<Node>,
    pub links: Vec<Link>
}

#[derive(Default,Serialize, Deserialize)]
pub struct Node {
    pub firstseen: DateTime<Utc>,
    pub lastseen: DateTime<Utc>,
    pub is_online: bool,
    pub is_gateway: bool,
    pub clients: u32,
    pub clients_wifi24: u32,
    pub clients_wifi5: u32,
    pub clients_other: u32,
    pub clients_owe: u32,
    pub clients_owe24: u32,
    pub clients_owe5: u32,
    pub rootfs_usage: f64,
    pub loadavg: f64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub memory_usage: Option<f64>,
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub uptime: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub gateway_nexthop: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub gateway: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub gateway6: String,
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub gateway_tq: Option<f64>,
    pub node_id: String,
    pub mac: String,
    pub addresses: Vec<String>,
    pub domain: String,
    pub hostname: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub owner: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub location: Option<Location>,
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub firmware: Option<Firmware>,
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub autoupdater: Option<Autoupdater>,
    pub nproc: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>
}

#[derive(Default,Serialize, Deserialize)]
pub struct Firmware {
    #[serde(skip_serializing_if = "String::is_empty")]
    pub base: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub release: String
}

#[derive(Default,Serialize, Deserialize)]
pub struct Autoupdater {
    pub enabled: bool,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub branch: String
}

#[derive(Default,Serialize, Deserialize)]
pub struct Location {
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub longitude: Option<f64>,
    // in yanic always set with omitempty
    #[serde(skip_serializing_if = "Option::is_none")]
    pub latitude: Option<f64>
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Link {
    #[serde(rename="type")]
    pub link_type: String,
    pub source: String,
    pub target: String,
    pub source_tq: f64,
    pub target_tq: f64,
    pub source_addr: String,
    pub target_addr: String,
}

pub struct MeshviewerBuilder {
    nodes: HashMap<String,Node>,
    links: HashMap<String,Link>
}

impl MeshviewerBuilder {
    pub fn new() -> Self {
        MeshviewerBuilder{
            nodes: HashMap::new(),
            links: HashMap::new()
        }
    }
    pub fn build(self) -> Meshviewer {
        let mut mv = Meshviewer::default();
        mv.nodes = self.nodes.into_values().collect();
        mv.links = self.links.into_values().collect();
        mv
    }
    pub fn add_node(mut self, node: Node) -> Self {
        let id = node.node_id.clone();
        self.nodes.insert(id, node);
        self
    }
    pub fn add_link(mut self, link: Link) -> Self {
        let source_addr = link.source_addr.clone();
        let target_addr = link.target_addr.clone();
        let ordering = source_addr.cmp(&target_addr.clone());
        let id = match ordering {
            Ordering::Less => [source_addr, "-".to_string(), target_addr].concat(),
            Ordering::Greater => [target_addr, "-".to_string(), source_addr].concat(),
            Ordering::Equal => panic!("source and target id are equal"),
        };
        let current_tq = match self.links.get(&id.clone()) {
            Some(stored_link) => match ordering {
                Ordering::Less =>  stored_link.target_tq,
                Ordering::Greater =>  stored_link.source_tq,
                Ordering::Equal => panic!("source and target id are equal"),
            },
            None => 0.0
        };
        let ordered_link = match ordering {
            Ordering::Less => Link{
                link_type: link.link_type,
                source: link.source,
                target: link.target,
                source_addr: link.source_addr,
                target_addr: link.target_addr,
                source_tq: link.source_tq,
                target_tq: current_tq
            },
            Ordering::Greater => Link{
                link_type: link.link_type,
                source: link.target,
                target: link.source,
                source_addr: link.target_addr,
                target_addr: link.source_addr,
                source_tq: current_tq,
                target_tq: link.source_tq
            },
            Ordering::Equal => panic!("source and target id are equal"),
        };
        self.links.insert(id, ordered_link);
        self
    }
}


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

    #[test]
    fn use_builder() {
        let mv = MeshviewerBuilder::new()
            .add_node(Node{
                firstseen: DateTime::from_timestamp(61, 0).unwrap(),
                lastseen: DateTime::from_timestamp(61, 0).unwrap(),
                node_id: "test01".to_string(),
                is_online: true,
                ..Default::default()
            })
            .add_link(Link{
                link_type: "wifi5".to_string(),
                source: "test01".to_string(),
                target: "test02".to_string(),
                source_tq: 0.1,
                target_tq: -9.0,
                source_addr: "test01-if01".to_string(),
                target_addr: "test02-if01".to_string(),
            })
            .add_link(Link{
                link_type: "vpn".to_string(),
                source: "test02".to_string(),
                target: "test01".to_string(),
                source_tq: 0.3,
                target_tq: -9.0,
                source_addr: "test02-if01".to_string(),
                target_addr: "test01-if01".to_string(),
            })
            .build();
        assert_eq!(mv.nodes.len(), 1);
        assert_eq!(mv.links.len(), 1);
        assert_eq!(mv.links[0], Link{
            link_type: "vpn".to_string(),
            source: "test01".to_string(),
            target: "test02".to_string(),
            source_tq: 0.1,
            target_tq: 0.3,
            source_addr: "test01-if01".to_string(),
            target_addr: "test02-if01".to_string(),
        })

        
    }
}