1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use actix::prelude::*;
use errors::Result;
use std::result::Result as StdResult;
use std::str::FromStr;
use std::{
    borrow::Cow,
    collections::{HashMap, HashSet},
    net::Ipv4Addr,
};

/// Struct describing single service in .local domain's network
///
/// Service Instance Name = <Instance> . <Service> . <Domain>
#[derive(Debug, Clone)]
pub struct ServiceDescription {
    /// Instance name; eg. "gu-provider"
    instance: Cow<'static, str>,
    /// Service type; eg. "_http._tcp"
    service: Cow<'static, str>,
}

impl ServiceDescription {
    pub fn new<A, B>(instance: A, service: B) -> Self
    where
        A: Into<Cow<'static, str>>,
        B: Into<Cow<'static, str>>,
    {
        ServiceDescription {
            instance: instance.into(),
            service: service.into(),
        }
    }

    pub(crate) fn to_string(&self) -> String {
        format!("{}.{}.local", self.instance, self.service)
    }
}

impl<T> From<T> for ServiceDescription
where
    T: Into<Cow<'static, str>>,
{
    fn from(s: T) -> Self {
        ServiceDescription {
            instance: s.into(),
            service: "_unlimited._tcp".into(),
        }
    }
}

impl Message for ServiceDescription {
    type Result = Result<HashSet<ServiceInstance>>;
}

#[derive(Debug, Clone)]
pub struct ServicesDescription {
    services: Vec<ServiceDescription>,
}

impl ServicesDescription {
    pub fn new(services: Vec<ServiceDescription>) -> Self {
        ServicesDescription { services }
    }

    pub fn single<A, B>(instance: A, service: B) -> Self
    where
        A: Into<Cow<'static, str>>,
        B: Into<Cow<'static, str>>,
    {
        Self::new(vec![ServiceDescription::new(instance, service)])
    }

    pub(crate) fn services(&self) -> &Vec<ServiceDescription> {
        &self.services
    }
}

impl Message for ServicesDescription {
    type Result = Result<HashSet<ServiceInstance>>;
}

/// Contains information about single service in a network
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct ServiceInstance {
    pub name: String,
    pub host: String,
    pub txt: Vec<String>,
    pub addrs_v4: Vec<Ipv4Addr>,
    pub ports: Vec<u16>,
}

impl ServiceInstance {
    pub fn extract<T, R>(&self, key: &T) -> Option<StdResult<R, R::Err>>
    where
        R: FromStr,
        T: AsRef<str> + ?Sized,
    {
        let key_str = format!("{}=", key.as_ref());

        self.txt
            .iter()
            .filter(|txt| txt.starts_with(&key_str))
            .map(|txt| R::from_str(&txt[key_str.len()..]))
            .next()
    }
}

#[derive(Debug, Serialize, Default)]
pub struct Services {
    map: HashMap<String, HashSet<ServiceInstance>>,
}

impl<'a> From<&'a ServicesDescription> for Services {
    fn from(s: &'a ServicesDescription) -> Self {
        let mut res = Services::default();
        for service in s.services() {
            res.add_service(service.to_string());
        }

        res
    }
}

impl Services {
    pub(crate) fn add_service(&mut self, s: String) {
        self.map.insert(s, HashSet::new());
    }

    pub(crate) fn add_instance(&mut self, instance: ServiceInstance) {
        self.map
            .get_mut::<str>(instance.name.as_ref())
            .and_then(|map| Some(map.insert(instance)));
    }

    pub(crate) fn collect(self) -> HashSet<ServiceInstance> {
        let mut set: HashSet<ServiceInstance> = HashSet::new();
        for i in self.map {
            set.extend(i.1)
        }
        set
    }
}