wiser 0.2.5

Access to the a Drayton Wiser hub.
Documentation
use std::{collections::BTreeMap, fmt};

use time::{OffsetDateTime, UtcOffset};

pub struct Measurement {
    id: String,
    pub timestamp: i128,
    tags: BTreeMap<String, String>,
    fields: BTreeMap<String, f64>,
}

impl Measurement {
    pub fn new(id: &str, timestamp: OffsetDateTime) -> Self {
        Measurement {
            id: id.to_owned(),
            timestamp: timestamp.to_offset(UtcOffset::UTC).unix_timestamp_nanos(),
            tags: BTreeMap::new(),
            fields: BTreeMap::new(),
        }
    }

    pub fn add_tag(&mut self, key: &str, value: &str) {
        self.tags.insert(key.to_owned(), value.to_owned());
    }

    pub fn add_field(&mut self, key: &str, value: f64) {
        assert!(value.is_finite());

        self.fields.insert(key.to_owned(), value);
    }
}

impl fmt::Display for Measurement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        assert!(!self.fields.is_empty());

        let tags = self
            .tags
            .iter()
            .map(|(k, v)| format!("{}={}", escape(k), escape(v)))
            .collect::<Vec<String>>();

        let fields = self
            .fields
            .iter()
            .map(|(k, v)| format!("{}={}", escape(k), v))
            .collect::<Vec<String>>();

        if !tags.is_empty() {
            f.pad(&format!(
                "{},{} {} {}",
                self.id,
                tags.join(","),
                fields.join(","),
                self.timestamp
            ))
        } else {
            f.pad(&format!(
                "{} {} {}",
                self.id,
                fields.join(","),
                self.timestamp
            ))
        }
    }
}

fn escape(tag: &str) -> String {
    tag.replace(' ', "\\ ").replace(',', "\\,")
}