1use kcode_k1_transaction_id::TxId;
2use semver::Version;
3use std::fmt::{Display, Formatter};
4
5mod source;
6pub use source::{SourceFile, SourcePackage, WebDependency};
7
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct AuthorityId(TxId);
10
11impl AuthorityId {
12 pub const fn new(transaction_id: TxId) -> Self {
13 Self(transaction_id)
14 }
15
16 pub const fn transaction_id(&self) -> &TxId {
17 &self.0
18 }
19}
20
21impl Display for AuthorityId {
22 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
23 Display::fmt(&self.0, formatter)
24 }
25}
26
27#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28pub struct WebFamily(AuthorityId, String);
29
30impl WebFamily {
31 pub fn new(
32 authority: AuthorityId,
33 logical_name: impl Into<String>,
34 ) -> Result<Self, PackageError> {
35 let logical_name = logical_name.into();
36 validate_logical_name(&logical_name)?;
37 Ok(Self(authority, logical_name))
38 }
39
40 pub const fn authority(&self) -> AuthorityId {
41 self.0
42 }
43
44 pub fn logical_name(&self) -> &str {
45 &self.1
46 }
47}
48
49#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
50pub struct WebId(WebFamily, Version);
51
52impl WebId {
53 pub fn new(family: WebFamily, version: Version) -> Result<Self, PackageError> {
54 validate_stable(&version)?;
55 Ok(Self(family, version))
56 }
57
58 pub fn family(&self) -> &WebFamily {
59 &self.0
60 }
61
62 pub fn version(&self) -> &Version {
63 &self.1
64 }
65}
66
67#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68enum SelectorKind {
69 Any,
70 Major(u64),
71 MajorMinor(u64, u64),
72 Exact(u64, u64, u64),
73}
74
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub struct DependencySelector(SelectorKind);
77
78impl DependencySelector {
79 pub fn parse(text: &str) -> Result<Self, PackageError> {
80 if text == "*" {
81 return Ok(Self(SelectorKind::Any));
82 }
83 let numbers = text
84 .split('.')
85 .map(parse_selector_number)
86 .collect::<Result<Vec<_>, _>>()?;
87 let kind = match numbers.as_slice() {
88 [major] => SelectorKind::Major(*major),
89 [major, minor] => SelectorKind::MajorMinor(*major, *minor),
90 [major, minor, patch] => SelectorKind::Exact(*major, *minor, *patch),
91 _ => return fail("dependency selector must contain one to three numbers or *"),
92 };
93 Ok(Self(kind))
94 }
95
96 pub fn matches(&self, version: &Version) -> bool {
97 if !version.pre.is_empty() || !version.build.is_empty() {
98 return false;
99 }
100 match self.0 {
101 SelectorKind::Any => true,
102 SelectorKind::Major(major) => version.major == major,
103 SelectorKind::MajorMinor(major, minor) => {
104 version.major == major && version.minor == minor
105 }
106 SelectorKind::Exact(major, minor, patch) => {
107 version.major == major && version.minor == minor && version.patch == patch
108 }
109 }
110 }
111}
112
113impl Display for DependencySelector {
114 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
115 match self.0 {
116 SelectorKind::Any => formatter.write_str("*"),
117 SelectorKind::Major(major) => write!(formatter, "{major}"),
118 SelectorKind::MajorMinor(major, minor) => write!(formatter, "{major}.{minor}"),
119 SelectorKind::Exact(major, minor, patch) => {
120 write!(formatter, "{major}.{minor}.{patch}")
121 }
122 }
123 }
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct PackageError(String);
128
129impl PackageError {
130 pub fn message(&self) -> &str {
131 &self.0
132 }
133}
134
135impl Display for PackageError {
136 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
137 formatter.write_str(&self.0)
138 }
139}
140
141impl std::error::Error for PackageError {}
142
143pub(crate) fn fail<T>(message: impl Into<String>) -> Result<T, PackageError> {
144 Err(PackageError(message.into()))
145}
146
147pub(crate) fn validate_logical_name(name: &str) -> Result<(), PackageError> {
148 let valid = !name.is_empty()
149 && name.len() <= 36
150 && name.split('-').all(|part| {
151 !part.is_empty()
152 && part
153 .bytes()
154 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
155 });
156 if !valid {
157 return fail("logical name must be 1-36 bytes of lowercase kebab case");
158 }
159 Ok(())
160}
161
162pub(crate) fn validate_stable(version: &Version) -> Result<(), PackageError> {
163 if !version.pre.is_empty() || !version.build.is_empty() {
164 return fail("version must be stable SemVer without prerelease or build metadata");
165 }
166 Ok(())
167}
168
169fn parse_selector_number(text: &str) -> Result<u64, PackageError> {
170 let canonical = !text.is_empty()
171 && !(text.len() > 1 && text.starts_with('0'))
172 && text.bytes().all(|byte| byte.is_ascii_digit());
173 if !canonical {
174 return fail("dependency selector numbers must use canonical decimal spelling");
175 }
176 text.parse()
177 .map_err(|_| PackageError("dependency selector number is too large".into()))
178}