Skip to content

Commit ab7b915

Browse files
committed
fix: Improve release URL conversion logic
The conversion logic for GitHub release URLs was updated to correctly handle tag segments that require URL encoding, ensuring that special characters in release tags are properly represented in the generated web URLs. Additionally, the code was refined to prevent unintended rewriting of release asset URLs to tag URLs, maintaining the integrity of asset links. This change also introduces more robust parsing of the API URL to ensure accuracy and prevent potential errors during conversion.
1 parent 42312e8 commit ab7b915

1 file changed

Lines changed: 55 additions & 8 deletions

File tree

src/models/notification.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::models::{NotificationReason, NotificationType};
22
use chrono::{DateTime, Utc};
33
use serde::{Deserialize, Serialize};
4+
use url::Url;
45

56
#[derive(Debug, Clone, Serialize, Deserialize)]
67
pub struct Notification {
@@ -168,14 +169,36 @@ impl Notification {
168169
// API: https://api.github.com/repos/owner/repo/releases/12345
169170
// Web: https://github.com/owner/repo/releases/tag/v1.0.0
170171
if let Some(remainder) = api_url.strip_prefix(&prefix) {
171-
let parts: Vec<&str> = remainder.splitn(4, '/').collect();
172-
if parts.len() >= 3 && parts[2] == "releases" {
173-
let (owner, repo) = (parts[0], parts[1]);
174-
let tag = &self.subject.title;
175-
return Some(format!(
176-
"{}/{}/{}/releases/tag/{}",
177-
web_base, owner, repo, tag
178-
));
172+
let mut parts = remainder.split('/');
173+
let owner = parts.next();
174+
let repo = parts.next();
175+
let resource = parts.next();
176+
let release_id = parts.next();
177+
let trailing = parts.next();
178+
179+
if let (Some(owner), Some(repo), Some("releases"), Some(release_id), None) =
180+
(owner, repo, resource, release_id, trailing)
181+
{
182+
if !matches!(self.notification_type(), NotificationType::Release)
183+
|| release_id.parse::<u64>().is_err()
184+
{
185+
// Not a release-by-id subject URL; continue with generic conversion below.
186+
// This avoids rewriting paths such as /releases/assets/{id}.
187+
} else {
188+
let mut release_url = Url::parse(&format!(
189+
"{}/{}/{}/releases/tag/",
190+
web_base, owner, repo
191+
))
192+
.ok()?;
193+
194+
{
195+
let mut path = release_url.path_segments_mut().ok()?;
196+
path.pop_if_empty();
197+
path.push(&self.subject.title);
198+
}
199+
200+
return Some(release_url.into());
201+
}
179202
}
180203
}
181204

@@ -304,4 +327,28 @@ mod tests {
304327
Some("https://git.example.com/org/proj/releases/tag/v2.0.0".to_string())
305328
);
306329
}
330+
331+
#[test]
332+
fn web_url_release_encodes_tag_segment() {
333+
let n = make_notification(
334+
Some("https://api.github.com/repos/owner/repo/releases/12345"),
335+
"release/v1.2.3",
336+
);
337+
assert_eq!(
338+
n.web_url("github.com"),
339+
Some("https://github.com/owner/repo/releases/tag/release%2Fv1.2.3".to_string())
340+
);
341+
}
342+
343+
#[test]
344+
fn web_url_release_assets_not_rewritten_to_tag() {
345+
let n = make_notification(
346+
Some("https://api.github.com/repos/owner/repo/releases/assets/42"),
347+
"v1.2.3",
348+
);
349+
assert_eq!(
350+
n.web_url("github.com"),
351+
Some("https://github.com/owner/repo/releases/assets/42".to_string())
352+
);
353+
}
307354
}

0 commit comments

Comments
 (0)