ns_router/
name.rs

1use std::str::FromStr;
2use std::num::ParseIntError;
3use std::net::{IpAddr, SocketAddr};
4
5use abstract_ns;
6use abstract_ns::name::{self, Name};
7use quick_error::ResultExt;
8
9quick_error! {
10    #[derive(Debug)]
11    pub enum Error {
12        Name(name: String, err: name::Error) {
13            cause(err)
14            context(name: &'a str, err: name::Error)
15                -> (name.to_string(), err)
16        }
17        Port(name: String, err: ParseIntError) {
18            cause(err)
19            context(name: &'a str, err: ParseIntError)
20                -> (name.to_string(), err)
21        }
22    }
23}
24
25/// A name type that can be read from config
26///
27/// The core idea of `AutoName` is that for service with default port `80`
28/// (HTTP) we treat names the following way:
29///
30/// * `example.org` → A record example.org, port 80
31/// * `example.org:8080` → A record example.org, port 8080
32/// * `_service._proto.example.org` → SRV record, and port from the record
33/// * `127.0.0.1` → IP used directly, port 80
34/// * `127.0.0.1:8080` → IP/port used directly
35/// * `2001:db8::2:1` → IPv6 address (note: no brackets)
36/// * `[2001:db8::2:1]:1235` → IPv6 address and port (note: square brackets)
37///
38/// This works by wrapping the string read from configuration file into
39/// `AutoName::Auto` and using it in `Router`. You might override things
40/// via configuration specific things, for example in yaml you might want
41/// to write:
42///
43/// ```yaml
44/// addresses:
45/// - !Srv myservice.query.consul
46/// ```
47///
48/// ... And convert it into `Service("myservice.query.consul")` which will
49/// be resolved using ``SRV`` record (or similar mechanism) instead of
50/// using hostname (i.e. standard expects using `_service._proto` prefix but
51/// does not requires that).
52#[derive(Debug)]
53pub enum AutoName<'a> {
54    /// Auto-determine how to treat the name
55    Auto(&'a str),
56    /// Resolve host and attach specified port
57    HostPort(&'a str, u16),
58    /// Resolve host and attach default port to it
59    HostDefaultPort(&'a str),
60    /// Use service name and port resolved using SRV record or similar
61    Service(&'a str),
62    /// A bare IP used directly as a host
63    IpAddr(IpAddr),
64    /// A bare socket address used directly as a service address
65    SocketAddr(SocketAddr),
66}
67
68
69/// A helper trait to convert anything (yielded by a Stream) into name
70///
71/// The idea is that if you have a `Stream<Item=Vec<String>>` or vec of
72/// other things that are convertible into an `AutoName` you can pass this
73/// stream without copying anything. This is identical to `IntoIterator` but
74/// works by borrowing object.
75///
76/// Used for [`subscribe_stream`] method.
77///
78/// [`subscribe_stream`]: struct.Router.html#method.subscribe_stream
79pub trait IntoNameIter<'a> {
80    /// Item type, must be convertible into `AutoName`
81    type Item: Into<AutoName<'a>>;
82    /// Iterator type
83    type IntoIter: Iterator<Item=Self::Item>;
84    /// Borrow a iterator over the names from this type
85    fn into_name_iter(&'a self) -> Self::IntoIter;
86}
87
88#[derive(Debug, PartialEq, Eq, Hash, Clone)]
89pub(crate) enum InternalName {
90    HostPort(Name, u16),
91    Service(Name),
92    Addr(SocketAddr),
93}
94
95impl<'a> AutoName<'a> {
96    pub(crate) fn parse(&self, default_port: u16)
97        -> Result<InternalName, Error>
98    {
99        use self::AutoName as A;
100        use self::InternalName as I;
101        match *self {
102            A::Auto(x) => {
103                if let Ok(ip) = x.parse() {
104                    Ok(I::Addr(SocketAddr::new(ip, default_port)))
105                } else if let Ok(sa) = x.parse() {
106                    Ok(I::Addr(sa))
107                } else if x.starts_with("_") {
108                    Ok(I::Service(Name::from_str(x).context(x)?))
109                } else if let Some(pos) = x.find(':') {
110                    Ok(I::HostPort(Name::from_str(&x[..pos]).context(x)?,
111                                   x[pos+1..].parse().context(x)?))
112                } else {
113                    Ok(I::HostPort(Name::from_str(x).context(x)?,
114                                   default_port))
115                }
116            }
117            A::HostPort(name, port)
118            => Ok(I::HostPort(Name::from_str(name).context(name)?, port)),
119            A::HostDefaultPort(name)
120            => Ok(I::HostPort(Name::from_str(name).context(name)?, default_port)),
121            A::Service(name)
122            => Ok(I::Service(Name::from_str(name).context(name)?)),
123            A::IpAddr(ip) => Ok(I::Addr(SocketAddr::new(ip, default_port))),
124            A::SocketAddr(sa) => Ok(I::Addr(sa)),
125        }
126    }
127}
128
129impl<'a, T: AsRef<str> + 'a> From<&'a T> for AutoName<'a> {
130    fn from(val: &'a T) -> AutoName<'a> {
131        AutoName::Auto(val.as_ref())
132    }
133}
134
135impl<'a> From<&'a str> for AutoName<'a> {
136    fn from(val: &'a str) -> AutoName<'a> {
137        AutoName::Auto(val)
138    }
139}
140
141impl<'a, T: 'a> IntoNameIter<'a> for T
142    where &'a T: IntoIterator,
143          <&'a T as IntoIterator>::Item: Into<AutoName<'a>>,
144{
145    type Item = <&'a T as IntoIterator>::Item;
146    type IntoIter = <&'a T as IntoIterator>::IntoIter;
147    fn into_name_iter(&'a self) -> Self::IntoIter {
148        self.into_iter()
149    }
150}
151
152
153impl Into<abstract_ns::Error> for Error {
154    fn into(self) -> abstract_ns::Error {
155        match self {
156            Error::Name(name, _) => {
157                abstract_ns::Error::InvalidName(name, "bad name")
158            }
159            Error::Port(name, _) => {
160                abstract_ns::Error::InvalidName(name, "bad port number")
161            }
162        }
163    }
164}
165
166#[cfg(test)]
167mod test {
168    use abstract_ns::Name;
169    use super::AutoName as A;
170    use super::InternalName as I;
171
172    fn name(name: &str) -> Name {
173        name.parse().unwrap()
174    }
175
176    #[test]
177    fn auto() {
178        assert_eq!(A::Auto("localhost").parse(1234).unwrap(),
179            I::HostPort(name("localhost"), 1234));
180        assert_eq!(A::Auto("localhost:8080").parse(1234).unwrap(),
181            I::HostPort(name("localhost"), 8080));
182        assert_eq!(A::Auto("_my._svc.localhost").parse(1234).unwrap(),
183            I::Service(name("_my._svc.localhost")));
184    }
185
186    #[test]
187    #[should_panic(expected="InvalidChar")]
188    fn bad_names() {
189        A::Auto("_my._svc.localhost:8080").parse(1234).unwrap();
190    }
191}
192