use crate::CounterSources;
use crate::errors::{CounterError, CounterErrorKind, Result};
use async_nats::HeaderMap;
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
#[derive(Debug, Serialize, Deserialize)]
struct CounterPayload {
val: String,
}
pub fn parse_counter_value(data: &[u8]) -> Result<BigInt> {
if data.is_empty() {
return Err(CounterError::new(CounterErrorKind::InvalidCounterValue));
}
let payload: CounterPayload = serde_json::from_slice(data)
.map_err(|e| CounterError::with_source(CounterErrorKind::Serialization, e))?;
BigInt::from_str(&payload.val)
.map_err(|_| CounterError::new(CounterErrorKind::InvalidResponseValue))
}
pub fn parse_counter_value_from_string(value: Option<String>) -> Result<BigInt> {
match value {
Some(val_str) => BigInt::from_str(&val_str)
.map_err(|_| CounterError::new(CounterErrorKind::InvalidResponseValue)),
None => Err(CounterError::new(CounterErrorKind::MissingResponseValue)),
}
}
pub fn parse_sources(headers: &HeaderMap) -> Result<CounterSources> {
let sources_header = headers.get(crate::COUNTER_SOURCES_HEADER);
match sources_header {
Some(header_value) => {
let header_str = header_value.as_str();
let sources_json: HashMap<String, HashMap<String, String>> =
serde_json::from_str(header_str)
.map_err(|e| CounterError::with_source(CounterErrorKind::Serialization, e))?;
let mut sources = HashMap::new();
for (source_id, subjects) in sources_json {
let mut subject_values = HashMap::new();
for (subject, value_str) in subjects {
let value = BigInt::from_str(&value_str)
.map_err(|_| CounterError::new(CounterErrorKind::InvalidSources))?;
subject_values.insert(subject, value);
}
sources.insert(source_id, subject_values);
}
Ok(sources)
}
None => Ok(HashMap::new()),
}
}
pub fn parse_increment(headers: &HeaderMap) -> Result<Option<BigInt>> {
let increment_header = headers.get(crate::COUNTER_INCREMENT_HEADER);
match increment_header {
Some(header_value) => {
let increment_str = header_value.as_str();
let increment = BigInt::from_str(increment_str)
.map_err(|_| CounterError::new(CounterErrorKind::InvalidResponseValue))?;
Ok(Some(increment))
}
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_counter_value_valid() {
let data = br#"{"val": "123"}"#;
let result = parse_counter_value(data).unwrap();
assert_eq!(result, BigInt::from(123));
}
#[test]
fn test_parse_counter_value_negative() {
let data = br#"{"val": "-456"}"#;
let result = parse_counter_value(data).unwrap();
assert_eq!(result, BigInt::from(-456));
}
#[test]
fn test_parse_counter_value_large() {
let data = br#"{"val": "999999999999999999999999999999"}"#;
let result = parse_counter_value(data).unwrap();
assert_eq!(
result,
BigInt::from_str("999999999999999999999999999999").unwrap()
);
}
#[test]
fn test_parse_counter_value_empty() {
let data = b"";
let result = parse_counter_value(data);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::InvalidCounterValue));
}
#[test]
fn test_parse_counter_value_invalid_json() {
let data = b"not json";
let result = parse_counter_value(data);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::Serialization));
}
#[test]
fn test_parse_counter_value_invalid_number() {
let data = br#"{"val": "not a number"}"#;
let result = parse_counter_value(data);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::InvalidResponseValue));
}
#[test]
fn test_parse_sources_empty() {
let headers = HeaderMap::new();
let result = parse_sources(&headers).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_parse_sources_valid() {
let mut headers = HeaderMap::new();
headers.insert(
crate::COUNTER_SOURCES_HEADER,
r#"{"stream1": {"subject1": "100", "subject2": "200"}}"#,
);
let result = parse_sources(&headers).unwrap();
assert_eq!(result.len(), 1);
assert!(result.contains_key("stream1"));
let stream1 = &result["stream1"];
assert_eq!(stream1.len(), 2);
assert_eq!(stream1["subject1"], BigInt::from(100));
assert_eq!(stream1["subject2"], BigInt::from(200));
}
#[test]
fn test_parse_sources_multiple_streams() {
let mut headers = HeaderMap::new();
headers.insert(
crate::COUNTER_SOURCES_HEADER,
r#"{"stream1": {"sub1": "10"}, "stream2": {"sub2": "20"}}"#,
);
let result = parse_sources(&headers).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result["stream1"]["sub1"], BigInt::from(10));
assert_eq!(result["stream2"]["sub2"], BigInt::from(20));
}
#[test]
fn test_parse_increment_present() {
let mut headers = HeaderMap::new();
headers.insert(crate::COUNTER_INCREMENT_HEADER, "42");
let result = parse_increment(&headers).unwrap();
assert_eq!(result, Some(BigInt::from(42)));
}
#[test]
fn test_parse_increment_negative() {
let mut headers = HeaderMap::new();
headers.insert(crate::COUNTER_INCREMENT_HEADER, "-10");
let result = parse_increment(&headers).unwrap();
assert_eq!(result, Some(BigInt::from(-10)));
}
#[test]
fn test_parse_increment_absent() {
let headers = HeaderMap::new();
let result = parse_increment(&headers).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_parse_increment_invalid() {
let mut headers = HeaderMap::new();
headers.insert(crate::COUNTER_INCREMENT_HEADER, "not_a_number");
let result = parse_increment(&headers);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::InvalidResponseValue));
}
#[test]
fn test_parse_counter_value_from_string_valid() {
let value = Some("42".to_string());
let result = parse_counter_value_from_string(value).unwrap();
assert_eq!(result, BigInt::from(42));
}
#[test]
fn test_parse_counter_value_from_string_large() {
let value = Some("999999999999999999999999".to_string());
let result = parse_counter_value_from_string(value).unwrap();
assert_eq!(
result,
BigInt::from_str("999999999999999999999999").unwrap()
);
}
#[test]
fn test_parse_counter_value_from_string_none() {
let result = parse_counter_value_from_string(None);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::MissingResponseValue));
}
#[test]
fn test_parse_counter_value_from_string_invalid() {
let value = Some("not_a_number".to_string());
let result = parse_counter_value_from_string(value);
assert!(matches!(result, Err(e) if e.kind() == CounterErrorKind::InvalidResponseValue));
}
}