clawspec_core/client/parameters/
cookies.rs1use indexmap::IndexMap;
2use utoipa::openapi::Required;
3use utoipa::openapi::path::{Parameter, ParameterBuilder, ParameterIn};
4
5use super::param::{ParamValue, ParameterValue, ResolvedParamValue};
6use crate::client::error::ApiClientError;
7use crate::client::openapi::schema::Schemas;
8
9#[derive(Debug, Clone, Default)]
30pub struct CallCookies {
31 cookies: IndexMap<String, ResolvedParamValue>,
32 pub(in crate::client) schemas: Schemas,
33}
34
35impl CallCookies {
36 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn add_cookie<T: ParameterValue>(
75 mut self,
76 name: impl Into<String>,
77 value: impl Into<ParamValue<T>>,
78 ) -> Self {
79 let name = name.into();
80 let param_value = value.into();
81
82 let schema = self.schemas.add::<T>();
84
85 let resolved = ResolvedParamValue {
87 value: param_value
88 .as_query_value()
89 .expect("Cookie serialization should not fail"),
90 schema,
91 style: param_value.query_style(), };
93
94 self.cookies.insert(name, resolved);
95 self
96 }
97
98 pub fn merge(mut self, other: Self) -> Self {
117 self.schemas.merge(other.schemas);
119
120 for (name, value) in other.cookies {
122 self.cookies.insert(name, value);
123 }
124
125 self
126 }
127
128 pub fn is_empty(&self) -> bool {
142 self.cookies.is_empty()
143 }
144
145 pub fn len(&self) -> usize {
158 self.cookies.len()
159 }
160
161 pub(in crate::client) fn to_parameters(&self) -> impl Iterator<Item = Parameter> + '_ {
174 self.cookies.iter().map(|(name, resolved)| {
175 ParameterBuilder::new()
176 .name(name)
177 .parameter_in(ParameterIn::Cookie)
178 .required(Required::False) .schema(Some(resolved.schema.clone()))
180 .build()
181 })
182 }
183
184 pub(in crate::client) fn to_cookie_header(&self) -> Result<String, ApiClientError> {
194 if self.cookies.is_empty() {
195 return Ok(String::new());
196 }
197
198 let mut cookie_parts = Vec::new();
199
200 for (name, resolved) in &self.cookies {
201 let value = resolved.to_string_value()?;
202 cookie_parts.push(format!("{name}={value}"));
203 }
204
205 Ok(cookie_parts.join("; "))
206 }
207
208 pub(in crate::client) fn schemas(&self) -> &Schemas {
213 &self.schemas
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::client::ParamStyle;
221 use serde::{Deserialize, Serialize};
222 use utoipa::ToSchema;
223
224 #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
225 struct UserId(u64);
226
227 #[test]
228 fn test_new_empty_cookies() {
229 let cookies = CallCookies::new();
230
231 assert!(cookies.is_empty());
232 assert_eq!(cookies.len(), 0);
233 }
234
235 #[test]
236 fn test_add_string_cookie() {
237 let cookies = CallCookies::new().add_cookie("session_id", "abc123");
238
239 assert!(!cookies.is_empty());
240 assert_eq!(cookies.len(), 1);
241
242 let cookie_header = cookies
243 .to_cookie_header()
244 .expect("Should convert to cookie header");
245 assert_eq!(cookie_header, "session_id=abc123");
246 }
247
248 #[test]
249 fn test_add_multiple_cookies() {
250 let cookies = CallCookies::new()
251 .add_cookie("session_id", "abc123")
252 .add_cookie("user_id", 456)
253 .add_cookie("is_admin", true);
254
255 assert_eq!(cookies.len(), 3);
256
257 let cookie_header = cookies
258 .to_cookie_header()
259 .expect("Should convert to cookie header");
260
261 assert!(cookie_header.contains("session_id=abc123"));
263 assert!(cookie_header.contains("user_id=456"));
264 assert!(cookie_header.contains("is_admin=true"));
265
266 let parts: Vec<&str> = cookie_header.split("; ").collect();
268 assert_eq!(parts.len(), 3);
269 }
270
271 #[test]
272 fn test_add_custom_type_cookie() {
273 let cookies = CallCookies::new().add_cookie("user_id", UserId(42));
274
275 let cookie_header = cookies
276 .to_cookie_header()
277 .expect("Should convert to cookie header");
278 assert_eq!(cookie_header, "user_id=42");
279 }
280
281 #[test]
282 fn test_cookie_merge() {
283 let cookies1 = CallCookies::new()
284 .add_cookie("session_id", "abc123")
285 .add_cookie("user_id", 456);
286
287 let cookies2 = CallCookies::new()
288 .add_cookie("theme", "dark")
289 .add_cookie("user_id", 789); let merged = cookies1.merge(cookies2);
292
293 assert_eq!(merged.len(), 3);
294
295 let cookie_header = merged
296 .to_cookie_header()
297 .expect("Should convert to cookie header");
298
299 assert!(cookie_header.contains("session_id=abc123"));
300 assert!(cookie_header.contains("user_id=789")); assert!(cookie_header.contains("theme=dark"));
302 }
303
304 #[test]
305 fn test_cookies_to_parameters() {
306 let cookies = CallCookies::new()
307 .add_cookie("session_id", "abc123")
308 .add_cookie("user_id", 456);
309
310 let parameters: Vec<Parameter> = cookies.to_parameters().collect();
311
312 assert_eq!(parameters.len(), 2);
313
314 for param in ¶meters {
316 assert_eq!(param.parameter_in, ParameterIn::Cookie);
317 assert_eq!(param.required, Required::False);
318 assert!(param.schema.is_some());
319 assert!(param.name == "session_id" || param.name == "user_id");
320 }
321 }
322
323 #[test]
324 fn test_empty_cookies_header() {
325 let cookies = CallCookies::new();
326 let cookie_header = cookies
327 .to_cookie_header()
328 .expect("Should handle empty cookies");
329 assert_eq!(cookie_header, "");
330 }
331
332 #[test]
333 fn test_cookie_insertion_order_preserved() {
334 let cookies = CallCookies::new()
335 .add_cookie("first", "value1")
336 .add_cookie("second", "value2")
337 .add_cookie("third", "value3");
338
339 let cookie_header = cookies
340 .to_cookie_header()
341 .expect("Should convert to cookie header");
342
343 let parts: Vec<&str> = cookie_header.split("; ").collect();
345 assert_eq!(parts, vec!["first=value1", "second=value2", "third=value3"]);
346 }
347
348 #[test]
349 fn test_cookie_with_special_characters() {
350 let cookies = CallCookies::new()
351 .add_cookie("encoded_data", "hello world")
352 .add_cookie("special", "test@example.com");
353
354 let cookie_header = cookies
355 .to_cookie_header()
356 .expect("Should handle special characters");
357
358 assert!(cookie_header.contains("encoded_data=hello world"));
361 assert!(cookie_header.contains("special=test@example.com"));
362 }
363
364 #[test]
365 fn test_cookie_with_numeric_values() {
366 let cookies = CallCookies::new()
367 .add_cookie("count", 42)
368 .add_cookie("rate", 2.5);
369
370 let cookie_header = cookies
371 .to_cookie_header()
372 .expect("Should handle numeric values");
373
374 assert!(cookie_header.contains("count=42"));
375 assert!(cookie_header.contains("rate=2.5"));
376 }
377
378 #[test]
379 fn test_cookie_with_boolean_values() {
380 let cookies = CallCookies::new()
381 .add_cookie("is_logged_in", true)
382 .add_cookie("is_admin", false);
383
384 let cookie_header = cookies
385 .to_cookie_header()
386 .expect("Should handle boolean values");
387
388 assert!(cookie_header.contains("is_logged_in=true"));
389 assert!(cookie_header.contains("is_admin=false"));
390 }
391
392 #[test]
393 fn test_cookie_schemas_collection() {
394 let cookies = CallCookies::new()
395 .add_cookie("session_id", "abc123")
396 .add_cookie("user_id", UserId(42));
397
398 let schemas = cookies.schemas();
399
400 assert!(!schemas.schema_vec().is_empty());
402 }
403
404 #[test]
405 fn test_cookie_override_in_merge() {
406 let cookies1 = CallCookies::new()
407 .add_cookie("same_cookie", "original_value")
408 .add_cookie("unique_1", "value1");
409
410 let cookies2 = CallCookies::new()
411 .add_cookie("same_cookie", "new_value")
412 .add_cookie("unique_2", "value2");
413
414 let merged = cookies1.merge(cookies2);
415
416 let cookie_header = merged
417 .to_cookie_header()
418 .expect("Should convert merged cookies");
419
420 assert!(cookie_header.contains("same_cookie=new_value"));
422 assert!(cookie_header.contains("unique_1=value1"));
423 assert!(cookie_header.contains("unique_2=value2"));
424 assert_eq!(merged.len(), 3);
425 }
426
427 #[test]
428 fn test_cookie_with_array_values() {
429 let cookies = CallCookies::new()
430 .add_cookie("tags", vec!["rust", "web", "api"])
431 .add_cookie("ids", vec![1, 2, 3]);
432
433 let cookie_header = cookies
434 .to_cookie_header()
435 .expect("Should handle array values");
436
437 assert!(cookie_header.contains("tags=rust,web,api"));
439 assert!(cookie_header.contains("ids=1,2,3"));
440 }
441
442 #[test]
443 fn test_cookie_with_null_value() {
444 let cookies = CallCookies::new().add_cookie("optional", serde_json::Value::Null);
445
446 let cookie_header = cookies
447 .to_cookie_header()
448 .expect("Should handle null values");
449
450 assert!(cookie_header.contains("optional="));
452 }
453
454 #[test]
455 fn test_cookie_error_with_complex_object() {
456 use serde_json::json;
457
458 let mut cookies = CallCookies::new();
460
461 let complex_value = json!({
463 "nested": {
464 "object": "not supported in cookies"
465 }
466 });
467
468 let resolved = ResolvedParamValue {
469 value: complex_value,
470 schema: cookies.schemas.add::<serde_json::Value>(),
471 style: ParamStyle::Simple,
472 };
473
474 cookies
475 .cookies
476 .insert("complex_cookie".to_string(), resolved);
477
478 let result = cookies.to_cookie_header();
480 assert!(
481 result.is_err(),
482 "Complex objects should cause error in cookies"
483 );
484
485 match result {
486 Err(ApiClientError::UnsupportedParameterValue { .. }) => {
487 }
489 _ => panic!("Expected UnsupportedParameterValue error for complex object in cookie"),
490 }
491 }
492
493 #[test]
494 fn test_single_cookie_no_semicolon() {
495 let cookies = CallCookies::new().add_cookie("single", "value");
496
497 let cookie_header = cookies
498 .to_cookie_header()
499 .expect("Should convert single cookie");
500
501 assert_eq!(cookie_header, "single=value");
503 }
504
505 #[test]
506 fn test_cookie_with_empty_string_value() {
507 let cookies = CallCookies::new().add_cookie("empty", "");
508
509 let cookie_header = cookies
510 .to_cookie_header()
511 .expect("Should handle empty string values");
512
513 assert_eq!(cookie_header, "empty=");
514 }
515}