33use anyhow:: { anyhow, bail} ;
44use itertools:: Itertools ;
55use nutype:: nutype;
6- use std:: { convert:: Infallible , num:: NonZeroU32 , str:: FromStr } ;
6+ use std:: { convert:: Infallible , fmt :: Display , num:: NonZeroU32 , path :: PathBuf , str:: FromStr } ;
77use tap:: Pipe as _;
88
99use indexmap:: IndexSet ;
@@ -17,7 +17,7 @@ pub struct Config {
1717 pub local_branch : BranchName ,
1818 /// List of patches to apply
1919 #[ serde( default ) ]
20- pub patches : IndexSet < String > ,
20+ pub patches : IndexSet < PatchName > ,
2121 /// List of pull request to apply
2222 #[ serde( default ) ]
2323 pub pull_requests : Vec < PullRequest > ,
@@ -34,9 +34,9 @@ pub struct Config {
3434#[ derive( Debug , Eq , PartialEq , Clone ) ]
3535pub struct Remote {
3636 /// e.g. `helix-editor`
37- pub owner : String ,
37+ pub owner : RepoOwner ,
3838 /// e.g. `helix`
39- pub repo : String ,
39+ pub repo : RepoName ,
4040 /// e.g. `master`
4141 pub branch : BranchName ,
4242 /// e.g. `1a2b3c`
@@ -66,6 +66,9 @@ impl FromStr for Remote {
6666 bail ! ( "Invalid branch format: {item}. Expected format: owner/repo/branch" ) ;
6767 } ;
6868
69+ let owner = RepoOwner :: try_new ( owner) ?;
70+ let repo = RepoName :: try_new ( repo) ?;
71+
6972 let branch = parts
7073 // insert back the removed '/', this could be part of the branch itself
7174 // e.g. in `helix-editor/helix/master/main` the branch is considered to be `master/main`
@@ -87,8 +90,8 @@ impl FromStr for Remote {
8790 . map_err ( |err| anyhow ! ( "invalid branch name: {err}" ) ) ?;
8891
8992 Ok ( Self {
90- owner : owner . to_string ( ) ,
91- repo : repo . to_string ( ) ,
93+ owner,
94+ repo,
9295 branch,
9396 commit,
9497 } )
@@ -184,11 +187,67 @@ impl FromStr for Ref {
184187 }
185188}
186189
190+ #[ cfg( test) ]
191+ impl From < & str > for RepoOwner {
192+ fn from ( value : & str ) -> Self {
193+ Self :: try_new ( value) . unwrap ( )
194+ }
195+ }
196+ #[ cfg( test) ]
197+ impl From < & str > for RepoName {
198+ fn from ( value : & str ) -> Self {
199+ Self :: try_new ( value) . unwrap ( )
200+ }
201+ }
202+ #[ cfg( test) ]
203+ impl From < & str > for BranchName {
204+ fn from ( value : & str ) -> Self {
205+ Self :: try_new ( value) . unwrap ( )
206+ }
207+ }
208+ #[ cfg( test) ]
209+ impl From < & str > for Commit {
210+ fn from ( value : & str ) -> Self {
211+ Self :: try_new ( value) . unwrap ( )
212+ }
213+ }
214+
187215/// Number of a pull request
188216#[ nutype( const_fn, derive( Eq , PartialEq , Display , Debug , FromStr , Copy , Clone ) ) ]
189217pub struct PrNumber ( NonZeroU32 ) ;
190218
219+ #[ cfg( test) ]
220+ impl From < u32 > for PrNumber {
221+ fn from ( value : u32 ) -> Self {
222+ Self :: new ( NonZeroU32 :: new ( value) . unwrap ( ) )
223+ }
224+ }
225+
226+ /// Represents owner of a repository
227+ ///
228+ /// E.g. in `helix-editor/helix/master`, this is `helix-editor`
229+ #[ nutype(
230+ validate( not_empty) ,
231+ derive(
232+ Debug , Eq , PartialEq , Ord , PartialOrd , Clone , AsRef , Display , Serialize
233+ )
234+ ) ]
235+ pub struct RepoOwner ( String ) ;
236+
237+ /// Represents name of a repository
238+ ///
239+ /// E.g. in `helix-editor/helix/master`, this is `helix`
240+ #[ nutype(
241+ validate( not_empty) ,
242+ derive(
243+ Debug , Eq , PartialEq , Ord , PartialOrd , Clone , AsRef , Display , Serialize
244+ )
245+ ) ]
246+ pub struct RepoName ( String ) ;
247+
191248/// Name of a branch in git
249+ ///
250+ /// E.g. in `helix-editor/helix/master`, this is `master`
192251#[ nutype(
193252 validate( not_empty) ,
194253 derive(
@@ -207,6 +266,16 @@ impl FromStr for BranchName {
207266 }
208267}
209268
269+ /// File name of a patch
270+ #[ nutype( validate( predicate = |p| !p. as_os_str( ) . is_empty( ) ) , derive( Hash , Eq , PartialEq , Debug , AsRef , Deserialize , Clone , FromStr ) ) ]
271+ pub struct PatchName ( PathBuf ) ;
272+
273+ impl Display for PatchName {
274+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
275+ write ! ( f, "{}" , self . as_ref( ) . display( ) )
276+ }
277+ }
278+
210279/// Represents a git commit hash
211280#[ nutype(
212281 validate( not_empty, predicate = is_valid_commit_hash) ,
@@ -264,45 +333,45 @@ mod tests {
264333 (
265334 "helix-editor/helix/master @ 1a2b3c" ,
266335 Remote {
267- owner : "helix-editor" . to_string ( ) ,
268- repo : "helix" . to_string ( ) ,
269- branch : BranchName :: try_new ( "master" ) . unwrap ( ) ,
270- commit : Some ( Commit :: try_new ( "1a2b3c" . to_string ( ) ) . unwrap ( ) ) ,
336+ owner : "helix-editor" . into ( ) ,
337+ repo : "helix" . into ( ) ,
338+ branch : "master" . into ( ) ,
339+ commit : Some ( "1a2b3c" . into ( ) ) ,
271340 } ,
272341 ) ,
273342 (
274343 "helix-editor/helix @ deadbeef" ,
275344 Remote {
276- owner : "helix-editor" . to_string ( ) ,
277- repo : "helix" . to_string ( ) ,
278- branch : BranchName :: try_new ( Remote :: DEFAULT_BRANCH ) . unwrap ( ) ,
279- commit : Some ( Commit :: try_new ( "deadbeef" . to_string ( ) ) . unwrap ( ) ) ,
345+ owner : "helix-editor" . into ( ) ,
346+ repo : "helix" . into ( ) ,
347+ branch : Remote :: DEFAULT_BRANCH . into ( ) ,
348+ commit : Some ( "deadbeef" . into ( ) ) ,
280349 } ,
281350 ) ,
282351 (
283352 "helix-editor/helix/feat/feature-x @ abc123" ,
284353 Remote {
285- owner : "helix-editor" . to_string ( ) ,
286- repo : "helix" . to_string ( ) ,
287- branch : BranchName :: try_new ( "feat/feature-x" ) . unwrap ( ) ,
288- commit : Some ( Commit :: try_new ( "abc123" . to_string ( ) ) . unwrap ( ) ) ,
354+ owner : "helix-editor" . into ( ) ,
355+ repo : "helix" . into ( ) ,
356+ branch : "feat/feature-x" . into ( ) ,
357+ commit : Some ( "abc123" . into ( ) ) ,
289358 } ,
290359 ) ,
291360 (
292361 "owner/repo/branch" ,
293362 Remote {
294- owner : "owner" . to_string ( ) ,
295- repo : "repo" . to_string ( ) ,
296- branch : BranchName :: try_new ( "branch" ) . unwrap ( ) ,
363+ owner : "owner" . into ( ) ,
364+ repo : "repo" . into ( ) ,
365+ branch : "branch" . into ( ) ,
297366 commit : None ,
298367 } ,
299368 ) ,
300369 (
301370 "owner/repo" ,
302371 Remote {
303- owner : "owner" . to_string ( ) ,
304- repo : "repo" . to_string ( ) ,
305- branch : BranchName :: try_new ( Remote :: DEFAULT_BRANCH ) . unwrap ( ) ,
372+ owner : "owner" . into ( ) ,
373+ repo : "repo" . into ( ) ,
374+ branch : Remote :: DEFAULT_BRANCH . into ( ) ,
306375 commit : None ,
307376 } ,
308377 ) ,
@@ -338,7 +407,7 @@ patches = ['remove-tab']"#;
338407 conf,
339408 Config {
340409 local_branch: BranchName :: try_new( "patchy" . to_string( ) ) . unwrap( ) ,
341- patches: indexset![ "remove-tab" . to_string ( ) ] ,
410+ patches: indexset![ PatchName :: try_new ( "remove-tab" . into ( ) ) . unwrap ( ) ] ,
342411 pull_requests: vec![
343412 PullRequest {
344413 number: pr_number!( 10000 ) ,
0 commit comments