1use crate::error::{Result, UmicpError};
8use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
9use chrono::{DateTime, Utc};
10use sha2::{Digest, Sha256};
11use uuid::Uuid;
12
13pub fn generate_uuid() -> String {
15 Uuid::new_v4().to_string()
16}
17
18pub fn get_current_timestamp() -> String {
20 Utc::now().to_rfc3339()
21}
22
23pub fn parse_timestamp(timestamp: &str) -> Result<DateTime<Utc>> {
25 DateTime::parse_from_rfc3339(timestamp)
26 .map_err(|e| UmicpError::validation(format!("Invalid timestamp format: {}", e)))
27 .map(|dt| dt.with_timezone(&Utc))
28}
29
30pub fn generate_hash(data: &[u8]) -> String {
32 let mut hasher = Sha256::new();
33 hasher.update(data);
34 let result = hasher.finalize();
35 hex::encode(result)
36}
37
38pub fn validate_uuid(uuid_str: &str) -> bool {
40 Uuid::parse_str(uuid_str).is_ok()
41}
42
43pub fn validate_non_empty(value: &str, field_name: &str) -> Result<()> {
45 if value.trim().is_empty() {
46 return Err(UmicpError::validation(format!(
47 "Field '{}' cannot be empty",
48 field_name
49 )));
50 }
51 Ok(())
52}
53
54pub fn validate_positive(value: f64, field_name: &str) -> Result<()> {
56 if value <= 0.0 {
57 return Err(UmicpError::validation(format!(
58 "Field '{}' must be positive, got {}",
59 field_name, value
60 )));
61 }
62 Ok(())
63}
64
65pub fn validate_index(index: usize, max_index: usize, field_name: &str) -> Result<()> {
67 if index >= max_index {
68 return Err(UmicpError::validation(format!(
69 "Field '{}' index {} is out of bounds (max: {})",
70 field_name, index, max_index
71 )));
72 }
73 Ok(())
74}
75
76pub fn base64_encode(data: &[u8]) -> String {
78 BASE64.encode(data)
79}
80
81pub fn base64_decode(data: &str) -> Result<Vec<u8>> {
83 BASE64.decode(data).map_err(|e| UmicpError::validation(format!("Invalid base64: {}", e)))
84}
85
86pub fn format_bytes(bytes: u64) -> String {
88 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
89 let mut size = bytes as f64;
90 let mut unit_index = 0;
91
92 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
93 size /= 1024.0;
94 unit_index += 1;
95 }
96
97 if unit_index == 0 {
98 format!("{} {}", bytes, UNITS[0])
99 } else {
100 format!("{:.2} {}", size, UNITS[unit_index])
101 }
102}
103
104pub fn format_duration(seconds: u64) -> String {
106 let days = seconds / 86400;
107 let hours = (seconds % 86400) / 3600;
108 let minutes = (seconds % 3600) / 60;
109 let secs = seconds % 60;
110
111 if days > 0 {
112 format!("{}d {}h {}m {}s", days, hours, minutes, secs)
113 } else if hours > 0 {
114 format!("{}h {}m {}s", hours, minutes, secs)
115 } else if minutes > 0 {
116 format!("{}m {}s", minutes, secs)
117 } else {
118 format!("{}s", secs)
119 }
120}
121
122pub fn sanitize_string(input: &str) -> String {
124 input
125 .chars()
126 .filter(|c| c.is_alphanumeric() || *c == '_' || *c == '-' || *c == '.')
127 .collect()
128}
129
130pub fn is_ascii_only(input: &str) -> bool {
132 input.chars().all(|c| c.is_ascii())
133}
134
135pub fn truncate_string(input: &str, max_length: usize) -> String {
137 if input.len() <= max_length {
138 input.to_string()
139 } else {
140 format!("{}...", &input[..max_length.saturating_sub(3)])
141 }
142}
143
144pub fn parse_version(version: &str) -> Result<(u32, u32, u32)> {
146 let parts: Vec<&str> = version.split('.').collect();
147 if parts.len() != 3 {
148 return Err(UmicpError::validation(format!(
149 "Invalid version format: {}. Expected x.y.z",
150 version
151 )));
152 }
153
154 let major = parts[0].parse().map_err(|_| {
155 UmicpError::validation(format!("Invalid major version: {}", parts[0]))
156 })?;
157
158 let minor = parts[1].parse().map_err(|_| {
159 UmicpError::validation(format!("Invalid minor version: {}", parts[1]))
160 })?;
161
162 let patch = parts[2].parse().map_err(|_| {
163 UmicpError::validation(format!("Invalid patch version: {}", parts[2]))
164 })?;
165
166 Ok((major, minor, patch))
167}
168
169pub fn compare_versions(version1: &str, version2: &str) -> Result<std::cmp::Ordering> {
171 let v1 = parse_version(version1)?;
172 let v2 = parse_version(version2)?;
173
174 Ok(v1.cmp(&v2))
175}