1use serde::Deserialize;
2
3use crate::{
4 error::PlatformError,
5 platform::{Release, ReleaseAsset, ReleasePlatform},
6};
7
8pub struct Github;
9impl ReleasePlatform for Github {
10 const API_BASE_PRIMARY: &'static str = "https://api.github.com";
11
12 const API_BASE_PKGFORGE: &'static str = "https://api.gh.pkgforge.dev";
13
14 const TOKEN_ENV_VAR: &'static str = "GITHUB_TOKEN";
15
16 fn format_project_path(project: &str) -> Result<(String, String), PlatformError> {
17 match project.split_once('/') {
18 Some((owner, repo)) if !owner.trim().is_empty() && !repo.trim().is_empty() => {
19 Ok((owner.to_string(), repo.to_string()))
20 }
21 _ => Err(PlatformError::InvalidInput(format!(
22 "Github project '{}' must be in 'owner/repo' format",
23 project
24 ))),
25 }
26 }
27
28 fn format_api_path(project: &str, tag: Option<&str>) -> Result<String, PlatformError> {
29 let (owner, repo) = Self::format_project_path(project)?;
30 if let Some(tag) = tag {
31 Ok(format!(
32 "/repos/{}/{}/releases/tags/{}?per_page=100",
33 owner, repo, tag
34 ))
35 } else {
36 Ok(format!("/repos/{}/{}/releases?per_page=100", owner, repo))
37 }
38 }
39}
40
41#[derive(Debug, Deserialize)]
42pub struct GithubRelease {
43 name: Option<String>,
44 tag_name: String,
45 prerelease: bool,
46 published_at: String,
47 assets: Vec<GithubAsset>,
48}
49
50impl Release<GithubAsset> for GithubRelease {
51 fn name(&self) -> &str {
52 self.name.as_deref().unwrap_or("")
53 }
54
55 fn tag_name(&self) -> &str {
56 &self.tag_name
57 }
58
59 fn is_prerelease(&self) -> bool {
60 self.prerelease
61 }
62
63 fn published_at(&self) -> &str {
64 &self.published_at
65 }
66
67 fn assets(&self) -> Vec<GithubAsset> {
68 self.assets.clone()
69 }
70}
71
72#[derive(Clone, Debug, Deserialize)]
73pub struct GithubAsset {
74 pub name: String,
75 pub size: u64,
76 pub browser_download_url: String,
77}
78
79impl ReleaseAsset for GithubAsset {
80 fn name(&self) -> &str {
81 &self.name
82 }
83
84 fn size(&self) -> Option<u64> {
85 Some(self.size)
86 }
87
88 fn download_url(&self) -> &str {
89 &self.browser_download_url
90 }
91}