Skip to content

Commit 7d534a5

Browse files
authored
Merge pull request #428 from mulkieran/master-ids
Put str_id macro into a separate module
2 parents b25bcd0 + 142a5d1 commit 7d534a5

3 files changed

Lines changed: 185 additions & 118 deletions

File tree

src/id_macros.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// A module to contain functionality used for generating DM ids which
6+
// are restricted in length and format by devicemapper.
7+
8+
// Evaluates to an error string if the value does not match the requirements.
9+
macro_rules! str_check {
10+
($value:expr, $max_allowed_chars:expr) => {{
11+
let value = $value;
12+
let max_allowed_chars = $max_allowed_chars;
13+
if !value.is_ascii() {
14+
Some(format!("value {} has some non-ascii characters", value))
15+
} else {
16+
let num_chars = value.len();
17+
if num_chars == 0 {
18+
Some("value has zero characters".into())
19+
} else if num_chars > max_allowed_chars {
20+
Some(format!(
21+
"value {} has {} chars which is greater than maximum allowed {}",
22+
value, num_chars, max_allowed_chars
23+
))
24+
} else {
25+
None
26+
}
27+
}
28+
}};
29+
}
30+
31+
/// Define borrowed and owned versions of string types that guarantee
32+
/// conformance to DM restrictions, such as maximum length.
33+
// This implementation follows the example of Path/PathBuf as closely as
34+
// possible.
35+
macro_rules! str_id {
36+
($B:ident, $O:ident, $MAX:ident) => {
37+
/// The borrowed version of the DM identifier.
38+
#[derive(Debug, PartialEq, Eq, Hash)]
39+
pub struct $B {
40+
inner: str,
41+
}
42+
43+
/// The owned version of the DM identifier.
44+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45+
pub struct $O {
46+
inner: String,
47+
}
48+
49+
impl $B {
50+
/// Create a new borrowed identifier from a `&str`.
51+
#[allow(clippy::new_ret_no_self)]
52+
pub fn new(value: &str) -> DmResult<&$B> {
53+
if let Some(err_msg) = str_check!(value, $MAX - 1) {
54+
return Err(DmError::Core(
55+
ErrorKind::InvalidArgument(err_msg.into()).into(),
56+
));
57+
}
58+
Ok(unsafe { &*(value as *const str as *const $B) })
59+
}
60+
61+
/// Get the inner value as bytes
62+
pub fn as_bytes(&self) -> &[u8] {
63+
self.inner.as_bytes()
64+
}
65+
}
66+
67+
impl ToOwned for $B {
68+
type Owned = $O;
69+
fn to_owned(&self) -> $O {
70+
$O {
71+
inner: self.inner.to_owned(),
72+
}
73+
}
74+
}
75+
76+
impl fmt::Display for $B {
77+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78+
write!(f, "{}", &self.inner)
79+
}
80+
}
81+
82+
impl $O {
83+
/// Construct a new owned identifier.
84+
#[allow(clippy::new_ret_no_self)]
85+
pub fn new(value: String) -> DmResult<$O> {
86+
if let Some(err_msg) = str_check!(&value, $MAX - 1) {
87+
return Err(DmError::Core(
88+
ErrorKind::InvalidArgument(err_msg.into()).into(),
89+
));
90+
}
91+
Ok($O { inner: value })
92+
}
93+
}
94+
95+
impl AsRef<$B> for $O {
96+
fn as_ref(&self) -> &$B {
97+
self
98+
}
99+
}
100+
101+
impl Borrow<$B> for $O {
102+
fn borrow(&self) -> &$B {
103+
self.deref()
104+
}
105+
}
106+
107+
impl Deref for $O {
108+
type Target = $B;
109+
fn deref(&self) -> &$B {
110+
$B::new(&self.inner).expect("inner satisfies all correctness criteria for $B::new")
111+
}
112+
}
113+
};
114+
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
use std::borrow::Borrow;
119+
use std::fmt;
120+
use std::iter;
121+
use std::ops::Deref;
122+
123+
use crate::errors::{Error, ErrorKind};
124+
use crate::result::{DmError, DmResult};
125+
126+
const TYPE_LEN: usize = 12;
127+
str_id!(Id, IdBuf, TYPE_LEN);
128+
129+
#[test]
130+
/// Test for errors on an empty name.
131+
fn test_empty_name() {
132+
assert!(match Id::new("") {
133+
Err(DmError::Core(Error(ErrorKind::InvalidArgument(_), _))) => true,
134+
_ => false,
135+
});
136+
assert!(match IdBuf::new("".into()) {
137+
Err(DmError::Core(Error(ErrorKind::InvalidArgument(_), _))) => true,
138+
_ => false,
139+
})
140+
}
141+
142+
#[test]
143+
/// Test for errors on an overlong name.
144+
fn test_too_long_name() {
145+
let name = iter::repeat('a').take(TYPE_LEN + 1).collect::<String>();
146+
assert!(match Id::new(&name) {
147+
Err(DmError::Core(Error(ErrorKind::InvalidArgument(_), _))) => true,
148+
_ => false,
149+
});
150+
assert!(match IdBuf::new(name) {
151+
Err(DmError::Core(Error(ErrorKind::InvalidArgument(_), _))) => true,
152+
_ => false,
153+
})
154+
}
155+
156+
#[test]
157+
/// Test the concrete methods and traits of the interface.
158+
fn test_interface() {
159+
let id = Id::new("id").expect("is valid id");
160+
let id_buf = IdBuf::new("id".into()).expect("is valid id");
161+
162+
// Test as_bytes.
163+
assert_eq!(id.as_bytes(), &[105u8, 100u8]);
164+
assert_eq!(id_buf.as_bytes(), &[105u8, 100u8]);
165+
166+
// Test ToOwned implementation.
167+
// $B.to_owned() == $O
168+
assert_eq!(id.to_owned(), id_buf);
169+
170+
// Test Display implementation
171+
// X.to_string() = (*X).to_string()
172+
assert_eq!(id.to_string(), (*id).to_string());
173+
assert_eq!(id_buf.to_string(), (*id_buf).to_string());
174+
175+
// Test Deref
176+
assert_eq!(id_buf.deref(), id);
177+
assert_eq!(*id_buf, *id);
178+
}
179+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ extern crate uuid;
9393
/// Range macros
9494
#[macro_use]
9595
mod range_macros;
96+
/// ID macros
97+
#[macro_use]
98+
mod id_macros;
9699
/// shared constants
97100
mod consts;
98101
/// rust definitions of ioctl structs and consts

src/types.rs

Lines changed: 3 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -62,113 +62,14 @@ impl Sectors {
6262
}
6363
}
6464

65-
/// Returns an error if value is unsuitable.
66-
fn str_check(value: &str, max_allowed_chars: usize) -> DmResult<()> {
67-
if !value.is_ascii() {
68-
let err_msg = format!("value {} has some non-ascii characters", value);
69-
return Err(DmError::Core(ErrorKind::InvalidArgument(err_msg).into()));
70-
}
71-
let num_chars = value.len();
72-
if num_chars == 0 {
73-
return Err(DmError::Core(
74-
ErrorKind::InvalidArgument("value has zero characters".into()).into(),
75-
));
76-
}
77-
if num_chars > max_allowed_chars {
78-
let err_msg = format!(
79-
"value {} has {} chars which is greater than maximum allowed {}",
80-
value, num_chars, max_allowed_chars
81-
);
82-
return Err(DmError::Core(ErrorKind::InvalidArgument(err_msg).into()));
83-
}
84-
Ok(())
85-
}
86-
87-
/// Define borrowed and owned versions of string types that guarantee
88-
/// conformance to DM restrictions, such as maximum length.
89-
// This implementation follows the example of Path/PathBuf as closely as
90-
// possible.
91-
macro_rules! str_id {
92-
($B:ident, $O:ident, $MAX:ident, $check:ident) => {
93-
/// The borrowed version of the DM identifier.
94-
#[derive(Debug, PartialEq, Eq, Hash)]
95-
pub struct $B {
96-
inner: str,
97-
}
98-
99-
/// The owned version of the DM identifier.
100-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101-
pub struct $O {
102-
inner: String,
103-
}
104-
105-
impl $B {
106-
/// Create a new borrowed identifier from a `&str`.
107-
#[allow(clippy::new_ret_no_self)]
108-
pub fn new(value: &str) -> DmResult<&$B> {
109-
$check(value, $MAX - 1)?;
110-
Ok(unsafe { &*(value as *const str as *const $B) })
111-
}
112-
113-
/// Get the inner value as bytes
114-
pub fn as_bytes(&self) -> &[u8] {
115-
self.inner.as_bytes()
116-
}
117-
}
118-
119-
impl ToOwned for $B {
120-
type Owned = $O;
121-
fn to_owned(&self) -> $O {
122-
$O {
123-
inner: self.inner.to_owned(),
124-
}
125-
}
126-
}
127-
128-
impl fmt::Display for $B {
129-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130-
write!(f, "{}", &self.inner)
131-
}
132-
}
133-
134-
impl $O {
135-
/// Construct a new owned identifier.
136-
#[allow(clippy::new_ret_no_self)]
137-
pub fn new(value: String) -> DmResult<$O> {
138-
$check(&value, $MAX - 1)?;
139-
Ok($O { inner: value })
140-
}
141-
}
142-
143-
impl AsRef<$B> for $O {
144-
fn as_ref(&self) -> &$B {
145-
self
146-
}
147-
}
148-
149-
impl Borrow<$B> for $O {
150-
fn borrow(&self) -> &$B {
151-
self.deref()
152-
}
153-
}
154-
155-
impl Deref for $O {
156-
type Target = $B;
157-
fn deref(&self) -> &$B {
158-
$B::new(&self.inner).expect("inner satisfies all correctness criteria for $B::new")
159-
}
160-
}
161-
};
162-
}
163-
16465
/// A devicemapper name. Really just a string, but also the argument type of
16566
/// DevId::Name. Used in function arguments to indicate that the function
16667
/// takes only a name, not a devicemapper uuid.
167-
str_id!(DmName, DmNameBuf, DM_NAME_LEN, str_check);
68+
str_id!(DmName, DmNameBuf, DM_NAME_LEN);
16869

16970
/// A devicemapper uuid. A devicemapper uuid has a devicemapper-specific
17071
/// format.
171-
str_id!(DmUuid, DmUuidBuf, DM_UUID_LEN, str_check);
72+
str_id!(DmUuid, DmUuidBuf, DM_UUID_LEN);
17273

17374
/// Used as a parameter for functions that take either a Device name
17475
/// or a Device UUID.
@@ -192,20 +93,4 @@ impl<'a> fmt::Display for DevId<'a> {
19293
/// Number of bytes in Struct_dm_target_spec::target_type field.
19394
const DM_TARGET_TYPE_LEN: usize = 16;
19495

195-
str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN, str_check);
196-
197-
#[cfg(test)]
198-
mod tests {
199-
use crate::errors::Error;
200-
201-
use super::*;
202-
203-
#[test]
204-
/// Verify that creating an empty DmName is an error.
205-
pub fn test_empty_name() {
206-
assert!(match DmName::new("") {
207-
Err(DmError::Core(Error(ErrorKind::InvalidArgument(_), _))) => true,
208-
_ => false,
209-
})
210-
}
211-
}
96+
str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN);

0 commit comments

Comments
 (0)