ephemeris_forge_forgejo/api/http/
release.rs1use 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 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}