diff --git a/src/parser/url.rs b/src/parser/url.rs new file mode 100644 index 0000000..e648924 --- /dev/null +++ b/src/parser/url.rs @@ -0,0 +1,117 @@ +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq)] +pub enum Provider { + GitHub, + GitLab, +} + +#[derive(Debug, Clone)] +pub struct ParsedIssueUrl { + pub provider: Provider, + pub owner: String, + pub repo: String, + pub issue_number: u64, + pub is_pull_request: bool, +} + +#[derive(Debug, Error, PartialEq)] +pub enum UrlParseError { + #[error("Invalid URL format")] + InvalidFormat, + #[error("Unsupported provider")] + UnsupportedProvider, + #[error("Missing issue number")] + MissingIssueNumber, +} + +pub fn parse_issue_url(url: &str) -> Result { + if let Some(github_url) = parse_github_url(url) { + return Ok(github_url); + } + if let Some(gitlab_url) = parse_gitlab_url(url) { + return Ok(gitlab_url); + } + Err(UrlParseError::InvalidFormat) +} + +fn parse_github_url(url: &str) -> Option { + let re = regex::Regex::new(r"github\.com/([^/]+)/([^/]+)/(?:issues|pull)/(\d+)").expect("Invalid regex pattern for GitHub URLs"); + let caps = re.captures(url)?; + + let owner = caps.get(1)?.as_str().to_string(); + let repo = caps.get(2)?.as_str().to_string(); + let issue_number = caps.get(3)?.as_str().parse().ok()?; + let is_pull_request = url.contains("/pull/"); + + Some(ParsedIssueUrl { + provider: Provider::GitHub, + owner, + repo, + issue_number, + is_pull_request, + }) +} + +fn parse_gitlab_url(url: &str) -> Option { + let re = regex::Regex::new(r"gitlab\.com/([^/]+)/([^/]+)/-/issues/(\d+)").expect("Invalid regex pattern for GitLab URLs"); + let caps = re.captures(url)?; + + let owner = caps.get(1)?.as_str().to_string(); + let repo = caps.get(2)?.as_str().to_string(); + let issue_number = caps.get(3)?.as_str().parse().ok()?; + let is_pull_request = url.contains("/merge_requests"); + + Some(ParsedIssueUrl { + provider: Provider::GitLab, + owner, + repo, + issue_number, + is_pull_request, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_github_issue_url() { + let url = "https://github.com/owner/repo/issues/123"; + let parsed = parse_issue_url(url).unwrap(); + assert_eq!(parsed.provider, Provider::GitHub); + assert_eq!(parsed.owner, "owner"); + assert_eq!(parsed.repo, "repo"); + assert_eq!(parsed.issue_number, 123); + assert!(!parsed.is_pull_request); + } + + #[test] + fn test_parse_github_pr_url() { + let url = "https://github.com/owner/repo/pull/456"; + let parsed = parse_issue_url(url).unwrap(); + assert_eq!(parsed.provider, Provider::GitHub); + assert_eq!(parsed.owner, "owner"); + assert_eq!(parsed.repo, "repo"); + assert_eq!(parsed.issue_number, 456); + assert!(parsed.is_pull_request); + } + + #[test] + fn test_parse_gitlab_issue_url() { + let url = "https://gitlab.com/owner/repo/-/issues/789"; + let parsed = parse_issue_url(url).unwrap(); + assert_eq!(parsed.provider, Provider::GitLab); + assert_eq!(parsed.owner, "owner"); + assert_eq!(parsed.repo, "repo"); + assert_eq!(parsed.issue_number, 789); + assert!(!parsed.is_pull_request); + } + + #[test] + fn test_parse_invalid_url() { + let result = parse_issue_url("not-a-valid-url"); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), UrlParseError::InvalidFormat); + } +}