reqwest_builder/
serialization.rs

1use crate::errors::ReqwestBuilderError;
2use http::HeaderMap;
3use serde::Serialize;
4use std::collections::HashMap;
5
6/// Convert a serializable type to form parameters with improved error handling
7pub fn serialize_to_form_params_safe<T: Serialize>(data: &T) -> HashMap<String, String> {
8    serde_json::to_value(data)
9        .ok()
10        .and_then(|v| v.as_object().cloned())
11        .map(|obj| {
12            obj.iter()
13                .filter_map(|(key, val)| {
14                    let value_str = match val {
15                        serde_json::Value::String(s) => s.clone(),
16                        serde_json::Value::Number(n) => n.to_string(),
17                        serde_json::Value::Bool(b) => b.to_string(),
18                        serde_json::Value::Null => return None, // Skip null values
19                        _ => val.to_string(), // Arrays and objects as JSON strings
20                    };
21                    Some((key.clone(), value_str))
22                })
23                .collect()
24        })
25        .unwrap_or_default()
26}
27
28/// Convert a serializable type to form parameters with proper error handling
29pub fn serialize_to_form_params<T: Serialize>(
30    data: &T,
31) -> std::result::Result<HashMap<String, String>, ReqwestBuilderError> {
32    let value = serde_json::to_value(data)?;
33
34    let obj = value.as_object().ok_or_else(|| {
35        ReqwestBuilderError::SerializationError("Data must serialize to a JSON object".to_string())
36    })?;
37
38    let mut params = HashMap::new();
39    for (key, val) in obj {
40        let value_str = match val {
41            serde_json::Value::String(s) => s.clone(),
42            serde_json::Value::Number(n) => n.to_string(),
43            serde_json::Value::Bool(b) => b.to_string(),
44            serde_json::Value::Null => continue, // Skip null values
45            _ => val.to_string(),                // Arrays and objects as JSON strings
46        };
47        params.insert(key.clone(), value_str);
48    }
49
50    Ok(params)
51}
52
53/// Convert serializable headers to HeaderMap with improved error handling
54pub fn serialize_to_header_map_safe<T: Serialize>(headers: &T) -> HeaderMap {
55    let mut header_map = HeaderMap::new();
56
57    if let Ok(value) = serde_json::to_value(headers) {
58        if let Some(obj) = value.as_object() {
59            for (key, val) in obj {
60                if let Some(val_str) = val.as_str() {
61                    if let (Ok(header_name), Ok(header_value)) = (
62                        http::HeaderName::from_bytes(key.as_bytes()),
63                        http::HeaderValue::from_str(val_str),
64                    ) {
65                        header_map.insert(header_name, header_value);
66                    }
67                    // Note: Invalid headers are silently skipped
68                }
69            }
70        }
71    }
72
73    header_map
74}
75
76/// Convert serializable headers to HeaderMap with proper error handling
77pub fn serialize_to_header_map<T: Serialize>(
78    headers: &T,
79) -> std::result::Result<HeaderMap, ReqwestBuilderError> {
80    let mut header_map = HeaderMap::new();
81    let value = serde_json::to_value(headers)?;
82
83    let obj = value.as_object().ok_or_else(|| {
84        ReqwestBuilderError::SerializationError(
85            "Headers must serialize to a JSON object".to_string(),
86        )
87    })?;
88
89    for (key, val) in obj {
90        if let Some(val_str) = val.as_str() {
91            let header_name = http::HeaderName::from_bytes(key.as_bytes()).map_err(|e| {
92                ReqwestBuilderError::HeaderError {
93                    key: key.clone(),
94                    value: val_str.to_string(),
95                    source: format!("Invalid header name: {}", e),
96                }
97            })?;
98
99            let header_value = http::HeaderValue::from_str(val_str).map_err(|e| {
100                ReqwestBuilderError::HeaderError {
101                    key: key.clone(),
102                    value: val_str.to_string(),
103                    source: format!("Invalid header value: {}", e),
104                }
105            })?;
106
107            header_map.insert(header_name, header_value);
108        } else {
109            return Err(ReqwestBuilderError::HeaderError {
110                key: key.clone(),
111                value: val.to_string(),
112                source: "Header value must be a string".to_string(),
113            });
114        }
115    }
116
117    Ok(header_map)
118}
119
120/// Construct a URL by combining base URL and endpoint
121pub fn construct_url_safe(base_url: &url::Url, endpoint: &str) -> String {
122    let base_str = base_url.as_str().trim_end_matches('/');
123    let endpoint_str = endpoint.trim_start_matches('/');
124
125    if endpoint_str.is_empty() {
126        return base_str.to_string();
127    }
128
129    format!("{base_str}/{endpoint_str}")
130}