Skip to main content

worktree_io/issue/parse/
mod.rs

1mod shorthand;
2mod worktree_url;
3
4use anyhow::{bail, Context, Result};
5use url::Url;
6
7use super::{DeepLinkOptions, IssueRef};
8
9impl IssueRef {
10    /// Parse any of the supported input formats:
11    /// - `https://github.com/owner/repo/issues/42`
12    /// - `worktree://open?owner=X&repo=Y&issue=42`
13    /// - `worktree://open?url=<encoded-github-url>`
14    /// - `worktree://open?owner=X&repo=Y&linear_id=<uuid>`
15    /// - `owner/repo#42`
16    /// - `owner/repo@<linear-uuid>`
17    ///
18    /// # Errors
19    ///
20    /// Returns an error if `s` does not match any supported format or if the
21    /// extracted values (e.g. issue number) are invalid.
22    pub fn parse(s: &str) -> Result<Self> {
23        let s = s.trim();
24
25        if s.starts_with("worktree://") {
26            let (issue, _opts) = worktree_url::parse_worktree_url(s)?;
27            return Ok(issue);
28        }
29
30        if s.starts_with("https://github.com") || s.starts_with("http://github.com") {
31            return parse_github_url(s);
32        }
33
34        if let Some(result) = shorthand::try_parse_shorthand(s) {
35            return result;
36        }
37
38        bail!(
39            "Could not parse issue reference: {s:?}\n\
40             Supported formats:\n\
41             - https://github.com/owner/repo/issues/42\n\
42             - worktree://open?owner=owner&repo=repo&issue=42\n\
43             - worktree://open?owner=owner&repo=repo&linear_id=<uuid>\n\
44             - owner/repo#42\n\
45             - owner/repo@<linear-uuid>"
46        )
47    }
48
49    /// Like [`parse`] but also returns any [`DeepLinkOptions`] embedded in a
50    /// `worktree://` URL (e.g. the `editor` query param).
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if `s` cannot be parsed as a valid issue reference.
55    pub fn parse_with_options(s: &str) -> Result<(Self, DeepLinkOptions)> {
56        let s = s.trim();
57        if s.starts_with("worktree://") {
58            return worktree_url::parse_worktree_url(s);
59        }
60        Ok((Self::parse(s)?, DeepLinkOptions::default()))
61    }
62}
63
64pub(super) fn parse_github_url(s: &str) -> Result<IssueRef> {
65    let url = Url::parse(s).with_context(|| format!("Invalid URL: {s}"))?;
66
67    let segments: Vec<&str> = url
68        .path_segments()
69        .context("URL has no path")?
70        .filter(|s| !s.is_empty())
71        .collect();
72
73    if segments.len() < 4 || segments[2] != "issues" {
74        bail!("Expected GitHub issue URL like https://github.com/owner/repo/issues/42, got: {s}");
75    }
76
77    let owner = segments[0].to_string();
78    let repo = segments[1].to_string();
79    let number = segments[3]
80        .parse::<u64>()
81        .with_context(|| format!("Invalid issue number in URL: {}", segments[3]))?;
82
83    Ok(IssueRef::GitHub {
84        owner,
85        repo,
86        number,
87    })
88}