11use std:: fmt:: Formatter ;
2+ use url:: Url ;
23
34#[ derive( Debug , Eq , PartialEq , Clone ) ]
45pub struct Repository {
@@ -11,10 +12,19 @@ impl Repository {
1112 if src. is_empty ( ) {
1213 return Err ( "Invalid repository. Cannot be empty" . to_string ( ) ) ;
1314 }
14- if !src. contains ( '/' ) {
15+
16+ if src. starts_with ( "http://github.com" ) || src. starts_with ( "https://github.com" ) {
17+ Self :: parse_url ( src)
18+ } else {
19+ Self :: parse ( src)
20+ }
21+ }
22+
23+ fn parse ( input : & str ) -> Result < Repository , String > {
24+ if !input. contains ( '/' ) {
1525 return Err ( "Invalid repository. Use {owner}/{repo} format" . to_string ( ) ) ;
1626 }
17- let parts = src
27+ let parts = input
1828 . split ( '/' )
1929 . filter ( |x| !x. is_empty ( ) )
2030 . collect :: < Vec < & str > > ( ) ;
@@ -27,6 +37,23 @@ impl Repository {
2737 repo : parts[ 1 ] . to_string ( ) ,
2838 } )
2939 }
40+
41+ fn parse_url ( input : & str ) -> Result < Repository , String > {
42+ let github_url = Url :: parse ( input) . map_err ( |x| format ! ( "Invalid repository URL: {}" , x) ) ?;
43+ let parts = github_url
44+ . path ( )
45+ . split ( '/' )
46+ . filter ( |x| !x. is_empty ( ) )
47+ . collect :: < Vec < & str > > ( ) ;
48+ if parts. len ( ) < 2 {
49+ return Err ( "Invalid repository URL. Missing owner or repo" . to_string ( ) ) ;
50+ }
51+
52+ Ok ( Repository {
53+ owner : parts[ 0 ] . to_string ( ) ,
54+ repo : parts[ 1 ] . to_string ( ) ,
55+ } )
56+ }
3057}
3158
3259impl std:: fmt:: Display for Repository {
@@ -54,6 +81,36 @@ mod tests {
5481 ) ;
5582 }
5683
84+ #[ test]
85+ fn valid_repository_from_url ( ) {
86+ let input = "https://github.com/foo/bar?tab=readme-ov-file" ;
87+
88+ let result = Repository :: try_parse ( input) ;
89+
90+ assert_eq ! (
91+ Ok ( Repository {
92+ owner: "foo" . to_string( ) ,
93+ repo: "bar" . to_string( )
94+ } ) ,
95+ result
96+ ) ;
97+ }
98+
99+ #[ test]
100+ fn valid_repository_url_from_any_page ( ) {
101+ let input = "https://github.com/foo/bar/actions/runs/16966254957" ;
102+
103+ let result = Repository :: try_parse ( input) ;
104+
105+ assert_eq ! (
106+ Ok ( Repository {
107+ owner: "foo" . to_string( ) ,
108+ repo: "bar" . to_string( )
109+ } ) ,
110+ result
111+ ) ;
112+ }
113+
57114 #[ test]
58115 fn missing_owner ( ) {
59116 let input = "/bar" ;
@@ -72,6 +129,24 @@ mod tests {
72129 assert_error ( |e| assert_contains ( "Missing owner or repo" , e) , result) ;
73130 }
74131
132+ #[ test]
133+ fn missing_owner_in_url ( ) {
134+ let input = "https://github.com" ;
135+
136+ let result = Repository :: try_parse ( input) ;
137+
138+ assert_error ( |e| assert_contains ( "Missing owner or repo" , e) , result) ;
139+ }
140+
141+ #[ test]
142+ fn missing_repo_in_url ( ) {
143+ let input = "https://github.com/foo/" ;
144+
145+ let result = Repository :: try_parse ( input) ;
146+
147+ assert_error ( |e| assert_contains ( "Missing owner or repo" , e) , result) ;
148+ }
149+
75150 #[ test]
76151 fn empty_repository ( ) {
77152 let input = "" ;
0 commit comments