azure_speech/connector/
utils.rs1use crate::Headers;
2
3static CRLF: &str = "\r\n";
4static HEADER_JSON_SEPARATOR: &str = "\r\n\r\n";
5
6pub fn make_text_payload(headers: Headers, data: Option<&str>) -> String {
7 let headers = transform_headers_to_string(headers);
8 let data = data.map_or("", |d| d);
9
10 format!("{}{CRLF}{}", headers, data)
11}
12
13pub fn make_binary_payload(headers: Headers, data: Option<&[u8]>) -> Vec<u8> {
14 let headers = transform_headers_to_string(headers);
15
16 let data_length = if let Some(d) = data { d.len() } else { 0 };
17
18 let header_buffer: Vec<_> = headers.bytes().collect();
19 let header_length = header_buffer.len();
20 let mut payload = vec![0; 2 + header_length + data_length];
21 payload[0] = ((header_length >> 8) & 0xff) as u8;
22 payload[1] = (header_length & 0xff) as u8;
23 payload[2..2 + header_length].copy_from_slice(&header_buffer);
24
25 if let Some(d) = data {
26 payload[2 + header_length..].copy_from_slice(d);
27 }
28
29 payload
30}
31
32pub fn extract_headers_and_data_from_binary_message(
33 data: &[u8],
34) -> Result<(Headers, Option<Vec<u8>>), crate::Error> {
35 if data.len() < 2 {
36 return Err(crate::Error::ParseError(
37 "binary message too short".to_string(),
38 ));
39 }
40
41 let header_length = ((data[0] as usize) << 8) + data[1] as usize;
42
43 if data.len() < 2 + header_length {
44 return Err(crate::Error::ParseError(format!(
45 "binary header length {} exceeds data len {}",
46 header_length, data.len()
47 )));
48 }
49
50 let headers = std::str::from_utf8(&data[2..2 + header_length])
51 .map_err(|_| crate::Error::ParseError("Error parsing headers".to_string()))?;
52 let data = if header_length + 2 < data.len() {
53 Some(data[2 + header_length..].to_vec())
54 } else {
55 None
56 };
57
58 Ok((explode_headers_message(headers), data))
59}
60
61pub fn extract_headers_and_data_from_text_message(
62 text: &str,
63) -> Result<(Headers, Option<String>), crate::Error> {
64 let mut split_response = text.split(HEADER_JSON_SEPARATOR);
65
66 let headers = explode_headers_message(split_response.next().unwrap_or_default());
67
68 Ok((headers, split_response.next().map(|x| x.to_string())))
69}
70
71fn transform_headers_to_string(map: Headers) -> String {
72 let mut headers = String::new();
73 for (content_type, value) in map {
74 headers.push_str(format!("{content_type}:{value}{CRLF}").as_str());
75 }
76
77 headers
78}
79
80fn explode_headers_message(headers: &str) -> Headers {
85 headers
86 .split(CRLF)
87 .map(|x| {
88 let mut split = x.split(":");
89 (
90 split.next().unwrap_or("").to_string(),
91 split.next().unwrap_or("").to_string(),
92 )
93 })
94 .filter(|(k, _)| !k.is_empty())
95 .collect()
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn explode_message() {
104 let text = "X-RequestId:91067ed0-bd0d-4682-891f-446a95996c19\r\nContent-Type:application/json; charset=utf-8\r\nPath:audio.metadata\r\n\r\n{\"Metadata\": [{\"Type\": \"SessionEnd\",\"Data\": {\"Offset\": 11250000}}]}";
105 let result = extract_headers_and_data_from_text_message(text);
106 match result {
107 Ok((headers, data)) => {
108 assert_eq!(
109 headers,
110 vec![
111 (
112 "X-RequestId".to_string(),
113 "91067ed0-bd0d-4682-891f-446a95996c19".to_string()
114 ),
115 (
116 "Content-Type".to_string(),
117 "application/json; charset=utf-8".to_string()
118 ),
119 ("Path".to_string(), "audio.metadata".to_string()),
120 ]
121 );
122 assert_eq!(data, Some("{\"Metadata\": [{\"Type\": \"SessionEnd\",\"Data\": {\"Offset\": 11250000}}]}".to_string()));
123 }
124 Err(e) => {
125 panic!("Error: {:?}", e);
126 }
127 }
128 }
129
130 #[test]
131 fn explode_headers_message_returns_correct_pairs_for_valid_input() {
132 let headers = "X-RequestId:5FF045681350489AAF1CD740EE5ACDDD\r\nPath:turn.start\r\nContent-Type:application/json; charset=utf-8";
133 let result = explode_headers_message(headers);
134 assert_eq!(
135 result,
136 vec![
137 (
138 "X-RequestId".to_string(),
139 "5FF045681350489AAF1CD740EE5ACDDD".to_string()
140 ),
141 ("Path".to_string(), "turn.start".to_string()),
142 (
143 "Content-Type".to_string(),
144 "application/json; charset=utf-8".to_string()
145 ),
146 ]
147 );
148 }
149
150 #[test]
151 fn explode_headers_message_returns_empty_vector_for_empty_input() {
152 let headers = "";
153 let result = explode_headers_message(headers);
154 assert_eq!(result, Vec::<(String, String)>::new());
155 }
156
157 #[test]
158 fn explode_headers_message_ignores_lines_without_colon() {
159 let headers =
160 "X-RequestId:5FF045681350489AAF1CD740EE5ACDDD\r\nInvalidLine\r\nPath:turn.start";
161 let result = explode_headers_message(headers);
162 assert_eq!(
163 result,
164 vec![
165 (
166 "X-RequestId".to_string(),
167 "5FF045681350489AAF1CD740EE5ACDDD".to_string()
168 ),
169 ("InvalidLine".to_string(), "".to_string()),
170 ("Path".to_string(), "turn.start".to_string()),
171 ]
172 );
173 }
174
175 #[test]
176 fn explode_headers_message_handles_lines_with_multiple_colons() {
177 let headers = "X-RequestId:5FF045681350489AAF1CD740EE5ACDDD\r\nPath:turn.start\r\nContent-Type:application/json; charset=utf-8\r\nMulti:Part:Header";
178 let result = explode_headers_message(headers);
179 assert_eq!(
180 result,
181 vec![
182 (
183 "X-RequestId".to_string(),
184 "5FF045681350489AAF1CD740EE5ACDDD".to_string()
185 ),
186 ("Path".to_string(), "turn.start".to_string()),
187 (
188 "Content-Type".to_string(),
189 "application/json; charset=utf-8".to_string()
190 ),
191 ("Multi".to_string(), "Part".to_string()),
192 ]
193 );
194 }
195
196 #[test]
197 fn extract_headers_and_data_from_binary_message_rejects_short_frames() {
198 let data = [0u8, 10, 1, 2, 3];
200 let res = extract_headers_and_data_from_binary_message(&data);
201 assert!(matches!(res, Err(crate::Error::ParseError(_))));
202 }
203}