drmem_api/types/device/
name.rs

1//! Defines and enforces device name requirements.
2//!
3//! Every device in a running instance of `drmemd` needs to have a
4//! unique name. A device name is made up of segments separated with
5//! colons. The last segment is the base name of the device and all
6//! previous segments are the "path" of the device. A segment consists
7//! of one or more UTF-8 alphanumeric or dash characters.
8//!
9//! In a `[[driver]]` section of `drmemd`'s configuration file, a path
10//! is specified for the driver instance and the driver provides the
11//! base names for its set of devices.
12use crate::{types::Error, Result};
13use serde_derive::Deserialize;
14use std::fmt;
15use std::str::FromStr;
16
17#[derive(Debug, Clone, PartialEq, Deserialize, Hash, Eq)]
18struct Segment(String);
19
20impl Segment {
21    // Returns `true` if the character can be used in a segment of the
22    // device name.
23
24    fn is_valid_char((idx, ch): (usize, char), len: usize) -> bool {
25        ch.is_alphanumeric() || (ch == '-' && idx != 0 && idx != len - 1)
26    }
27
28    // Creates a `Segment`, if the strings contains a well-formed
29    // segment name.
30
31    fn create(s: &str) -> Result<Self> {
32        if !s.is_empty() {
33            if s.chars()
34                .enumerate()
35                .all(|v| Segment::is_valid_char(v, s.len()))
36            {
37                Ok(Segment(String::from(s)))
38            } else {
39                Err(Error::InvArgument(String::from(
40                    "segment contains invalid character",
41                )))
42            }
43        } else {
44            Err(Error::InvArgument(String::from(
45                "contains zero-length segment",
46            )))
47        }
48    }
49}
50
51// This trait allows one to use `.parse::<Segment>()`.
52
53impl FromStr for Segment {
54    type Err = Error;
55
56    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
57        Segment::create(s)
58    }
59}
60
61impl fmt::Display for Segment {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", &self.0)
64    }
65}
66
67/// A type which models a device's path.
68///
69/// A path is a series of segments, where segments are made up of one
70/// or more alphanumeric or dash characters.
71#[derive(Debug, PartialEq, Clone, Deserialize, Hash, Eq)]
72#[serde(try_from = "String")]
73pub struct Path(Vec<Segment>);
74
75impl Path {
76    /// Creates a `Path` from a string slice. If any segment contains
77    /// an invalid character, an `Err()` is returned.
78    pub fn create(s: &str) -> Result<Self> {
79        s.split(':')
80            .map(Segment::create)
81            .collect::<Result<Vec<Segment>>>()
82            .map(Path)
83    }
84}
85
86// This trait is defined so that the .TOML parser will use it to parse
87// the device prefix field. Without this, the .TOML parser wants array
88// notation for the path specification (because `Path` is a newtype
89// that wraps a `Vec<>`.)
90
91impl TryFrom<String> for Path {
92    type Error = Error;
93
94    fn try_from(s: String) -> Result<Self> {
95        Path::create(&s)
96    }
97}
98
99// This trait allows one to use `.parse::<Path>()`.
100
101impl FromStr for Path {
102    type Err = Error;
103
104    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
105        Path::create(s)
106    }
107}
108
109impl fmt::Display for Path {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "{}", &self.0[0])?;
112        for ii in &self.0[1..] {
113            write!(f, ":{}", &ii)?
114        }
115        Ok(())
116    }
117}
118
119/// A type representing the base name of a device.
120///
121/// The base name consists of one or more alphanumeric or dash
122/// characters.
123#[derive(Debug, PartialEq, Clone, Deserialize, Hash, Eq)]
124#[serde(try_from = "String")]
125pub struct Base(Segment);
126
127impl Base {
128    /// Creates a `Base` from a string slice. If it contains an
129    /// invalid character, `Err()` is returned.
130    pub fn create(s: &str) -> Result<Self> {
131        Segment::create(s).map(Base)
132    }
133}
134
135impl TryFrom<String> for Base {
136    type Error = Error;
137
138    fn try_from(s: String) -> Result<Self> {
139        Base::create(&s)
140    }
141}
142
143// This trait allows one to use `.parse::<Base>()`.
144
145impl FromStr for Base {
146    type Err = Error;
147
148    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
149        Base::create(s)
150    }
151}
152
153impl fmt::Display for Base {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        write!(f, "{}", &self.0)
156    }
157}
158
159/// Holds a validated device name. A device name consists of a path
160/// and a base name where each portion of the path is separated with a
161/// colon. Each segment of the path or the name is composed of alpha-
162/// numeric and the dash characters. The dash cannot be the first or
163/// last character, however.
164///
165/// More formally:
166///
167/// ```ignore
168/// DEVICE-NAME = PATH NAME
169/// PATH = (SEGMENT ':')+
170/// NAME = SEGMENT
171/// SEGMENT = [0-9a-zA-Z] ( [0-9a-zA-Z-]* [0-9a-zA-Z] )?
172/// ```
173///
174/// All device names will have a path and a name. Although
175/// superficially similar, device names are not like file system
176/// names. Specifically, there's no concept of moving up or down
177/// paths. The paths provide a naming convention to organize devices.
178/// The client API supports looking up device names using patterns, so
179/// a logical path hierarchy can make those searches more productive.
180#[derive(Debug, PartialEq, Hash, Eq, Clone, Deserialize)]
181#[serde(try_from = "String")]
182pub struct Name {
183    path: Path,
184    base: Base,
185}
186
187impl Name {
188    /// Creates an instance of `Name`, if the provided string
189    /// describes a well-formed device name.
190    pub fn create(s: &str) -> Result<Name> {
191        match s
192            .split(':')
193            .map(Segment::create)
194            .collect::<Result<Vec<Segment>>>()
195        {
196            Ok(segments) if segments.len() < 2 => Err(Error::InvArgument(
197                String::from("device name requires a path and base name"),
198            )),
199            Ok(segments) => Ok(Name {
200                path: Path(segments[0..segments.len() - 1].to_vec()),
201                base: Base(segments[segments.len() - 1].clone()),
202            }),
203            Err(e) => Err(e),
204        }
205    }
206
207    /// Builds a device name from `Path` and `Base` components.
208    pub fn build(path: Path, base: Base) -> Name {
209        Name { path, base }
210    }
211
212    /// Returns the path of the device name without the trailing ':'.
213    pub fn get_path(&self) -> Path {
214        self.path.clone()
215    }
216
217    /// Returns the base name of the device.
218    pub fn get_name(&self) -> Base {
219        self.base.clone()
220    }
221}
222
223impl fmt::Display for Name {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        write!(f, "{}:{}", &self.path, &self.base)
226    }
227}
228
229impl TryFrom<String> for Name {
230    type Error = Error;
231
232    fn try_from(s: String) -> Result<Self> {
233        Name::create(&s)
234    }
235}
236
237// This trait allows one to use `.parse::<Name>()`.
238
239impl FromStr for Name {
240    type Err = Error;
241
242    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
243        Name::create(s)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_segment() {
253        assert!("".parse::<Segment>().is_err());
254        assert!(
255            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
256                .parse::<Segment>()
257                .is_ok()
258        );
259        assert!("a-b".parse::<Segment>().is_ok());
260        assert!("a:b".parse::<Segment>().is_err());
261        assert!("-a".parse::<Segment>().is_err());
262        assert!("a-".parse::<Segment>().is_err());
263        assert!(" ".parse::<Segment>().is_err());
264        assert_eq!(format!("{}", "a-b".parse::<Segment>().unwrap()), "a-b");
265
266        // Check non-ASCII entries.
267
268        assert!("٣".parse::<Segment>().is_ok());
269        assert!("温度".parse::<Segment>().is_ok());
270        assert!("🤖".parse::<Segment>().is_err());
271    }
272
273    #[test]
274    fn test_base() {
275        assert_eq!(format!("{}", "a-b".parse::<Base>().unwrap()), "a-b");
276        assert!("a:b".parse::<Base>().is_err());
277    }
278
279    #[test]
280    fn test_path() {
281        assert!("".parse::<Path>().is_err());
282        assert!("basement:🤖".parse::<Path>().is_err());
283
284        assert_eq!(format!("{}", "a-b".parse::<Path>().unwrap()), "a-b");
285        assert_eq!(format!("{}", "a:b".parse::<Path>().unwrap()), "a:b");
286        assert_eq!(format!("{}", "a:b:c".parse::<Path>().unwrap()), "a:b:c");
287        assert_eq!(
288            format!("{}", "家:温度".parse::<Path>().unwrap()),
289            "家:温度"
290        );
291    }
292
293    #[test]
294    fn test_device_name() {
295        assert!("".parse::<Name>().is_err());
296        assert!(":".parse::<Name>().is_err());
297        assert!("a".parse::<Name>().is_err());
298        assert!(":a".parse::<Name>().is_err());
299        assert!("a:".parse::<Name>().is_err());
300        assert!("a::a".parse::<Name>().is_err());
301
302        assert!("p:a.".parse::<Name>().is_err());
303        assert!("p:a.a".parse::<Name>().is_err());
304        assert!("p.a:a".parse::<Name>().is_err());
305        assert!("p:a-".parse::<Name>().is_err());
306        assert!("p:-a".parse::<Name>().is_err());
307        assert!("p-:a".parse::<Name>().is_err());
308        assert!("-p:a".parse::<Name>().is_err());
309
310        assert_eq!(
311            "p:abc".parse::<Name>().unwrap(),
312            Name {
313                path: Path::create("p").unwrap(),
314                base: Base::create("abc").unwrap(),
315            }
316        );
317        assert_eq!(
318            "p:abc1".parse::<Name>().unwrap(),
319            Name {
320                path: Path::create("p").unwrap(),
321                base: Base::create("abc1").unwrap(),
322            }
323        );
324        assert_eq!(
325            "p:abc-1".parse::<Name>().unwrap(),
326            Name {
327                path: Path::create("p").unwrap(),
328                base: Base::create("abc-1").unwrap(),
329            }
330        );
331        assert_eq!(
332            "p-1:p-2:abc".parse::<Name>().unwrap(),
333            Name {
334                path: Path::create("p-1:p-2").unwrap(),
335                base: Base::create("abc").unwrap(),
336            }
337        );
338
339        let dn = "p-1:p-2:abc".parse::<Name>().unwrap();
340
341        assert_eq!(dn.get_path(), Path::create("p-1:p-2").unwrap());
342        assert_eq!(dn.get_name(), Base::create("abc").unwrap());
343
344        assert_eq!(format!("{}", dn), "p-1:p-2:abc");
345    }
346}