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