m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-08 05:03:49 -05:00
This commit is contained in:
Sergio
2025-08-13 11:59:48 +03:00
parent 7a4345076f
commit 15ffe1a258
16 changed files with 1717 additions and 56 deletions

1343
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,5 +6,6 @@ edition = "2024"
[lib] [lib]
[dependencies] [dependencies]
bollard = "0.19.2"
regex = "1.11.1" regex = "1.11.1"
rustc-hash = "2.1.1" rustc-hash = "2.1.1"

51
cup/src/auth.rs Normal file
View File

@@ -0,0 +1,51 @@
use std::{fmt::Display, str::FromStr};
pub struct AuthToken {
token_type: TokenType,
value: String,
}
enum TokenType {
Basic,
Bearer,
}
impl Display for TokenType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Basic => "Basic",
Self::Bearer => "Bearer",
}
)
}
}
impl AuthToken {
pub fn from_basic(token: &str) -> Self {
Self {
token_type: TokenType::Basic,
value: token.to_string()
}
}
pub fn from_bearer(token: &str) -> Self {
Self {
token_type: TokenType::Bearer,
value: token.to_string()
}
}
pub fn get_type(&self) -> &'static str {
match &self.token_type {
TokenType::Basic => "Basic",
TokenType::Bearer => "Bearer"
}
}
pub fn get_value(&self) -> &str {
&self.value
}
}

10
cup/src/docker.rs Normal file
View File

@@ -0,0 +1,10 @@
use crate::socket::Socket;
/// Wraps a Docker client and provides useful functions get data from it.
pub struct Docker {
socket: Socket,
client: bollard::Docker
}
impl Docker {
}

13
cup/src/image_config.rs Normal file
View File

@@ -0,0 +1,13 @@
use crate::version::{VersionType, standard::StandardUpdateType};
pub struct ImageConfig {
version_type: VersionType,
// Will only be read if update type is standard
ignored_update_types: Vec<StandardUpdateType>,
/// Whether to skip checking the image (default is false)
ignore: bool
}
impl ImageConfig {
}

View File

@@ -0,0 +1,3 @@
pub struct ImageToCheck {
}

View File

@@ -1 +1,110 @@
pub mod version; use rustc_hash::FxHashMap;
use crate::{
auth::AuthToken, image_config::ImageConfig, image_to_check::ImageToCheck, reference::Reference,
reference_matcher::ReferenceMatcher, registry::Registry, registry_config::RegistryConfig,
socket::Socket,
};
pub mod auth;
pub mod image_config;
pub mod image_to_check;
pub mod reference;
pub mod reference_matcher;
pub mod registry;
pub mod registry_config;
pub mod socket;
pub mod version;
pub mod docker;
/// This struct is the main interface to Cup's functions.
/// Any useful user-provided or generated data is stored here for reuse during the lifetime of the struct.
#[derive(Default)]
pub struct Cup {
images: Vec<ImageToCheck>,
socket: Socket,
}
impl<'a> Cup {
/// Returns a new empty instance with all values initialized to their defaults.
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> CupBuilder {
CupBuilder::default()
}
/// Check for updates to the images provided (and any images configured through options)
pub async fn check(images: &[&Reference]) -> Vec<Result<CheckResult, CheckError>> {
todo!()
}
}
#[derive(Default)]
pub struct CupBuilder {
/// Whether or not to check for updates to local images in addition to the other images.
check_local_images: bool,
/// Whether or not to check for updates to images used in the swarm in addition to the other images. If the host is not a manager node this will only check the locally running swarm images.
check_swarm_images: bool,
registry_config: FxHashMap<Registry, RegistryConfig>,
overrides: FxHashMap<ReferenceMatcher, ImageConfig>,
socket: Socket,
}
impl CupBuilder {
pub fn with_local_images(&mut self) {
self.check_local_images = true
}
pub fn with_swarm_images(&mut self) {
self.check_swarm_images = true
}
pub fn with_registry_auth(&mut self, registry: Registry, token: AuthToken) {
match self.registry_config.get_mut(&registry) {
Some(registry_config) => registry_config.auth = Some(token),
None => {
self.registry_config.insert(
registry,
RegistryConfig {
auth: Some(token),
..Default::default()
},
);
}
}
}
pub fn with_insecure_registry(&mut self, registry: Registry) {
match self.registry_config.get_mut(&registry) {
Some(registry_config) => registry_config.insecure = true,
None => {
self.registry_config.insert(
registry,
RegistryConfig {
insecure: true,
..Default::default()
},
);
}
}
}
pub fn with_image_config(&mut self, reference: ReferenceMatcher, config: ImageConfig) {
self.overrides.insert(reference, config);
}
pub fn with_socket(&mut self, socket: Socket) {
self.socket = socket
}
pub fn build(&self) -> Cup {
todo!()
}
}
pub struct CheckResult {}
pub struct CheckError {}

5
cup/src/reference.rs Normal file
View File

@@ -0,0 +1,5 @@
pub struct Reference(String);
impl Reference {
}

View File

@@ -0,0 +1,38 @@
use regex::Regex;
use std::hash::Hash;
/// When customizing image configuration, there may be a need to operate on a batch of images. The ReferenceMatcher describes which references the configuration should apply to.
pub enum ReferenceMatcher {
/// The reference is exactly equal to the string provided
ExactMatch(String),
/// The reference starts with the string provided, useful for configuring based on a registry or repository
PrefixMatch(String),
/// The reference ends with the string provided, useful for configuring based on a tag
SuffixMatch(String),
/// The reference contains the string provided
Contains(String),
/// For more complicated matching logic a regular expression can be used.
Custom(Regex),
}
impl PartialEq for ReferenceMatcher {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ReferenceMatcher::Custom(a), ReferenceMatcher::Custom(b)) => a.as_str() == b.as_str(),
(a, b) => a.eq(b),
}
}
}
impl Eq for ReferenceMatcher {}
impl Hash for ReferenceMatcher {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Custom(r) => r.as_str().hash(state),
o => o.hash(state),
}
}
}
impl ReferenceMatcher {}

49
cup/src/registry.rs Normal file
View File

@@ -0,0 +1,49 @@
use std::{error::Error, fmt::Display, str::FromStr, sync::OnceLock};
use regex::Regex;
#[derive(PartialEq, Eq, Hash)]
pub struct Registry {
value: String,
}
/// Regex representing the value of `domainAndPort` from https://github.com/distribution/reference/blob/727f80d42224f6696b8e1ad16b06aadf2c6b833b/regexp.go#L108
/// Used to verify whether a string is a registry hostname
const REGISTRY_REGEX: &str = r"^(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))*|\[(?:[a-fA-F0-9:]+)\])(?::[0-9]+)?$";
static REGISTRY: OnceLock<Regex> = OnceLock::new();
impl FromStr for Registry {
type Err = ParseRegistryError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let regex = REGISTRY.get_or_init(|| Regex::new(REGISTRY_REGEX).unwrap());
if s.is_empty() {
Ok(Self {
value: String::from("registry-1.docker.io"),
})
} else if regex.is_match(s) {
Ok(Self {
value: s.to_string(),
})
} else {
Err(ParseRegistryError {
registry: s.to_string(),
})
}
}
}
#[derive(Debug)]
pub struct ParseRegistryError {
pub registry: String,
}
impl Display for ParseRegistryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}` is not a valid registry hostname", self.registry)
}
}
impl Error for ParseRegistryError {}
// TODO: Add tests

View File

@@ -0,0 +1,9 @@
use crate::auth::AuthToken;
#[derive(Default)]
pub struct RegistryConfig {
/// An optional authentication token to use when making requests to this registry
pub(super) auth: Option<AuthToken>,
/// Whether or not to connect to the registry over unencrypted HTTP
pub(super) insecure: bool
}

7
cup/src/socket.rs Normal file
View File

@@ -0,0 +1,7 @@
#[derive(Default)]
pub enum Socket {
#[default]
Default,
Unix(String),
Tcp(String)
}

View File

@@ -0,0 +1 @@
pub struct DigestVersion {}

View File

@@ -15,10 +15,10 @@ pub struct ExtendedVersion {
} }
impl ExtendedVersion { impl ExtendedVersion {
pub fn parse(version_string: &str, regex: &str) -> Result<Self, VersionParseError> { pub fn parse(version_string: &str, regex: &str) -> Result<Self, ParseVersionError> {
let regex = Regex::new(regex).map_err(|e| VersionParseError { let regex = Regex::new(regex).map_err(|e| ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::InvalidRegex(e), kind: ParseVersionErrorKind::InvalidRegex(e),
})?; })?;
// Check if all capture groups are named or anonymous // Check if all capture groups are named or anonymous
@@ -29,9 +29,9 @@ impl ExtendedVersion {
match prev_state { match prev_state {
None => Ok(Some(name)), // First iteration None => Ok(Some(name)), // First iteration
Some(prev_state) => match (prev_state, name) { Some(prev_state) => match (prev_state, name) {
(Some(_), None) | (None, Some(_)) => Err(VersionParseError { (Some(_), None) | (None, Some(_)) => Err(ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::NonUniformCaptureGroups, kind: ParseVersionErrorKind::NonUniformCaptureGroups,
}), }),
_ => Ok(Some(name)), _ => Ok(Some(name)),
}, },
@@ -51,22 +51,22 @@ impl ExtendedVersion {
.capture_names() .capture_names()
.flatten() .flatten()
.map(|name| { .map(|name| {
let capture = captures.name(name).ok_or_else(|| VersionParseError { let capture = captures.name(name).ok_or_else(|| ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::GroupDidNotMatch(name.to_string()), kind: ParseVersionErrorKind::GroupDidNotMatch(name.to_string()),
})?; })?;
let component = let component =
VersionComponent::from_str(capture.as_str()).map_err(|_| { VersionComponent::from_str(capture.as_str()).map_err(|_| {
VersionParseError { ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::GroupDidNotMatch( kind: ParseVersionErrorKind::GroupDidNotMatch(
name.to_string(), name.to_string(),
), ),
} }
})?; })?;
Ok((name.to_string(), component)) Ok((name.to_string(), component))
}) })
.collect::<Result<FxHashMap<String, VersionComponent>, VersionParseError>>( .collect::<Result<FxHashMap<String, VersionComponent>, ParseVersionError>>(
)? )?
} else { } else {
captures captures
@@ -77,20 +77,20 @@ impl ExtendedVersion {
.map(|(i, m)| { .map(|(i, m)| {
VersionComponent::from_str(m.as_str()) VersionComponent::from_str(m.as_str())
.map(|comp| (i, comp)) .map(|comp| (i, comp))
.map_err(|e| VersionParseError { .map_err(|e| ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::ParseComponent(e), kind: ParseVersionErrorKind::ParseComponent(e),
}) })
}) })
.collect::<Result<FxHashMap<String, VersionComponent>, VersionParseError>>( .collect::<Result<FxHashMap<String, VersionComponent>, ParseVersionError>>(
)? )?
}; };
Self { components } Self { components }
} }
None => { None => {
return Err(VersionParseError { return Err(ParseVersionError {
version_string: version_string.to_string(), version_string: version_string.to_string(),
kind: VersionParseErrorKind::NoMatch, kind: ParseVersionErrorKind::NoMatch,
}); });
} }
}) })
@@ -100,12 +100,12 @@ impl ExtendedVersion {
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive] #[non_exhaustive]
pub struct VersionParseError { pub struct ParseVersionError {
version_string: String, version_string: String,
kind: VersionParseErrorKind, kind: ParseVersionErrorKind,
} }
impl Display for VersionParseError { impl Display for ParseVersionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -115,7 +115,7 @@ impl Display for VersionParseError {
} }
} }
impl Error for VersionParseError { impl Error for ParseVersionError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.kind) Some(&self.kind)
} }
@@ -123,7 +123,7 @@ impl Error for VersionParseError {
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
pub enum VersionParseErrorKind { pub enum ParseVersionErrorKind {
/// The regex supplied could not be compiled /// The regex supplied could not be compiled
InvalidRegex(regex::Error), InvalidRegex(regex::Error),
/// The regex supplied has both named and anonymous capture groups /// The regex supplied has both named and anonymous capture groups
@@ -136,7 +136,7 @@ pub enum VersionParseErrorKind {
ParseComponent(ParseIntError), ParseComponent(ParseIntError),
} }
impl Display for VersionParseErrorKind { impl Display for ParseVersionErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::InvalidRegex(_) => write!(f, "invalid regex"), Self::InvalidRegex(_) => write!(f, "invalid regex"),
@@ -152,7 +152,7 @@ impl Display for VersionParseErrorKind {
} }
} }
impl Error for VersionParseErrorKind { impl Error for ParseVersionErrorKind {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
match self { match self {
Self::InvalidRegex(e) => Some(e), Self::InvalidRegex(e) => Some(e),
@@ -172,8 +172,8 @@ mod tests {
use crate::version::version_component::VersionComponent; use crate::version::version_component::VersionComponent;
use super::ExtendedVersion; use super::ExtendedVersion;
use super::VersionParseError; use super::ParseVersionError;
use super::VersionParseErrorKind; use super::ParseVersionErrorKind;
#[test] #[test]
fn parse_with_anonymous() { fn parse_with_anonymous() {
@@ -274,9 +274,9 @@ mod tests {
"1.0.0-test2", "1.0.0-test2",
r"(\d+)\.(\d+))\.(\d+)-test(\d+)" // Whoops, someone left an extra ) somewhere... r"(\d+)\.(\d+))\.(\d+)-test(\d+)" // Whoops, someone left an extra ) somewhere...
), ),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("1.0.0-test2"), version_string: String::from("1.0.0-test2"),
kind: VersionParseErrorKind::InvalidRegex( kind: ParseVersionErrorKind::InvalidRegex(
Regex::new(r"(\d+)\.(\d+))\.(\d+)-test(\d+)").unwrap_err() Regex::new(r"(\d+)\.(\d+))\.(\d+)-test(\d+)").unwrap_err()
) )
}) })
@@ -287,9 +287,9 @@ mod tests {
fn invalid_component() { fn invalid_component() {
assert_eq!( assert_eq!(
ExtendedVersion::parse("50h+2-3_40", r"([a-z0-9]+)\+(\d+)-(\d+)_(\d+)"), ExtendedVersion::parse("50h+2-3_40", r"([a-z0-9]+)\+(\d+)-(\d+)_(\d+)"),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("50h+2-3_40"), version_string: String::from("50h+2-3_40"),
kind: VersionParseErrorKind::ParseComponent("50h".parse::<usize>().unwrap_err()) kind: ParseVersionErrorKind::ParseComponent("50h".parse::<usize>().unwrap_err())
}) })
) )
} }
@@ -301,9 +301,9 @@ mod tests {
"4.1.2.5", "4.1.2.5",
r"(?<Major>\d+)\.(?<Minor>\d+)\.(?<Patch>\d+)\.(\d+)" r"(?<Major>\d+)\.(?<Minor>\d+)\.(?<Patch>\d+)\.(\d+)"
), ),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("4.1.2.5"), version_string: String::from("4.1.2.5"),
kind: VersionParseErrorKind::NonUniformCaptureGroups kind: ParseVersionErrorKind::NonUniformCaptureGroups
}) })
) )
} }
@@ -315,9 +315,9 @@ mod tests {
"1-sometool-0.2.5-alpine", "1-sometool-0.2.5-alpine",
r"(?:\d+)-somet0ol-(\d+)\.(\d+)\.(\d+)-alpine" r"(?:\d+)-somet0ol-(\d+)\.(\d+)\.(\d+)-alpine"
), ),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("1-sometool-0.2.5-alpine"), version_string: String::from("1-sometool-0.2.5-alpine"),
kind: VersionParseErrorKind::NoMatch kind: ParseVersionErrorKind::NoMatch
}) })
) )
} }
@@ -329,9 +329,9 @@ mod tests {
"2.0.5-alpine", "2.0.5-alpine",
r"(?<Eenie>\d+)\.(?<Meenie>\d+)\.(?<Miney>\d+)|(?<Moe>moe)-alpine" r"(?<Eenie>\d+)\.(?<Meenie>\d+)\.(?<Miney>\d+)|(?<Moe>moe)-alpine"
), ),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("2.0.5-alpine"), version_string: String::from("2.0.5-alpine"),
kind: VersionParseErrorKind::GroupDidNotMatch(String::from("Moe")) kind: ParseVersionErrorKind::GroupDidNotMatch(String::from("Moe"))
}) })
) )
} }

View File

@@ -1,8 +1,10 @@
use crate::version::date::DateVersion; use crate::version::date::DateVersion;
use crate::version::digest::DigestVersion;
use crate::version::extended::ExtendedVersion; use crate::version::extended::ExtendedVersion;
use crate::version::standard::StandardVersion; use crate::version::standard::StandardVersion;
pub mod date; pub mod date;
pub mod digest;
pub mod extended; pub mod extended;
pub mod standard; pub mod standard;
@@ -17,6 +19,17 @@ pub enum Version {
Extended(ExtendedVersion), Extended(ExtendedVersion),
/// The "Date" versioning schema is for images which are versioned based on date and or time and come with their own quirky rules to compare them. Refer to `DateVersion` for more info. /// The "Date" versioning schema is for images which are versioned based on date and or time and come with their own quirky rules to compare them. Refer to `DateVersion` for more info.
Date(DateVersion), Date(DateVersion),
/// Version checking based on local and remote image digests
Digest(DigestVersion)
}
pub enum VersionType {
/// Tries to automatically infer the version type.
Auto,
Standard,
Extended,
Date,
Digest
} }
mod version_component { mod version_component {

View File

@@ -1,5 +1,7 @@
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr}; use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
use rustc_hash::FxHashSet;
use super::version_component::VersionComponent; use super::version_component::VersionComponent;
/// A versioning scheme I'd call SemVer-inspired. The main difference from [SemVer](https://semver.org) is that the minor and patch versions are optional. /// A versioning scheme I'd call SemVer-inspired. The main difference from [SemVer](https://semver.org) is that the minor and patch versions are optional.
@@ -13,28 +15,28 @@ pub struct StandardVersion {
} }
impl FromStr for StandardVersion { impl FromStr for StandardVersion {
type Err = VersionParseError; type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let splits = s.split('.'); let splits = s.split('.');
if splits.clone().any(|split| split.is_empty()) { if splits.clone().any(|split| split.is_empty()) {
return Err(VersionParseError { return Err(ParseVersionError {
version_string: s.to_string(), version_string: s.to_string(),
kind: VersionParseErrorKind::IncorrectFormat, kind: ParseVersionErrorKind::IncorrectFormat,
}); });
} }
let mut component_iter = splits.map(|component| { let mut component_iter = splits.map(|component| {
VersionComponent::from_str(component).map_err(|e| VersionParseError { VersionComponent::from_str(component).map_err(|e| ParseVersionError {
version_string: s.to_string(), version_string: s.to_string(),
kind: VersionParseErrorKind::ParseComponent(e), kind: ParseVersionErrorKind::ParseComponent(e),
}) })
}); });
let major = component_iter.next().transpose()?.unwrap(); let major = component_iter.next().transpose()?.unwrap();
let minor = component_iter.next().transpose()?; let minor = component_iter.next().transpose()?;
let patch = component_iter.next().transpose()?; let patch = component_iter.next().transpose()?;
if component_iter.next().is_some() { if component_iter.next().is_some() {
return Err(VersionParseError { return Err(ParseVersionError {
version_string: s.to_string(), version_string: s.to_string(),
kind: VersionParseErrorKind::TooManyComponents(4 + component_iter.count()), kind: ParseVersionErrorKind::TooManyComponents(4 + component_iter.count()),
}); });
} }
Ok(Self { Ok(Self {
@@ -45,15 +47,22 @@ impl FromStr for StandardVersion {
} }
} }
#[derive(PartialEq, Eq, Hash)]
pub enum StandardUpdateType {
Major,
Minor,
Patch
}
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive] #[non_exhaustive]
pub struct VersionParseError { pub struct ParseVersionError {
pub version_string: String, pub version_string: String,
pub kind: VersionParseErrorKind, pub kind: ParseVersionErrorKind,
} }
impl Display for VersionParseError { impl Display for ParseVersionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -63,7 +72,7 @@ impl Display for VersionParseError {
} }
} }
impl Error for VersionParseError { impl Error for ParseVersionError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.kind) Some(&self.kind)
} }
@@ -71,7 +80,7 @@ impl Error for VersionParseError {
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
pub enum VersionParseErrorKind { pub enum ParseVersionErrorKind {
/// A version component could not be parsed /// A version component could not be parsed
ParseComponent(ParseIntError), ParseComponent(ParseIntError),
/// The version string is not in the format expected by `StandardVersion` /// The version string is not in the format expected by `StandardVersion`
@@ -80,7 +89,7 @@ pub enum VersionParseErrorKind {
TooManyComponents(usize), TooManyComponents(usize),
} }
impl Display for VersionParseErrorKind { impl Display for ParseVersionErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self { match &self {
Self::IncorrectFormat => write!(f, "version string is incorrectly formatted"), Self::IncorrectFormat => write!(f, "version string is incorrectly formatted"),
@@ -94,7 +103,7 @@ impl Display for VersionParseErrorKind {
} }
} }
impl Error for VersionParseErrorKind { impl Error for ParseVersionErrorKind {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self { match &self {
Self::ParseComponent(e) => Some(e), Self::ParseComponent(e) => Some(e),
@@ -108,7 +117,7 @@ impl Error for VersionParseErrorKind {
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;
use super::{StandardVersion, VersionParseError, VersionParseErrorKind}; use super::{StandardVersion, ParseVersionError, ParseVersionErrorKind};
use super::super::version_component::VersionComponent; use super::super::version_component::VersionComponent;
#[test] #[test]
@@ -190,9 +199,9 @@ mod tests {
fn parse_invalid_string() { fn parse_invalid_string() {
assert_eq!( assert_eq!(
StandardVersion::from_str(".1.0"), StandardVersion::from_str(".1.0"),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from(".1.0"), version_string: String::from(".1.0"),
kind: VersionParseErrorKind::IncorrectFormat kind: ParseVersionErrorKind::IncorrectFormat
}) })
) )
} }
@@ -201,9 +210,9 @@ mod tests {
fn parse_invalid_component() { fn parse_invalid_component() {
assert_eq!( assert_eq!(
StandardVersion::from_str("0.1.O"), StandardVersion::from_str("0.1.O"),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("0.1.O"), version_string: String::from("0.1.O"),
kind: VersionParseErrorKind::ParseComponent( kind: ParseVersionErrorKind::ParseComponent(
"O".parse::<u32>().unwrap_err() "O".parse::<u32>().unwrap_err()
) )
}) })
@@ -214,9 +223,9 @@ mod tests {
fn parse_four_components() { fn parse_four_components() {
assert_eq!( assert_eq!(
StandardVersion::from_str("1.2.4.0"), StandardVersion::from_str("1.2.4.0"),
Err(VersionParseError { Err(ParseVersionError {
version_string: String::from("1.2.4.0"), version_string: String::from("1.2.4.0"),
kind: VersionParseErrorKind::TooManyComponents(4) kind: ParseVersionErrorKind::TooManyComponents(4)
}) })
) )
} }