Compare commits

...

2 Commits

Author SHA1 Message Date
a25092c7a0 Add getters for the attributes 2020-09-29 12:26:49 +02:00
5d83f1264f Add custom errors 2020-09-29 12:26:43 +02:00
3 changed files with 165 additions and 34 deletions

View File

@ -1,3 +1,3 @@
pub mod passwd;
pub mod userlib_error;
pub use passwd::{Password, Username};

View File

@ -1,10 +1,11 @@
/*#![warn(
#![warn(
clippy::all,
clippy::restriction,
/* clippy::restriction,*/
clippy::pedantic,
clippy::nursery,
clippy::cargo
)]*/
)]
use crate::userlib_error::UserLibError;
use std::cmp::Eq;
use std::convert::TryFrom;
use std::fmt::{self, Display};
@ -72,25 +73,105 @@ pub struct Passwd<'a> {
}
impl<'a> Passwd<'a> {
pub fn new_from_string(line: &'a str) -> Result<Self, &str> {
let elements: Vec<&str> = line.split(":").collect();
/// Parse a line formatted like one in `/etc/passwd` and construct a matching `Passwd` instance
///
/// # Example
/// ```
/// let pwd = adduser::passwd::Passwd::new_from_string(
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test"
/// ).unwrap();
/// assert_eq!(pwd.get_username(), "testuser");
/// ```
///
/// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
pub fn new_from_string(line: &'a str) -> Result<Self, UserLibError> {
let elements: Vec<&str> = line.split(':').collect();
if elements.len() == 7 {
Ok(Passwd {
username: Username::try_from(*elements.get(0).unwrap())
.expect("failed to parse username."),
password: Password::try_from(*elements.get(1).unwrap())
.expect("Failed to parse Password"),
uid: Uid::try_from(*elements.get(2).unwrap()).expect("Failed to parse uid"),
gid: Gid::try_from(*elements.get(3).unwrap()).expect("Failed to parse gid"),
gecos: Gecos::try_from(*elements.get(4).unwrap())
.expect("Failed to parse Gecos field"),
home_dir: HomeDir::try_from(*elements.get(5).unwrap())
.expect("Failed to parse home directory"),
shell_dir: ShellDir::try_from(*elements.get(6).unwrap())
.expect("Failed to parse shell directory"),
username: Username::try_from(*elements.get(0).unwrap())?,
password: Password::try_from(*elements.get(1).unwrap())?,
uid: Uid::try_from(*elements.get(2).unwrap())?,
gid: Gid::try_from(*elements.get(3).unwrap())?,
gecos: Gecos::try_from(*elements.get(4).unwrap())?,
home_dir: HomeDir::try_from(*elements.get(5).unwrap())?,
shell_dir: ShellDir::try_from(*elements.get(6).unwrap())?,
})
} else {
Err("Failed to parse: not enough elements")
Err("Failed to parse: not enough elements".into())
}
}
#[must_use]
pub const fn get_username(&self) -> &'a str {
self.username.username
}
#[must_use]
pub const fn get_password(&self) -> &'a str {
self.password.password
}
#[must_use]
pub const fn get_uid(&self) -> u32 {
self.uid.uid
}
#[must_use]
pub const fn get_gid(&self) -> u32 {
self.gid.gid
}
#[must_use]
pub const fn get_comment(&self) -> &Gecos {
&self.gecos
}
#[must_use]
pub const fn get_home_dir(&self) -> &'a str {
self.home_dir.dir
}
#[must_use]
pub const fn get_shell_dir(&self) -> &'a str {
self.shell_dir.shell
}
}
impl<'a> Gecos<'a> {
#[must_use]
pub const fn get_comment(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { comment, .. } => Some(comment),
Gecos::Detail { .. } => None,
}
}
#[must_use]
pub const fn get_full_name(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { full_name, .. } => Some(full_name),
}
}
#[must_use]
pub const fn get_room(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { room, .. } => Some(room),
}
}
#[must_use]
pub const fn get_phone_work(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_work, .. } => Some(phone_work),
}
}
#[must_use]
pub const fn get_phone_home(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_home, .. } => Some(phone_home),
}
}
#[must_use]
pub const fn get_other(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { other, .. } => Some(other),
}
}
}
@ -140,7 +221,7 @@ impl Display for Username<'_> {
}
impl<'a> TryFrom<&'a str> for Username<'a> {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
Ok(Self { username: source })
}
@ -153,7 +234,7 @@ impl Display for Password<'_> {
}
impl<'a> TryFrom<&'a str> for Password<'a> {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
Ok(Self { password: source })
}
@ -166,7 +247,7 @@ impl Display for Uid {
}
impl TryFrom<&str> for Uid {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> {
Ok(Self {
uid: source.parse::<u32>().unwrap(),
@ -181,7 +262,7 @@ impl Display for Gid {
}
impl TryFrom<&str> for Gid {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> {
Ok(Self {
gid: source.parse::<u32>().unwrap(),
@ -209,7 +290,7 @@ impl Display for Gecos<'_> {
}
impl<'a> TryFrom<&'a str> for Gecos<'a> {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
let vals: Vec<&str> = source.split(',').collect();
if vals.len() == 5 {
@ -228,8 +309,7 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
phone_home: vals.get(3).unwrap(),
other: "",
})
}
else if vals.len() == 1 {
} else if vals.len() == 1 {
Ok(Gecos::Simple {
comment: vals.get(0).unwrap(),
})
@ -246,7 +326,7 @@ impl Display for HomeDir<'_> {
}
impl<'a> TryFrom<&'a str> for HomeDir<'a> {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
Ok(Self { dir: source })
}
@ -259,7 +339,7 @@ impl Display for ShellDir<'_> {
}
impl<'a> TryFrom<&'a str> for ShellDir<'a> {
type Error = &'static str;
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
Ok(Self { shell: source })
}
@ -279,11 +359,11 @@ fn test_default_user() {
#[test]
fn test_parse_gecos() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
let gcd = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcs = "A böring comment →";
let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcsimple = "A böring comment →";
let gc_no_other: &str = "systemd Network Management,,,";
let res_detail = Gecos::try_from(gcd).unwrap();
let res_simple = Gecos::try_from(gcs).unwrap();
let res_detail = Gecos::try_from(gcdetail).unwrap();
let res_simple = Gecos::try_from(gcsimple).unwrap();
let res_no_other = Gecos::try_from(gc_no_other).unwrap();
match res_simple {
Gecos::Simple { comment } => assert_eq!(comment, "A böring comment →"),
@ -326,6 +406,11 @@ fn test_parse_gecos() {
#[test]
fn test_new_from_string() {
// Test if a single line can be parsed and if the resulting struct is populated correctly.
let fail = Passwd::new_from_string("").err().unwrap();
assert_eq!(
fail,
UserLibError::Message("Failed to parse: not enough elements".into())
);
let pwd =
Passwd::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test")
.unwrap();
@ -369,8 +454,8 @@ fn test_parse_passwd() {
let lineorig: String = line.unwrap();
assert_eq!(
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
format!("{}", Passwd::new_from_string(&lineorig.clone()).unwrap()).replace(",",""),
lineorig.replace(",","")
format!("{}", Passwd::new_from_string(&lineorig.clone()).unwrap()).replace(",", ""),
lineorig.replace(",", "")
);
}
}

46
src/userlib_error.rs Normal file
View File

@ -0,0 +1,46 @@
use std::error::Error;
use std::fmt::{self, Display};
#[derive(Debug, PartialEq)]
pub enum ParseError {
Username,
Password,
Uid,
Gid,
Gecos,
HomeDir,
ShellDir,
}
#[derive(Debug, PartialEq)]
pub enum UserLibError {
NotFound,
ParseError,
Message(String),
}
impl Display for UserLibError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NotFound => write!(f, ""),
Self::ParseError => write!(f, "Failed to parse"), // TODO details
Self::Message(message) => write!(f, "{}", message),
}
}
}
impl Error for UserLibError {
fn description(&self) -> &str {
match self {
Self::NotFound => "not found",
Self::ParseError => "failed to parse",
Self::Message(message) => message,
}
}
}
impl From<&str> for UserLibError {
fn from(err: &str) -> Self {
Self::Message(err.to_owned())
}
}