1use crate::error::{RepoNameError, RepoOwnerError};
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::path::PathBuf;
10use url::Url;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct RepoOwner(String);
18
19impl RepoOwner {
20 pub fn new(owner: impl AsRef<str>) -> Result<Self, RepoOwnerError> {
26 let owner = owner.as_ref();
27
28 if owner.is_empty() {
29 return Err(RepoOwnerError::Empty);
30 }
31
32 if owner.len() > 39 {
33 return Err(RepoOwnerError::TooLong { len: owner.len() });
34 }
35
36 if !owner.chars().all(|c| c.is_alphanumeric() || c == '-') {
38 return Err(RepoOwnerError::InvalidCharacters {
39 owner: owner.to_string(),
40 });
41 }
42
43 Ok(Self(owner.to_string()))
44 }
45
46 #[must_use]
48 pub fn as_str(&self) -> &str {
49 &self.0
50 }
51
52 #[must_use]
54 pub fn into_string(self) -> String {
55 self.0
56 }
57}
58
59impl fmt::Display for RepoOwner {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 write!(f, "{}", self.0)
62 }
63}
64
65impl AsRef<str> for RepoOwner {
66 fn as_ref(&self) -> &str {
67 &self.0
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
76pub struct RepoName(String);
77
78impl RepoName {
79 pub fn new(name: impl AsRef<str>) -> Result<Self, RepoNameError> {
85 let name = name.as_ref();
86
87 if name.is_empty() {
88 return Err(RepoNameError::Empty);
89 }
90
91 if name.len() > 100 {
92 return Err(RepoNameError::TooLong { len: name.len() });
93 }
94
95 if !name
97 .chars()
98 .all(|c| c.is_alphanumeric() || "-_.".contains(c))
99 {
100 return Err(RepoNameError::InvalidCharacters {
101 name: name.to_string(),
102 });
103 }
104
105 Ok(Self(name.to_string()))
106 }
107
108 #[must_use]
110 pub fn as_str(&self) -> &str {
111 &self.0
112 }
113
114 #[must_use]
116 pub fn into_string(self) -> String {
117 self.0
118 }
119}
120
121impl fmt::Display for RepoName {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{}", self.0)
124 }
125}
126
127impl AsRef<str> for RepoName {
128 fn as_ref(&self) -> &str {
129 &self.0
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub struct FileName(String);
136
137impl FileName {
138 pub fn new(name: impl Into<String>) -> Self {
140 Self(name.into())
141 }
142
143 #[must_use]
145 pub fn as_str(&self) -> &str {
146 &self.0
147 }
148
149 #[must_use]
151 pub fn into_string(self) -> String {
152 self.0
153 }
154}
155
156impl fmt::Display for FileName {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "{}", self.0)
159 }
160}
161
162impl From<String> for FileName {
163 fn from(name: String) -> Self {
164 Self(name)
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub struct FilePath(PathBuf);
171
172impl FilePath {
173 pub fn new(path: impl Into<PathBuf>) -> Self {
175 Self(path.into())
176 }
177
178 #[must_use]
180 pub fn as_path(&self) -> &std::path::Path {
181 &self.0
182 }
183
184 #[must_use]
186 pub fn into_path_buf(self) -> PathBuf {
187 self.0
188 }
189
190 #[must_use]
192 pub fn as_string_lossy(&self) -> std::borrow::Cow<str> {
193 self.0.to_string_lossy()
194 }
195}
196
197impl fmt::Display for FilePath {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "{}", self.0.display())
200 }
201}
202
203impl From<PathBuf> for FilePath {
204 fn from(path: PathBuf) -> Self {
205 Self(path)
206 }
207}
208
209impl From<&str> for FilePath {
210 fn from(path: &str) -> Self {
211 Self(PathBuf::from(path))
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
217pub struct DownloadUrl(Url);
218
219impl DownloadUrl {
220 #[must_use]
222 pub fn new(url: Url) -> Self {
223 Self(url)
224 }
225
226 pub fn parse(url: impl AsRef<str>) -> Result<Self, url::ParseError> {
232 Ok(Self(Url::parse(url.as_ref())?))
233 }
234
235 #[must_use]
237 pub fn as_url(&self) -> &Url {
238 &self.0
239 }
240
241 #[must_use]
243 pub fn into_url(self) -> Url {
244 self.0
245 }
246
247 #[must_use]
249 pub fn as_str(&self) -> &str {
250 self.0.as_str()
251 }
252}
253
254impl fmt::Display for DownloadUrl {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 write!(f, "{}", self.0)
257 }
258}
259
260impl From<Url> for DownloadUrl {
261 fn from(url: Url) -> Self {
262 Self(url)
263 }
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
268pub struct FileSizeBytes(u64);
269
270impl FileSizeBytes {
271 #[must_use]
273 pub fn new(bytes: u64) -> Self {
274 Self(bytes)
275 }
276
277 #[must_use]
279 pub fn bytes(&self) -> u64 {
280 self.0
281 }
282
283 #[must_use]
285 pub fn human_readable(&self) -> String {
286 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
287 #[allow(clippy::cast_precision_loss)]
288 let mut size = self.0 as f64;
289 let mut unit_index = 0;
290
291 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
292 size /= 1024.0;
293 unit_index += 1;
294 }
295
296 if unit_index == 0 {
297 format!("{} {}", self.0, UNITS[unit_index])
298 } else {
299 format!("{:.1} {}", size, UNITS[unit_index])
300 }
301 }
302}
303
304impl fmt::Display for FileSizeBytes {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 write!(f, "{}", self.human_readable())
307 }
308}
309
310impl From<u64> for FileSizeBytes {
311 fn from(bytes: u64) -> Self {
312 Self(bytes)
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
318pub struct DocsDirectory(String);
319
320impl DocsDirectory {
321 pub fn new(path: impl Into<String>) -> Self {
323 Self(path.into())
324 }
325
326 #[must_use]
328 pub fn as_str(&self) -> &str {
329 &self.0
330 }
331
332 #[must_use]
334 pub fn into_string(self) -> String {
335 self.0
336 }
337}
338
339impl fmt::Display for DocsDirectory {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 write!(f, "{}", self.0)
342 }
343}
344
345impl From<String> for DocsDirectory {
346 fn from(path: String) -> Self {
347 Self(path)
348 }
349}
350
351impl AsRef<str> for DocsDirectory {
352 fn as_ref(&self) -> &str {
353 &self.0
354 }
355}
356
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct DocumentationFile {
360 pub name: FileName,
362 pub path: FilePath,
364 pub download_url: DownloadUrl,
366 pub size: FileSizeBytes,
368 pub docs_directory: DocsDirectory,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Hash)]
374pub struct RepoSpec {
375 pub owner: RepoOwner,
377 pub name: RepoName,
379}
380
381impl RepoSpec {
382 #[must_use]
384 pub fn new(owner: RepoOwner, name: RepoName) -> Self {
385 Self { owner, name }
386 }
387
388 #[must_use]
390 pub fn full_name(&self) -> String {
391 format!("{}/{}", self.owner, self.name)
392 }
393}
394
395impl fmt::Display for RepoSpec {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 write!(f, "{}/{}", self.owner, self.name)
398 }
399}