clawspec_core/client/call/builder.rs
1use std::ops::{Range, RangeInclusive};
2
3use serde::Serialize;
4use utoipa::ToSchema;
5
6use super::ApiCall;
7use crate::client::parameters::{ParamValue, ParameterValue};
8use crate::client::response::ExpectedStatusCodes;
9use crate::client::{ApiClientError, CallBody, CallCookies, CallHeaders, CallQuery};
10
11impl ApiCall {
12 // =============================================================================
13 // OpenAPI Metadata Methods
14 // =============================================================================
15 pub fn with_operation_id(mut self, operation_id: impl Into<String>) -> Self {
16 self.metadata.operation_id = operation_id.into();
17 self
18 }
19
20 /// Sets the operation description for OpenAPI documentation.
21 ///
22 /// # Examples
23 ///
24 /// ```rust
25 /// # use clawspec_core::ApiClient;
26 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
27 /// let mut client = ApiClient::builder().build()?;
28 /// let call = client.get("/users")?.with_description("Retrieve all users");
29 /// # Ok(())
30 /// # }
31 /// ```
32 pub fn with_description(mut self, description: impl Into<String>) -> Self {
33 self.metadata.description = Some(description.into());
34 self
35 }
36
37 /// Sets the operation tags for OpenAPI categorization.
38 ///
39 /// # Examples
40 ///
41 /// ```rust
42 /// # use clawspec_core::ApiClient;
43 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
44 /// let mut client = ApiClient::builder().build()?;
45 /// let call = client.get("/users")?.with_tags(vec!["users", "admin"]);
46 /// // Also works with arrays, slices, or any IntoIterator
47 /// let call = client.get("/users")?.with_tags(["users", "admin"]);
48 /// # Ok(())
49 /// # }
50 /// ```
51 pub fn with_tags<I, T>(mut self, tags: I) -> Self
52 where
53 I: IntoIterator<Item = T>,
54 T: Into<String>,
55 {
56 self.metadata.tags = Some(tags.into_iter().map(|t| t.into()).collect());
57 self
58 }
59
60 /// Adds a single tag to the operation for OpenAPI categorization.
61 ///
62 /// # Examples
63 ///
64 /// ```rust
65 /// # use clawspec_core::ApiClient;
66 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
67 /// let mut client = ApiClient::builder().build()?;
68 /// let call = client.get("/users")?.with_tag("users").with_tag("admin");
69 /// # Ok(())
70 /// # }
71 /// ```
72 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
73 self.metadata
74 .tags
75 .get_or_insert_with(Vec::new)
76 .push(tag.into());
77 self
78 }
79
80 /// Sets a response description for the actual returned status code.
81 ///
82 /// This method allows you to document what the response means for your API endpoint.
83 /// The description will be applied to whatever status code is actually returned by the server
84 /// and included in the generated OpenAPI specification.
85 ///
86 /// # Examples
87 ///
88 /// ```rust
89 /// # use clawspec_core::ApiClient;
90 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
91 /// let mut client = ApiClient::builder().build()?;
92 /// let call = client.get("/users/{id}")?
93 /// .with_response_description("User details if found, or error information");
94 /// # Ok(())
95 /// # }
96 /// ```
97 pub fn with_response_description(mut self, description: impl Into<String>) -> Self {
98 self.response_description = Some(description.into());
99 self
100 }
101
102 /// Excludes this API call from OpenAPI collection and documentation generation.
103 ///
104 /// When called, this API call will be executed normally but will not appear
105 /// in the generated OpenAPI specification. This is useful for:
106 /// - Health check endpoints
107 /// - Debug/diagnostic endpoints
108 /// - Authentication/session management calls
109 /// - Test setup/teardown calls
110 /// - Internal utility endpoints
111 /// - Administrative endpoints not part of public API
112 ///
113 /// # Examples
114 ///
115 /// ```rust
116 /// # use clawspec_core::ApiClient;
117 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
118 /// let mut client = ApiClient::builder().build()?;
119 ///
120 /// // Health check that won't appear in OpenAPI spec
121 /// client
122 /// .get("/health")?
123 /// .without_collection()
124 /// .await?
125 /// .as_empty()
126 /// .await?;
127 ///
128 /// // Debug endpoint excluded from documentation
129 /// client
130 /// .get("/debug/status")?
131 /// .without_collection()
132 /// .await?
133 /// .as_text()
134 /// .await?;
135 /// # Ok(())
136 /// # }
137 /// ```
138 pub fn without_collection(mut self) -> Self {
139 self.skip_collection = true;
140 self
141 }
142
143 // =============================================================================
144 // Request Configuration Methods
145 // =============================================================================
146
147 pub fn with_query(mut self, query: CallQuery) -> Self {
148 self.query = query;
149 self
150 }
151
152 pub fn with_headers_option(mut self, headers: Option<CallHeaders>) -> Self {
153 self.headers = match (self.headers.take(), headers) {
154 (Some(existing), Some(new)) => Some(existing.merge(new)),
155 (existing, new) => existing.or(new),
156 };
157 self
158 }
159
160 /// Adds headers to the API call, merging with any existing headers.
161 ///
162 /// This is a convenience method that automatically wraps the headers in Some().
163 pub fn with_headers(self, headers: CallHeaders) -> Self {
164 self.with_headers_option(Some(headers))
165 }
166
167 /// Convenience method to add a single header.
168 ///
169 /// This method automatically handles type conversion and merges with existing headers.
170 /// If a header with the same name already exists, the new value will override it.
171 ///
172 /// # Examples
173 ///
174 /// ## Basic Usage
175 /// ```rust
176 /// # use clawspec_core::ApiClient;
177 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
178 /// let mut client = ApiClient::builder().build()?;
179 /// let call = client.get("/users")?
180 /// .with_header("Authorization", "Bearer token123")
181 /// .with_header("X-Request-ID", "abc-123-def");
182 /// # Ok(())
183 /// # }
184 /// ```
185 ///
186 /// ## Type Flexibility and Edge Cases
187 /// ```rust
188 /// # use clawspec_core::ApiClient;
189 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
190 /// let mut client = ApiClient::builder().build()?;
191 ///
192 /// // Different value types are automatically converted
193 /// let call = client.post("/api/data")?
194 /// .with_header("Content-Length", 1024_u64) // Numeric values
195 /// .with_header("X-Retry-Count", 3_u32) // Different numeric types
196 /// .with_header("X-Debug", true) // Boolean values
197 /// .with_header("X-Session-ID", "session-123"); // String values
198 ///
199 /// // Headers can be chained and overridden
200 /// let call = client.get("/protected")?
201 /// .with_header("Authorization", "Bearer old-token")
202 /// .with_header("Authorization", "Bearer new-token"); // Overrides previous value
203 /// # Ok(())
204 /// # }
205 /// ```
206 pub fn with_header<T: ParameterValue>(
207 self,
208 name: impl Into<String>,
209 value: impl Into<ParamValue<T>>,
210 ) -> Self {
211 let headers = CallHeaders::new().add_header(name, value);
212 self.with_headers(headers)
213 }
214
215 /// Adds cookies to the API call, merging with any existing cookies.
216 ///
217 /// This method accepts a `CallCookies` instance and merges it with any existing
218 /// cookies on the request. Cookies are sent in the HTTP Cookie header and can
219 /// be used for session management, authentication, and storing user preferences.
220 ///
221 /// # Examples
222 ///
223 /// ```rust
224 /// # use clawspec_core::{ApiClient, CallCookies};
225 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
226 /// let mut client = ApiClient::builder().build()?;
227 /// let cookies = CallCookies::new()
228 /// .add_cookie("session_id", "abc123")
229 /// .add_cookie("user_id", 456);
230 ///
231 /// let call = client.get("/dashboard")?
232 /// .with_cookies(cookies);
233 /// # Ok(())
234 /// # }
235 /// ```
236 pub fn with_cookies(mut self, cookies: CallCookies) -> Self {
237 self.cookies = match self.cookies.take() {
238 Some(existing) => Some(existing.merge(cookies)),
239 None => Some(cookies),
240 };
241 self
242 }
243
244 /// Convenience method to add a single cookie.
245 ///
246 /// This method automatically handles type conversion and merges with existing cookies.
247 /// If a cookie with the same name already exists, the new value will override it.
248 ///
249 /// # Examples
250 ///
251 /// ## Basic Usage
252 /// ```rust
253 /// # use clawspec_core::ApiClient;
254 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
255 /// let mut client = ApiClient::builder().build()?;
256 /// let call = client.get("/dashboard")?
257 /// .with_cookie("session_id", "abc123")
258 /// .with_cookie("user_id", 456);
259 /// # Ok(())
260 /// # }
261 /// ```
262 ///
263 /// ## Type Flexibility and Edge Cases
264 /// ```rust
265 /// # use clawspec_core::ApiClient;
266 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
267 /// let mut client = ApiClient::builder().build()?;
268 ///
269 /// // Different value types are automatically converted
270 /// let call = client.get("/preferences")?
271 /// .with_cookie("theme", "dark") // String values
272 /// .with_cookie("user_id", 12345_u64) // Numeric values
273 /// .with_cookie("is_premium", true) // Boolean values
274 /// .with_cookie("selected_tags", vec!["rust", "web"]); // Array values
275 ///
276 /// // Cookies can be chained and overridden
277 /// let call = client.get("/profile")?
278 /// .with_cookie("session_id", "old-session")
279 /// .with_cookie("session_id", "new-session"); // Overrides previous value
280 /// # Ok(())
281 /// # }
282 /// ```
283 pub fn with_cookie<T: ParameterValue>(
284 self,
285 name: impl Into<String>,
286 value: impl Into<ParamValue<T>>,
287 ) -> Self {
288 let cookies = CallCookies::new().add_cookie(name, value);
289 self.with_cookies(cookies)
290 }
291
292 /// Overrides the authentication for this specific request.
293 ///
294 /// This method allows you to use different authentication for a specific request,
295 /// overriding the default authentication configured on the API client.
296 ///
297 /// # Examples
298 ///
299 /// ```rust
300 /// use clawspec_core::{ApiClient, Authentication};
301 ///
302 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
303 /// // Client with default authentication
304 /// let mut client = ApiClient::builder()
305 /// .with_authentication(Authentication::Bearer("default-token".into()))
306 /// .build()?;
307 ///
308 /// // Use different authentication for a specific request
309 /// let response = client
310 /// .get("/admin/users")?
311 /// .with_authentication(Authentication::Bearer("admin-token".into()))
312 /// .await?;
313 ///
314 /// // Remove authentication for a public endpoint
315 /// let response = client
316 /// .get("/public/health")?
317 /// .with_authentication_none()
318 /// .await?;
319 /// # Ok(())
320 /// # }
321 /// ```
322 pub fn with_authentication(mut self, authentication: crate::client::Authentication) -> Self {
323 self.authentication = Some(authentication);
324 self
325 }
326
327 /// Removes authentication for this specific request.
328 ///
329 /// This is useful when making requests to public endpoints that don't require
330 /// authentication, even when the client has default authentication configured.
331 ///
332 /// # Examples
333 ///
334 /// ```rust
335 /// use clawspec_core::{ApiClient, Authentication};
336 ///
337 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
338 /// // Client with default authentication
339 /// let mut client = ApiClient::builder()
340 /// .with_authentication(Authentication::Bearer("token".into()))
341 /// .build()?;
342 ///
343 /// // Remove authentication for public endpoint
344 /// let response = client
345 /// .get("/public/status")?
346 /// .with_authentication_none()
347 /// .await?;
348 /// # Ok(())
349 /// # }
350 /// ```
351 pub fn with_authentication_none(mut self) -> Self {
352 self.authentication = None;
353 self
354 }
355
356 // =============================================================================
357 // Status Code Validation Methods
358 // =============================================================================
359
360 /// Sets the expected status codes for this request using an inclusive range.
361 ///
362 /// By default, status codes 200..500 are considered successful.
363 /// Use this method to customize which status codes should be accepted.
364 ///
365 /// # Examples
366 ///
367 /// ## Basic Usage
368 /// ```rust
369 /// # use clawspec_core::ApiClient;
370 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
371 /// let mut client = ApiClient::builder().build()?;
372 ///
373 /// // Accept only 200 to 201 (inclusive)
374 /// let call = client.post("/users")?.with_status_range_inclusive(200..=201);
375 ///
376 /// // Accept any 2xx status code
377 /// let call = client.get("/users")?.with_status_range_inclusive(200..=299);
378 /// # Ok(())
379 /// # }
380 /// ```
381 ///
382 /// ## Edge Cases
383 /// ```rust
384 /// # use clawspec_core::ApiClient;
385 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
386 /// let mut client = ApiClient::builder().build()?;
387 ///
388 /// // Single status code range (equivalent to with_expected_status)
389 /// let call = client.get("/health")?.with_status_range_inclusive(200..=200);
390 ///
391 /// // Accept both success and client error ranges
392 /// let call = client.delete("/users/123")?
393 /// .with_status_range_inclusive(200..=299)
394 /// .add_expected_status_range_inclusive(400..=404);
395 ///
396 /// // Handle APIs that return 2xx or 3xx for different success states
397 /// let call = client.post("/async-operation")?.with_status_range_inclusive(200..=302);
398 /// # Ok(())
399 /// # }
400 /// ```
401 pub fn with_status_range_inclusive(mut self, range: RangeInclusive<u16>) -> Self {
402 self.expected_status_codes = ExpectedStatusCodes::from_inclusive_range(range);
403 self
404 }
405
406 /// Sets the expected status codes for this request using an exclusive range.
407 ///
408 /// # Examples
409 ///
410 /// ```rust
411 /// # use clawspec_core::ApiClient;
412 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
413 /// let mut client = ApiClient::builder().build()?;
414 ///
415 /// // Accept 200 to 299 (200 included, 300 excluded)
416 /// let call = client.get("/users")?.with_status_range(200..300);
417 /// # Ok(())
418 /// # }
419 /// ```
420 pub fn with_status_range(mut self, range: Range<u16>) -> Self {
421 self.expected_status_codes = ExpectedStatusCodes::from_exclusive_range(range);
422 self
423 }
424
425 /// Sets a single expected status code for this request.
426 ///
427 /// # Examples
428 ///
429 /// ```rust
430 /// # use clawspec_core::ApiClient;
431 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
432 /// let mut client = ApiClient::builder().build()?;
433 ///
434 /// // Accept only 204 for DELETE operations
435 /// let call = client.delete("/users/123")?.with_expected_status(204);
436 /// # Ok(())
437 /// # }
438 /// ```
439 pub fn with_expected_status(mut self, status: u16) -> Self {
440 self.expected_status_codes = ExpectedStatusCodes::from_single(status);
441 self
442 }
443
444 /// Adds an additional expected status code to the existing set.
445 ///
446 /// # Examples
447 ///
448 /// ```rust
449 /// # use clawspec_core::ApiClient;
450 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
451 /// let mut client = ApiClient::builder().build()?;
452 ///
453 /// // Accept 200..299 and also 404
454 /// let call = client.get("/users")?.with_status_range_inclusive(200..=299).add_expected_status(404);
455 /// # Ok(())
456 /// # }
457 /// ```
458 pub fn add_expected_status(mut self, status: u16) -> Self {
459 self.expected_status_codes = self.expected_status_codes.add_expected_status(status);
460 self
461 }
462
463 /// Adds an additional expected status range (inclusive) to the existing set.
464 ///
465 /// # Examples
466 ///
467 /// ```rust
468 /// # use clawspec_core::ApiClient;
469 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
470 /// let mut client = ApiClient::builder().build()?;
471 ///
472 /// // Accept 200..=204 and also 400..=402
473 /// let call = client.post("/users")?.with_status_range_inclusive(200..=204).add_expected_status_range_inclusive(400..=402);
474 /// # Ok(())
475 /// # }
476 /// ```
477 pub fn add_expected_status_range_inclusive(mut self, range: RangeInclusive<u16>) -> Self {
478 self.expected_status_codes = self.expected_status_codes.add_expected_range(range);
479 self
480 }
481
482 /// Adds an additional expected status range (exclusive) to the existing set.
483 ///
484 /// # Examples
485 ///
486 /// ```rust
487 /// # use clawspec_core::ApiClient;
488 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
489 /// let mut client = ApiClient::builder().build()?;
490 ///
491 /// // Accept 200..=204 and also 400..403
492 /// let call = client.post("/users")?.with_status_range_inclusive(200..=204).add_expected_status_range(400..403);
493 /// # Ok(())
494 /// # }
495 /// ```
496 pub fn add_expected_status_range(mut self, range: Range<u16>) -> Self {
497 self.expected_status_codes = self.expected_status_codes.add_exclusive_range(range);
498 self
499 }
500
501 /// Convenience method to accept only 2xx status codes (200..300).
502 ///
503 /// # Examples
504 ///
505 /// ```rust
506 /// # use clawspec_core::ApiClient;
507 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
508 /// let mut client = ApiClient::builder().build()?;
509 /// let call = client.get("/users")?.with_success_only();
510 /// # Ok(())
511 /// # }
512 /// ```
513 pub fn with_success_only(self) -> Self {
514 self.with_status_range(200..300)
515 }
516
517 /// Convenience method to accept 2xx and 4xx status codes (200..500, excluding 3xx).
518 ///
519 /// # Examples
520 ///
521 /// ```rust
522 /// # use clawspec_core::ApiClient;
523 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
524 /// let mut client = ApiClient::builder().build()?;
525 /// let call = client.post("/users")?.with_client_errors();
526 /// # Ok(())
527 /// # }
528 /// ```
529 pub fn with_client_errors(self) -> Self {
530 self.with_status_range_inclusive(200..=299)
531 .add_expected_status_range_inclusive(400..=499)
532 }
533
534 /// Sets the expected status codes using an `ExpectedStatusCodes` instance.
535 ///
536 /// This method allows you to pass pre-configured `ExpectedStatusCodes` instances,
537 /// which is particularly useful with the `expected_status_codes!` macro.
538 ///
539 /// # Examples
540 ///
541 /// ```rust
542 /// use clawspec_core::{ApiClient, expected_status_codes};
543 ///
544 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
545 /// let mut client = ApiClient::builder().build()?;
546 ///
547 /// // Using the macro with with_expected_status_codes
548 /// let call = client.get("/users")?
549 /// .with_expected_status_codes(expected_status_codes!(200-299));
550 ///
551 /// // Using manually created ExpectedStatusCodes
552 /// let codes = clawspec_core::ExpectedStatusCodes::from_inclusive_range(200..=204)
553 /// .add_expected_status(404);
554 /// let call = client.get("/items")?.with_expected_status_codes(codes);
555 /// # Ok(())
556 /// # }
557 /// ```
558 pub fn with_expected_status_codes(mut self, codes: ExpectedStatusCodes) -> Self {
559 self.expected_status_codes = codes;
560 self
561 }
562
563 /// Sets expected status codes from a single `http::StatusCode`.
564 ///
565 /// This method provides **compile-time validation** of status codes through the type system.
566 /// Unlike the `u16` variants, this method does not perform runtime validation since
567 /// `http::StatusCode` guarantees valid HTTP status codes at compile time.
568 ///
569 /// # Example
570 ///
571 /// ```rust
572 /// use clawspec_core::ApiClient;
573 /// use http::StatusCode;
574 ///
575 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
576 /// let mut client = ApiClient::builder().build()?;
577 ///
578 /// let call = client.get("/users")?
579 /// .with_expected_status_code(StatusCode::OK);
580 /// # Ok(())
581 /// # }
582 /// ```
583 pub fn with_expected_status_code(self, status: http::StatusCode) -> Self {
584 self.with_expected_status_codes(ExpectedStatusCodes::from_status_code(status))
585 }
586
587 /// Sets expected status codes from a range of `http::StatusCode`.
588 ///
589 /// This method provides **compile-time validation** of status codes through the type system.
590 /// Unlike the `u16` variants, this method does not perform runtime validation since
591 /// `http::StatusCode` guarantees valid HTTP status codes at compile time.
592 ///
593 /// # Example
594 ///
595 /// ```rust
596 /// use clawspec_core::ApiClient;
597 /// use http::StatusCode;
598 ///
599 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
600 /// let mut client = ApiClient::builder().build()?;
601 ///
602 /// let call = client.get("/users")?
603 /// .with_expected_status_code_range(StatusCode::OK..=StatusCode::NO_CONTENT);
604 /// # Ok(())
605 /// # }
606 /// ```
607 pub fn with_expected_status_code_range(self, range: RangeInclusive<http::StatusCode>) -> Self {
608 self.with_expected_status_codes(ExpectedStatusCodes::from_status_code_range_inclusive(
609 range,
610 ))
611 }
612
613 // =============================================================================
614 // Request Body Methods
615 // =============================================================================
616
617 /// Sets the request body to JSON.
618 ///
619 /// This method serializes the provided data as JSON and sets the
620 /// Content-Type header to `application/json`.
621 ///
622 /// # Examples
623 ///
624 /// ```rust
625 /// # use clawspec_core::ApiClient;
626 /// # use serde::Serialize;
627 /// # use utoipa::ToSchema;
628 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
629 /// #[derive(Serialize, ToSchema)]
630 /// struct CreateUser {
631 /// name: String,
632 /// email: String,
633 /// }
634 ///
635 /// let mut client = ApiClient::builder().build()?;
636 /// let user_data = CreateUser {
637 /// name: "John Doe".to_string(),
638 /// email: "john@example.com".to_string(),
639 /// };
640 ///
641 /// let call = client.post("/users")?.json(&user_data)?;
642 /// # Ok(())
643 /// # }
644 /// ```
645 pub fn json<T>(mut self, t: &T) -> Result<Self, ApiClientError>
646 where
647 T: Serialize + ToSchema + 'static,
648 {
649 let body = CallBody::json(t)?;
650 self.body = Some(body);
651 Ok(self)
652 }
653
654 /// Sets the request body to form-encoded data.
655 ///
656 /// This method serializes the provided data as `application/x-www-form-urlencoded`
657 /// and sets the appropriate Content-Type header.
658 ///
659 /// # Examples
660 ///
661 /// ```rust
662 /// # use clawspec_core::ApiClient;
663 /// # use serde::Serialize;
664 /// # use utoipa::ToSchema;
665 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
666 /// #[derive(Serialize, ToSchema)]
667 /// struct LoginForm {
668 /// username: String,
669 /// password: String,
670 /// }
671 ///
672 /// let mut client = ApiClient::builder().build()?;
673 /// let form_data = LoginForm {
674 /// username: "user@example.com".to_string(),
675 /// password: "secret".to_string(),
676 /// };
677 ///
678 /// let call = client.post("/login")?.form(&form_data)?;
679 /// # Ok(())
680 /// # }
681 /// ```
682 pub fn form<T>(mut self, t: &T) -> Result<Self, ApiClientError>
683 where
684 T: Serialize + ToSchema + 'static,
685 {
686 let body = CallBody::form(t)?;
687 self.body = Some(body);
688 Ok(self)
689 }
690
691 /// Sets the request body to raw binary data with a custom content type.
692 ///
693 /// This method allows you to send arbitrary binary data with a specified
694 /// content type. This is useful for sending data that doesn't fit into
695 /// the standard JSON or form categories.
696 ///
697 /// # Examples
698 ///
699 /// ```rust
700 /// # use clawspec_core::ApiClient;
701 /// # use headers::ContentType;
702 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
703 /// let mut client = ApiClient::builder().build()?;
704 /// // Send XML data
705 /// let xml_data = r#"<?xml version="1.0"?><user><name>John</name></user>"#;
706 /// let call = client.post("/import")?
707 /// .raw(xml_data.as_bytes().to_vec(), ContentType::xml());
708 ///
709 /// // Send binary file
710 /// let binary_data = vec![0xFF, 0xFE, 0xFD];
711 /// let call = client.post("/upload")?
712 /// .raw(binary_data, ContentType::octet_stream());
713 /// # Ok(())
714 /// # }
715 /// ```
716 pub fn raw(mut self, data: Vec<u8>, content_type: headers::ContentType) -> Self {
717 let body = CallBody::raw(data, content_type);
718 self.body = Some(body);
719 self
720 }
721
722 /// Sets the request body to plain text.
723 ///
724 /// This is a convenience method for sending plain text data with
725 /// `text/plain` content type.
726 ///
727 /// # Examples
728 ///
729 /// ```rust
730 /// # use clawspec_core::ApiClient;
731 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
732 /// let mut client = ApiClient::builder().build()?;
733 /// let call = client.post("/notes")?.text("This is a plain text note");
734 /// # Ok(())
735 /// # }
736 /// ```
737 pub fn text(mut self, text: &str) -> Self {
738 let body = CallBody::text(text);
739 self.body = Some(body);
740 self
741 }
742
743 /// Sets the request body to multipart/form-data.
744 ///
745 /// This method creates a multipart body with a generated boundary and supports
746 /// both text fields and file uploads. This is commonly used for file uploads
747 /// or when combining different types of data in a single request.
748 ///
749 /// # Examples
750 ///
751 /// ```rust
752 /// # use clawspec_core::ApiClient;
753 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
754 /// let mut client = ApiClient::builder().build()?;
755 /// let parts = vec![
756 /// ("title", "My Document"),
757 /// ("file", "file content here"),
758 /// ];
759 /// let call = client.post("/upload")?.multipart(parts);
760 /// # Ok(())
761 /// # }
762 /// ```
763 pub fn multipart(mut self, parts: Vec<(&str, &str)>) -> Self {
764 let body = CallBody::multipart(parts);
765 self.body = Some(body);
766 self
767 }
768}
769
770// Call