git_bot_feedback/
output_variable.rs1#[cfg(feature = "pyo3")]
2use pyo3::prelude::*;
3
4use std::fmt::Display;
5
6use crate::error::OutputVariableError;
7
8#[derive(Debug, Clone)]
16#[cfg_attr(
17 feature = "pyo3",
18 pyclass(module = "git_bot_feedback", from_py_object, str, get_all, set_all)
19)]
20pub struct OutputVariable {
21 pub name: String,
23
24 pub value: String,
26}
27
28#[cfg(feature = "pyo3")]
29#[pymethods]
30impl OutputVariable {
31 #[new]
33 #[pyo3(
34 signature = (name, value),
35 text_signature = "(name: str, value: str)"
36 )]
37 pub fn new_py(name: String, value: String) -> Self {
38 Self { name, value }
39 }
40
41 pub fn validate_py(&self) -> PyResult<()> {
49 self.validate()?;
50 Ok(())
51 }
52}
53
54impl OutputVariable {
55 pub fn validate(&self) -> Result<(), OutputVariableError> {
60 let name = self.name.trim();
61 if name.is_empty() {
62 return Err(OutputVariableError::NameIsEmpty);
63 }
64 for (i, c) in name.chars().enumerate() {
65 if i == 0 && c.is_ascii_digit() {
66 return Err(OutputVariableError::NameStartsWithNumber(name.to_string()));
67 }
68 if !(c.is_ascii_alphanumeric() || c == '_' || c == '-') {
69 return Err(OutputVariableError::NameContainsNonPrintableCharacters(
70 name.to_string(),
71 ));
72 }
73 }
74 let value = self.value.trim();
75 if !value
76 .chars()
77 .all(|c| c.is_ascii_alphanumeric() || c.is_ascii_punctuation() || !c.is_ascii_control())
78 {
79 return Err(OutputVariableError::ValueContainsNonPrintableCharacters(
80 value.to_string(),
81 ));
82 }
83 Ok(())
84 }
85}
86
87impl Display for OutputVariable {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "{}={}", self.name.trim(), self.value.trim())
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 #![allow(clippy::unwrap_used)]
96
97 use super::{OutputVariable, OutputVariableError};
98
99 #[test]
100 fn empty_name() {
101 let var = OutputVariable {
102 name: " ".to_string(),
103 value: "value".to_string(),
104 };
105 assert_eq!(var.validate(), Err(OutputVariableError::NameIsEmpty));
106 }
107
108 #[test]
109 fn name_starts_with_number() {
110 let var = OutputVariable {
111 name: "1var".to_string(),
112 value: "value".to_string(),
113 };
114 assert_eq!(
115 var.validate(),
116 Err(OutputVariableError::NameStartsWithNumber(
117 "1var".to_string()
118 ))
119 );
120 }
121
122 #[test]
123 fn name_contains_non_printable_characters() {
124 let var = OutputVariable {
125 name: "var\nname".to_string(),
126 value: "value".to_string(),
127 };
128 assert_eq!(
129 var.validate(),
130 Err(OutputVariableError::NameContainsNonPrintableCharacters(
131 "var\nname".to_string()
132 ))
133 );
134 }
135
136 #[test]
137 fn value_contains_non_printable_characters() {
138 let var = OutputVariable {
139 name: "var".to_string(),
140 value: "(val)\nline2".to_string(),
141 };
142 assert_eq!(
143 var.validate(),
144 Err(OutputVariableError::ValueContainsNonPrintableCharacters(
145 "(val)\nline2".to_string()
146 ))
147 );
148 }
149
150 #[test]
151 fn valid_variable() {
152 OutputVariable {
153 name: " VAR_NAME ".to_string(),
154 value: " value -(123) ".to_string(),
155 }
156 .validate()
157 .unwrap();
158 }
159}