use kcode_k1_transaction_id::TxId;
use semver::Version;
use std::fmt::{Display, Formatter};
mod source;
pub use source::{SourceFile, SourcePackage, WebDependency};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AuthorityId(TxId);
impl AuthorityId {
pub const fn new(transaction_id: TxId) -> Self {
Self(transaction_id)
}
pub const fn transaction_id(&self) -> &TxId {
&self.0
}
}
impl Display for AuthorityId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, formatter)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WebFamily(AuthorityId, String);
impl WebFamily {
pub fn new(
authority: AuthorityId,
logical_name: impl Into<String>,
) -> Result<Self, PackageError> {
let logical_name = logical_name.into();
validate_logical_name(&logical_name)?;
Ok(Self(authority, logical_name))
}
pub const fn authority(&self) -> AuthorityId {
self.0
}
pub fn logical_name(&self) -> &str {
&self.1
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WebId(WebFamily, Version);
impl WebId {
pub fn new(family: WebFamily, version: Version) -> Result<Self, PackageError> {
validate_stable(&version)?;
Ok(Self(family, version))
}
pub fn family(&self) -> &WebFamily {
&self.0
}
pub fn version(&self) -> &Version {
&self.1
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum SelectorKind {
Any,
Major(u64),
MajorMinor(u64, u64),
Exact(u64, u64, u64),
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DependencySelector(SelectorKind);
impl DependencySelector {
pub fn parse(text: &str) -> Result<Self, PackageError> {
if text == "*" {
return Ok(Self(SelectorKind::Any));
}
let numbers = text
.split('.')
.map(parse_selector_number)
.collect::<Result<Vec<_>, _>>()?;
let kind = match numbers.as_slice() {
[major] => SelectorKind::Major(*major),
[major, minor] => SelectorKind::MajorMinor(*major, *minor),
[major, minor, patch] => SelectorKind::Exact(*major, *minor, *patch),
_ => return fail("dependency selector must contain one to three numbers or *"),
};
Ok(Self(kind))
}
pub fn matches(&self, version: &Version) -> bool {
if !version.pre.is_empty() || !version.build.is_empty() {
return false;
}
match self.0 {
SelectorKind::Any => true,
SelectorKind::Major(major) => version.major == major,
SelectorKind::MajorMinor(major, minor) => {
version.major == major && version.minor == minor
}
SelectorKind::Exact(major, minor, patch) => {
version.major == major && version.minor == minor && version.patch == patch
}
}
}
}
impl Display for DependencySelector {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
SelectorKind::Any => formatter.write_str("*"),
SelectorKind::Major(major) => write!(formatter, "{major}"),
SelectorKind::MajorMinor(major, minor) => write!(formatter, "{major}.{minor}"),
SelectorKind::Exact(major, minor, patch) => {
write!(formatter, "{major}.{minor}.{patch}")
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PackageError(String);
impl PackageError {
pub fn message(&self) -> &str {
&self.0
}
}
impl Display for PackageError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
impl std::error::Error for PackageError {}
pub(crate) fn fail<T>(message: impl Into<String>) -> Result<T, PackageError> {
Err(PackageError(message.into()))
}
pub(crate) fn validate_logical_name(name: &str) -> Result<(), PackageError> {
let valid = !name.is_empty()
&& name.len() <= 36
&& name.split('-').all(|part| {
!part.is_empty()
&& part
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
});
if !valid {
return fail("logical name must be 1-36 bytes of lowercase kebab case");
}
Ok(())
}
pub(crate) fn validate_stable(version: &Version) -> Result<(), PackageError> {
if !version.pre.is_empty() || !version.build.is_empty() {
return fail("version must be stable SemVer without prerelease or build metadata");
}
Ok(())
}
fn parse_selector_number(text: &str) -> Result<u64, PackageError> {
let canonical = !text.is_empty()
&& !(text.len() > 1 && text.starts_with('0'))
&& text.bytes().all(|byte| byte.is_ascii_digit());
if !canonical {
return fail("dependency selector numbers must use canonical decimal spelling");
}
text.parse()
.map_err(|_| PackageError("dependency selector number is too large".into()))
}