umicp_core/
utils.rs

1/*!
2# UMICP Utilities
3
4Utility functions for UMICP operations.
5*/
6
7use 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
13/// Generate a new UUID v4
14pub fn generate_uuid() -> String {
15    Uuid::new_v4().to_string()
16}
17
18/// Get current timestamp in ISO 8601 format
19pub fn get_current_timestamp() -> String {
20    Utc::now().to_rfc3339()
21}
22
23/// Parse timestamp from ISO 8601 format
24pub 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
30/// Generate SHA-256 hash of data
31pub 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
38/// Validate UUID format
39pub fn validate_uuid(uuid_str: &str) -> bool {
40    Uuid::parse_str(uuid_str).is_ok()
41}
42
43/// Validate that a string is not empty
44pub 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
54/// Validate that a number is positive
55pub 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
65/// Validate that an index is within bounds
66pub 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
76/// Base64 encode data
77pub fn base64_encode(data: &[u8]) -> String {
78    BASE64.encode(data)
79}
80
81/// Base64 decode data
82pub fn base64_decode(data: &str) -> Result<Vec<u8>> {
83    BASE64.decode(data).map_err(|e| UmicpError::validation(format!("Invalid base64: {}", e)))
84}
85
86/// Convert bytes to human readable format
87pub 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
104/// Get elapsed time in human readable format
105pub 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
122/// Sanitize string for safe usage
123pub fn sanitize_string(input: &str) -> String {
124    input
125        .chars()
126        .filter(|c| c.is_alphanumeric() || *c == '_' || *c == '-' || *c == '.')
127        .collect()
128}
129
130/// Check if string contains only ASCII characters
131pub fn is_ascii_only(input: &str) -> bool {
132    input.chars().all(|c| c.is_ascii())
133}
134
135/// Truncate string to maximum length
136pub 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
144/// Parse version string
145pub 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
169/// Compare versions
170pub 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}