commit_wizard/domain/
commit.rs1use crate::domain::errors::DomainError;
2use std::fmt::{self, Display};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CommitType {
6 Feat,
7 Fix,
8 Docs,
9 Chore,
10 Refactor,
11 Test,
12 Perf,
13 Build,
14 Ci,
15 Style,
16}
17
18impl CommitType {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 CommitType::Feat => "feat",
22 CommitType::Fix => "fix",
23 CommitType::Docs => "docs",
24 CommitType::Chore => "chore",
25 CommitType::Refactor => "refactor",
26 CommitType::Test => "test",
27 CommitType::Perf => "perf",
28 CommitType::Build => "build",
29 CommitType::Ci => "ci",
30 CommitType::Style => "style",
31 }
32 }
33}
34impl Display for CommitType {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str(self.as_str())
37 }
38}
39
40#[derive(Debug, Clone)]
41pub struct CommitMessage {
42 r#type: CommitType,
43 scope: Option<String>,
44 summary: String,
45 body: Option<String>,
46 breaking: bool,
47}
48
49impl CommitMessage {
50 pub fn new(
51 r#type: CommitType,
52 scope: Option<String>,
53 summary: String,
54 body: Option<String>,
55 breaking: bool,
56 ) -> Result<Self, DomainError> {
57 if summary.trim().is_empty() {
58 return Err(DomainError::EmptySummary);
59 }
60 if summary.chars().count() > 72 {
61 return Err(DomainError::SummaryTooLong(72));
62 }
63 Ok(Self {
64 r#type,
65 scope: scope.and_then(|s| {
66 let t = s.trim().to_string();
67 if t.is_empty() { None } else { Some(t) }
68 }),
69 summary: summary.trim().to_string(),
70 body: body.and_then(|b| {
71 let t = b.trim().to_string();
72 if t.is_empty() { None } else { Some(t) }
73 }),
74 breaking,
75 })
76 }
77
78 pub fn render(&self) -> String {
79 let header = match &self.scope {
80 Some(s) => format!("{}({}): {}", self.r#type, s, self.summary),
81 None => format!("{}: {}", self.r#type, self.summary),
82 };
83
84 let mut sections = Vec::new();
85 if self.breaking {
86 sections.push(String::from("BREAKING CHANGE: yes"));
87 }
88 if let Some(b) = &self.body {
89 sections.push(b.clone());
90 }
91
92 if sections.is_empty() {
93 header
94 } else {
95 format!("{header}\n\n{}", sections.join("\n\n"))
96 }
97 }
98}