I'm an absolute beginner, thus forgive me any beginner mistakes.
Since I do want to learn, I prefer to write my own code instead of using existing crates.
I have created a simple struct which represents a version (semver).
Thus, it contains the 'major', 'minor' and 'patch' fields.
#[derive(Debug, PartialEq)]
pub struct Version {
major: u8,
minor: u8,
patch: u8,
}
The goal of the function which I have created and for which I'm seeking a Code Review is a function which allows a &str
instance to be converted into a Version
instance.
Off course, it must fail when the string does NOT matches the pattern major.minor.patch
template.
Therefore, I have created a Trait which can perform this check and do the conversion:
trait SplitIntoWithSize<T> {
fn split_into(self, char: char, max_len: usize) -> Result<Vec<T>, ()>;
}
And here's the implementation on the &str
type:
impl SplitIntoWithSize<u8> for &str {
fn split_into(self, char: char, len: usize) -> Result<Vec<u8>, ()> {
let mut retval = vec![0; len];
let parts = self.split(char).collect::<Vec<Self>>();
if parts.len() != len {
return Err(());
}
for (idx, elem) in parts.iter().enumerate() {
match elem.parse::<u8>() {
Ok(result) => retval[idx] = result,
Err(_) => return Err(()),
};
}
Ok(retval)
}
}
I then implemented the TryFrom
trait on a &str
to actually perform this conversion:
impl TryFrom<&str> for Version {
type Error = ();
fn try_from(input: &str) -> Result<Version, Self::Error> {
match input.split_into_u8('.', 3) {
Ok(parts) => Ok(Version::new(parts[0], parts[1], parts[2])),
Err(_) => Err(()),
}
}
}
And off course we we have the unit tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_version() {
struct UTCase {
major: u8,
minor: u8,
patch: u8,
}
[
UTCase {
major: 1,
minor: 2,
patch: 3,
},
UTCase {
major: 3,
minor: 4,
patch: 5,
},
]
.iter()
.for_each(|case| {
let version = Version::new(case.major, case.minor, case.patch);
assert_eq!(version.major, case.major);
assert_eq!(version.minor, case.minor);
assert_eq!(version.patch, case.patch);
});
}
#[test]
fn version_from_valid_string() {
struct UTCase<'a> {
input: &'a str,
version: Version,
}
[
UTCase {
input: "0.0.0",
version: Version::new(0, 0, 0),
},
UTCase {
input: "1.2.3",
version: Version::new(1, 2, 3),
},
]
.iter()
.for_each(|case| {
let version = Version::try_from(case.input);
assert_eq!(true, version.is_ok());
assert_eq!(version.unwrap(), case.version);
});
}
#[test]
fn version_from_invalid_string() {
struct UTCase<'a> {
input: &'a str,
}
[
UTCase { input: "0.0.0.0" },
UTCase { input: "0" },
UTCase { input: "256.0.0" },
]
.iter()
.for_each(|case| {
let version = Version::try_from(case.input);
assert_eq!(true, version.is_err());
});
}
}
What's your opinion based on the code above?
Things which I should have done in another way?