#[derive(Debug, thiserror::Error)]
pub enum PipePathError {
#[error("invalid name {name:?}: {reason}")]
InvalidName {
name: String,
reason: &'static str,
},
#[error("derived path exceeds {limit_label} ({len} > {max})")]
PathTooLong {
len: usize,
max: usize,
limit_label: &'static str,
},
#[cfg(feature = "client")]
#[error(transparent)]
Sid(#[from] crate::broker::lifecycle::sid::SidError),
}
pub fn validate_service_name(name: &str) -> Result<(), PipePathError> {
if name.is_empty() {
return Err(PipePathError::InvalidName {
name: name.into(),
reason: "service name must be at least 1 character",
});
}
if name.len() > 64 {
return Err(PipePathError::InvalidName {
name: name.into(),
reason: "service name must be 64 characters or fewer",
});
}
for character in name.chars() {
match character {
'a'..='z' | '0'..='9' | '-' => {}
'A'..='Z' => {
return Err(PipePathError::InvalidName {
name: name.into(),
reason: "uppercase letters are forbidden (case-only \
collisions with lowercase names would silently \
merge under Windows named-pipe semantics)",
});
}
_ => {
return Err(PipePathError::InvalidName {
name: name.into(),
reason: "only lowercase ASCII letters, digits, and '-' allowed",
});
}
}
}
Ok(())
}
#[cfg(feature = "daemon-registration")]
pub fn validate_version(version: &str) -> Result<(), PipePathError> {
if version.is_empty() {
return Err(PipePathError::InvalidName {
name: version.into(),
reason: "version must not be empty",
});
}
let (core, prerelease) = match version.split_once('-') {
Some((core, tail)) => (core, Some(tail)),
None => (version, None),
};
let parts: Vec<&str> = core.split('.').collect();
if parts.len() != 3 {
return Err(PipePathError::InvalidName {
name: version.into(),
reason: "version core must be MAJOR.MINOR.PATCH",
});
}
for part in &parts {
if part.is_empty() || !part.chars().all(|character| character.is_ascii_digit()) {
return Err(PipePathError::InvalidName {
name: version.into(),
reason: "MAJOR/MINOR/PATCH must be non-empty digits",
});
}
}
if let Some(tail) = prerelease {
if tail.is_empty() {
return Err(PipePathError::InvalidName {
name: version.into(),
reason: "pre-release suffix after '-' must not be empty",
});
}
for character in tail.chars() {
match character {
'a'..='z' | '0'..='9' | '.' => {}
_ => {
return Err(PipePathError::InvalidName {
name: version.into(),
reason: "pre-release tail allows only [a-z0-9.]",
});
}
}
}
}
Ok(())
}