1use std::path::Path;
4
5use crate::CommandError;
6use crate::ref_format::{self, RefFormatError};
7
8crate::cow_str_newtype! {
9 pub struct Tag, TagError(RefFormatError), "invalid tag name"
14}
15
16impl Tag {
17 const fn validate(input: &str) -> Result<(), TagError> {
18 match ref_format::validate(input) {
19 Ok(()) => Ok(()),
20 Err(error) => Err(TagError(error)),
21 }
22 }
23}
24
25#[must_use]
27pub fn create(name: &Tag) -> Create<'_> {
28 Create::new(name)
29}
30
31#[derive(Debug)]
35pub struct Create<'a> {
36 repo_path: Option<&'a Path>,
37 name: &'a Tag,
38}
39
40crate::impl_repo_path!(Create);
41
42impl<'a> Create<'a> {
43 #[must_use]
44 fn new(name: &'a Tag) -> Self {
45 Self {
46 repo_path: None,
47 name,
48 }
49 }
50
51 pub async fn status(self) -> Result<(), CommandError> {
53 crate::Build::build(self).status().await
54 }
55}
56
57impl crate::Build for Create<'_> {
58 fn build(self) -> cmd_proc::Command {
59 crate::base_command(self.repo_path)
60 .argument("tag")
61 .argument(self.name)
62 }
63}
64
65#[cfg(feature = "test-utils")]
66impl Create<'_> {
67 pub fn test_eq(&self, other: &cmd_proc::Command) {
69 let command = crate::Build::build(Self {
70 repo_path: self.repo_path,
71 name: self.name,
72 });
73 command.test_eq(other);
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_valid_tag() {
83 assert!("v1.0.0".parse::<Tag>().is_ok());
84 assert!("release-2024".parse::<Tag>().is_ok());
85 assert!("hotfix/v1.2.3".parse::<Tag>().is_ok());
86 }
87
88 #[test]
89 fn test_invalid_passes_through() {
90 assert!(matches!(
91 "".parse::<Tag>(),
92 Err(TagError(RefFormatError::Empty))
93 ));
94 assert!(matches!(
95 "-tag".parse::<Tag>(),
96 Err(TagError(RefFormatError::StartsWithDash))
97 ));
98 assert!(matches!(
99 "v1.0.0.lock".parse::<Tag>(),
100 Err(TagError(RefFormatError::EndsWithLock))
101 ));
102 }
103
104 #[test]
105 fn test_from_static_or_panic() {
106 let tag = Tag::from_static_or_panic("v1.0.0");
107 assert_eq!(tag.as_str(), "v1.0.0");
108 }
109
110 #[test]
111 fn test_display() {
112 let tag: Tag = "v1.0.0".parse().unwrap();
113 assert_eq!(format!("{tag}"), "v1.0.0");
114 }
115
116 #[test]
117 fn test_as_ref_os_str() {
118 use std::ffi::OsStr;
119 let tag: Tag = "v1.0.0".parse().unwrap();
120 let os_str: &OsStr = tag.as_ref();
121 assert_eq!(os_str, "v1.0.0");
122 }
123
124 #[test]
125 fn test_serialize() {
126 let tag: Tag = "v1.0.0".parse().unwrap();
127 assert_eq!(serde_json::to_string(&tag).unwrap(), "\"v1.0.0\"");
128 }
129
130 #[test]
131 fn test_deserialize() {
132 let tag: Tag = serde_json::from_str("\"v1.0.0\"").unwrap();
133 assert_eq!(tag.as_str(), "v1.0.0");
134 }
135
136 #[test]
137 fn test_deserialize_invalid() {
138 assert!(serde_json::from_str::<Tag>("\"-bad\"").is_err());
139 }
140}