1use crate::core::error::BranchError;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum BranchType {
12 Feature,
13 Bugfix,
14 Hotfix,
15 Spike,
16}
17
18impl BranchType {
19 pub fn as_str(&self) -> &'static str {
21 match self {
22 Self::Feature => "feature",
23 Self::Bugfix => "bugfix",
24 Self::Hotfix => "hotfix",
25 Self::Spike => "spike",
26 }
27 }
28
29 pub fn format_branch_name(&self, name: &str) -> Result<String, BranchError> {
31 validate_leaf_name(name)?;
32 Ok(format!("{}/{}", self.as_str(), name))
33 }
34}
35
36impl fmt::Display for BranchType {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "{}", self.as_str())
39 }
40}
41
42impl FromStr for BranchType {
43 type Err = BranchError;
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 match s.to_lowercase().as_str() {
47 "feature" => Ok(Self::Feature),
48 "bugfix" => Ok(Self::Bugfix),
49 "hotfix" => Ok(Self::Hotfix),
50 "spike" => Ok(Self::Spike),
51 _ => Err(BranchError::InvalidBranchType(s.to_string())),
52 }
53 }
54}
55
56pub fn validate_leaf_name(name: &str) -> Result<(), BranchError> {
58 if name.is_empty() {
59 return Err(BranchError::EmptyName);
60 }
61
62 if name.starts_with('-') || name.ends_with('-') {
63 return Err(BranchError::InvalidKebabCase(name.to_string()));
64 }
65
66 let is_valid = name
67 .chars()
68 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-');
69
70 if !is_valid {
71 return Err(BranchError::InvalidKebabCase(name.to_string()));
72 }
73
74 Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_valid_leaf_names() {
83 assert!(validate_leaf_name("auth").is_ok());
84 assert!(validate_leaf_name("user-profile-v2").is_ok());
85 assert!(validate_leaf_name("fix123").is_ok());
86 }
87
88 #[test]
89 fn test_invalid_leaf_names() {
90 assert!(matches!(
91 validate_leaf_name(""),
92 Err(BranchError::EmptyName)
93 ));
94 assert!(matches!(
95 validate_leaf_name("-leading"),
96 Err(BranchError::InvalidKebabCase(_))
97 ));
98 assert!(matches!(
99 validate_leaf_name("trailing-"),
100 Err(BranchError::InvalidKebabCase(_))
101 ));
102 assert!(matches!(
103 validate_leaf_name("Uppercase"),
104 Err(BranchError::InvalidKebabCase(_))
105 ));
106 assert!(matches!(
107 validate_leaf_name("with_underscore"),
108 Err(BranchError::InvalidKebabCase(_))
109 ));
110 }
111
112 #[test]
113 fn test_branch_formatting() {
114 assert_eq!(
115 BranchType::Feature.format_branch_name("auth").unwrap(),
116 "feature/auth"
117 );
118 assert_eq!(
119 BranchType::Bugfix.format_branch_name("null-ptr").unwrap(),
120 "bugfix/null-ptr"
121 );
122 }
123}