Skip to main content

ephemeris_forge_forgejo/api/http/
release.rs

1use ephemeris_forge::{
2    AssetWire, DraftRelease, PublishRelease, PullRequestCreate, ReleaseWire, RepoRef, encode,
3    path_segment, query_value,
4};
5use reqwest::Method;
6use reqwest::multipart::{Form, Part};
7
8use crate::api::model::{GitRef, Pull};
9use crate::api::{ForgeError, HttpApi};
10
11impl HttpApi {
12    /// The commit `tag` points at, with an annotated tag peeled.
13    pub(crate) async fn tag_commit(
14        &self,
15        repository: &RepoRef,
16        tag: &str,
17    ) -> Result<Option<String>, ForgeError> {
18        let wanted = format!("refs/tags/{tag}");
19        let tag = path_segment(tag);
20        let path = repository.path(&format!("git/refs/tags/{tag}"));
21        let object = self
22            .http
23            .json_optional::<Vec<GitRef>>(Method::GET, &path, None)
24            .await?
25            .and_then(|references| {
26                references
27                    .into_iter()
28                    .find(|reference| reference.name == wanted)
29                    .map(|reference| reference.object)
30            });
31        let Some(object) = object else {
32            return Ok(None);
33        };
34        Ok(Some(object.peel(&self.http, repository).await?))
35    }
36
37    pub(crate) async fn release_by_tag(
38        &self,
39        repository: &RepoRef,
40        tag: &str,
41    ) -> Result<Option<ReleaseWire>, ForgeError> {
42        Ok(ReleaseWire::by_tag(&self.http, repository, tag).await?)
43    }
44
45    pub(crate) async fn create_draft_release(
46        &self,
47        repository: &RepoRef,
48        tag: &str,
49        target_commit: &str,
50        title: &str,
51        notes: &str,
52    ) -> Result<u64, ForgeError> {
53        let draft = DraftRelease::new(tag, target_commit, title, notes);
54        Ok(draft.create(&self.http, repository).await?)
55    }
56
57    pub(crate) async fn release_assets(
58        &self,
59        repository: &RepoRef,
60        release_id: u64,
61    ) -> Result<Vec<AssetWire>, ForgeError> {
62        let path = repository.path(&format!("releases/{release_id}/assets"));
63        Ok(self.http.json(Method::GET, &path, None).await?)
64    }
65
66    pub(crate) async fn upload_asset(
67        &self,
68        repository: &RepoRef,
69        release_id: u64,
70        filename: &str,
71        bytes: Vec<u8>,
72    ) -> Result<(), ForgeError> {
73        let part = Part::bytes(bytes).file_name(filename.to_owned());
74        let form = Form::new().part("attachment", part);
75        let name = query_value(filename);
76        let path = repository.path(&format!("releases/{release_id}/assets?name={name}"));
77        self.http.send_multipart(Method::POST, &path, form).await?;
78        Ok(())
79    }
80
81    pub(crate) async fn delete_asset(
82        &self,
83        repository: &RepoRef,
84        release_id: u64,
85        asset_id: u64,
86    ) -> Result<(), ForgeError> {
87        let path = repository.path(&format!("releases/{release_id}/assets/{asset_id}"));
88        self.http.send(Method::DELETE, &path, None).await?;
89        Ok(())
90    }
91
92    pub(crate) async fn publish_release(
93        &self,
94        repository: &RepoRef,
95        release_id: u64,
96    ) -> Result<(), ForgeError> {
97        let path = repository.path(&format!("releases/{release_id}"));
98        let body = encode(&PublishRelease { draft: false });
99        self.http.send(Method::PATCH, &path, Some(&body)).await?;
100        Ok(())
101    }
102
103    pub(crate) async fn open_pull_request(
104        &self,
105        repository: &RepoRef,
106        head: &str,
107    ) -> Result<Option<Pull>, ForgeError> {
108        let branch = query_value(head);
109        let path = repository.path(&format!("pulls?state=open&head={branch}"));
110        let pulls: Vec<Pull> = self.http.json(Method::GET, &path, None).await?;
111        Ok(pulls.into_iter().find(|pull| pull.head.branch == head))
112    }
113
114    pub(crate) async fn ensure_pull_request(
115        &self,
116        repository: &RepoRef,
117        head: &str,
118        base: &str,
119        title: &str,
120        body: &str,
121    ) -> Result<(), ForgeError> {
122        let current = self.open_pull_request(repository, head).await?;
123        PullRequestCreate {
124            title,
125            body,
126            head,
127            base,
128        }
129        .ensure(&self.http, repository, current.map(|pull| pull.number))
130        .await?;
131        Ok(())
132    }
133}