clawspec_core/client/
param.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3
4use serde::Serialize;
5use utoipa::openapi::path::ParameterStyle;
6use utoipa::openapi::{RefOr, Schema};
7use utoipa::{PartialSchema, ToSchema};
8
9use super::ApiClientError;
10
11/// A trait alias for types that can be used as parameter values.
12///
13/// This simplifies the generic constraints that are repeated throughout the codebase.
14/// All parameter values must be serializable, provide OpenAPI schemas, and be thread-safe.
15pub trait ParameterValue: Serialize + ToSchema + Debug + Send + Sync + Clone + 'static {}
16
17// Blanket implementation for all types that satisfy the constraints
18impl<T> ParameterValue for T where T: Serialize + ToSchema + Debug + Send + Sync + Clone + 'static {}
19
20/// Parameter styles supported by OpenAPI 3.1 specification.
21///
22/// These styles define how array values and complex parameters are serialized
23/// in strings according to the OpenAPI standard.
24///
25/// # Examples
26///
27/// ```rust
28/// use clawspec_core::{ParamStyle, ParamValue, CallQuery};
29///
30/// // Form style (default) - arrays are repeated: ?tags=rust&tags=web&tags=api
31/// let form_query = ParamValue::new(vec!["rust", "web", "api"]);
32/// assert_eq!(form_query.query_style(), ParamStyle::Form);
33///
34/// // Space delimited - arrays are joined with spaces: ?tags=rust%20web%20api
35/// let space_query = ParamValue::with_style(
36///     vec!["rust", "web", "api"],
37///     ParamStyle::SpaceDelimited
38/// );
39///
40/// // Pipe delimited - arrays are joined with pipes: ?tags=rust|web|api
41/// let pipe_query = ParamValue::with_style(
42///     vec!["rust", "web", "api"],
43///     ParamStyle::PipeDelimited
44/// );
45/// ```
46///
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ParamStyle {
49    /// Default style - will use Form for query parameters and Simple for path parameters
50    Default,
51    /// Form style: `param=value1&param=value2` (query default)
52    Form,
53    /// Simple style: `value1,value2` (path default)
54    Simple,
55    /// Space delimited: `param=value1 value2`
56    SpaceDelimited,
57    /// Pipe delimited: `param=value1|value2`
58    PipeDelimited,
59    /// Label style: `/users/.value` (path parameter with . prefix)
60    Label,
61    /// Matrix style: `/users/;name=value` (path parameter with ; prefix)
62    Matrix,
63    /// Deep object style: `?obj[key]=value` (query parameter for nested objects)
64    DeepObject,
65}
66
67impl From<ParamStyle> for Option<ParameterStyle> {
68    fn from(value: ParamStyle) -> Self {
69        let result = match value {
70            ParamStyle::Default => return None,
71            ParamStyle::Form => ParameterStyle::Form,
72            ParamStyle::Simple => ParameterStyle::Simple,
73            ParamStyle::SpaceDelimited => ParameterStyle::SpaceDelimited,
74            ParamStyle::PipeDelimited => ParameterStyle::PipeDelimited,
75            ParamStyle::Label => ParameterStyle::Label,
76            ParamStyle::Matrix => ParameterStyle::Matrix,
77            ParamStyle::DeepObject => ParameterStyle::DeepObject,
78        };
79        Some(result)
80    }
81}
82
83/// A parameter value with its serialization style
84#[derive(Debug, Clone)]
85pub struct ParamValue<T>
86where
87    T: Serialize + ToSchema + Debug + Send + Sync + Clone,
88{
89    /// The parameter value
90    pub value: T,
91    /// The serialization style
92    pub style: ParamStyle,
93}
94
95/// A resolved parameter value with its serialized JSON value, schema reference, and style.
96///
97/// This struct is used internally to represent a parameter that has been processed
98/// and is ready for use in OpenAPI generation and HTTP requests.
99#[derive(Debug, Clone)]
100pub(super) struct ResolvedParamValue {
101    /// The serialized JSON value of the parameter
102    pub value: serde_json::Value,
103    /// The OpenAPI schema reference for this parameter
104    pub schema: RefOr<Schema>,
105    /// The serialization style for this parameter
106    pub style: ParamStyle,
107}
108
109impl<T> ParamValue<T>
110where
111    T: Serialize + ToSchema + Debug + Send + Sync + Clone,
112{
113    /// Create a new parameter value with default style
114    pub fn new(value: T) -> Self {
115        Self {
116            value,
117            style: ParamStyle::Default,
118        }
119    }
120
121    /// Create a new parameter value with specified style
122    pub fn with_style(value: T, style: ParamStyle) -> Self {
123        Self { value, style }
124    }
125
126    /// Get the actual style to use for query parameters
127    pub fn query_style(&self) -> ParamStyle {
128        match self.style {
129            ParamStyle::Default => ParamStyle::Form,
130            style => style,
131        }
132    }
133
134    /// Get the actual style to use for path parameters
135    pub fn path_style(&self) -> ParamStyle {
136        match self.style {
137            ParamStyle::Default => ParamStyle::Simple,
138            style => style,
139        }
140    }
141
142    /// Get the actual style to use for header parameters
143    pub fn header_style(&self) -> ParamStyle {
144        match self.style {
145            ParamStyle::Default => ParamStyle::Simple,
146            style => style,
147        }
148    }
149
150    /// Converts the parameter to a JSON value for query string serialization.
151    ///
152    /// Returns `None` if the parameter should not be included in the query string.
153    pub fn as_query_value(&self) -> Option<serde_json::Value> {
154        serde_json::to_value(&self.value).ok()
155    }
156
157    /// Converts the parameter to a JSON value for header serialization.
158    pub fn as_header_value(&self) -> Result<serde_json::Value, ApiClientError> {
159        serde_json::to_value(&self.value).map_err(|e| ApiClientError::SerializationError {
160            message: format!("Failed to serialize header value: {e}"),
161        })
162    }
163
164    /// Resolves this parameter value into a complete parameter with schema reference.
165    ///
166    /// This method is used internally by query and path parameter systems to create
167    /// a resolved parameter that includes the serialized value, schema reference, and style.
168    pub(super) fn resolve<F>(&self, add_schema_fn: F) -> Option<ResolvedParamValue>
169    where
170        F: FnOnce(serde_json::Value) -> RefOr<Schema>,
171    {
172        self.as_query_value().map(|value| {
173            let schema = add_schema_fn(value.clone());
174            ResolvedParamValue {
175                value,
176                schema,
177                style: self.style,
178            }
179        })
180    }
181}
182
183impl ResolvedParamValue {
184    /// Converts a JSON value to a string representation.
185    ///
186    /// This helper method handles the common logic for converting individual JSON values
187    /// to their string representations.
188    fn json_value_to_string(value: &serde_json::Value) -> Result<String, ApiClientError> {
189        match value {
190            serde_json::Value::String(s) => Ok(s.clone()),
191            serde_json::Value::Number(n) => Ok(n.to_string()),
192            serde_json::Value::Bool(b) => Ok(b.to_string()),
193            serde_json::Value::Null => Ok(String::new()),
194            serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
195                Err(ApiClientError::UnsupportedParameterValue {
196                    message: "nested complex values not supported in parameters".to_string(),
197                    value: value.clone(),
198                })
199            }
200        }
201    }
202
203    /// Converts array items to string values.
204    ///
205    /// This helper method handles the common logic for converting array items
206    /// to their string representations.
207    fn array_to_string_values(arr: &[serde_json::Value]) -> Result<Vec<String>, ApiClientError> {
208        let mut result = Vec::with_capacity(arr.len());
209        for value in arr {
210            result.push(Self::json_value_to_string(value)?);
211        }
212        Ok(result)
213    }
214
215    /// Converts the JSON value to a string representation for use in URLs.
216    ///
217    /// This method handles both simple values and arrays according to the parameter style.
218    /// Arrays are serialized using the appropriate delimiter based on the style.
219    ///
220    /// # Returns
221    ///
222    /// - `Ok(String)` - The serialized string value
223    /// - `Err(ApiClientError)` - Error if the value cannot be serialized
224    pub(super) fn to_string_value(&self) -> Result<String, ApiClientError> {
225        match &self.value {
226            serde_json::Value::Array(arr) => {
227                let string_values = Self::array_to_string_values(arr)?;
228                let delimiter = match self.style {
229                    ParamStyle::Default | ParamStyle::Simple => ",",
230                    ParamStyle::Form => ",", // Form style uses comma for single values in paths
231                    ParamStyle::SpaceDelimited => " ",
232                    ParamStyle::PipeDelimited => "|",
233                    ParamStyle::Label => ",", // Label style uses comma for arrays
234                    ParamStyle::Matrix => ",", // Matrix style uses comma for arrays
235                    ParamStyle::DeepObject => {
236                        return Err(ApiClientError::UnsupportedParameterValue {
237                            message:
238                                "DeepObject style not supported for arrays, use objects instead"
239                                    .to_string(),
240                            value: self.value.clone(),
241                        });
242                    }
243                };
244                Ok(string_values.join(delimiter))
245            }
246            serde_json::Value::Object(_) => {
247                match self.style {
248                    ParamStyle::DeepObject => {
249                        // DeepObject style is handled differently in query parameters
250                        Err(ApiClientError::UnsupportedParameterValue {
251                            message: "DeepObject style objects require special handling in query parameters".to_string(),
252                            value: self.value.clone(),
253                        })
254                    }
255                    _ => Err(ApiClientError::UnsupportedParameterValue {
256                        message: "object values not supported in parameters".to_string(),
257                        value: self.value.clone(),
258                    }),
259                }
260            }
261            _ => Self::json_value_to_string(&self.value),
262        }
263    }
264
265    /// Converts the JSON value to a vector of strings for query parameter encoding.
266    ///
267    /// This method is specifically for query parameters where Form style arrays
268    /// are repeated as multiple parameters rather than joined with delimiters.
269    ///
270    /// # Returns
271    ///
272    /// - `Ok(Vec<String>)` - Vector of string values for query encoding
273    /// - `Err(ApiClientError)` - Error if the value cannot be serialized
274    pub(super) fn to_query_values(&self) -> Result<Vec<String>, ApiClientError> {
275        match &self.value {
276            serde_json::Value::Array(arr) => {
277                match self.style {
278                    ParamStyle::Default | ParamStyle::Form => {
279                        // Form style: repeat parameter name for each array item
280                        Self::array_to_string_values(arr)
281                    }
282                    _ => {
283                        // For other styles, join into a single value
284                        self.to_string_value().map(|s| vec![s])
285                    }
286                }
287            }
288            serde_json::Value::Object(_) => {
289                match self.style {
290                    ParamStyle::DeepObject => {
291                        // DeepObject style is handled differently in query parameters
292                        Err(ApiClientError::UnsupportedParameterValue {
293                            message: "DeepObject style objects require special handling in query parameters".to_string(),
294                            value: self.value.clone(),
295                        })
296                    }
297                    _ => Err(ApiClientError::UnsupportedParameterValue {
298                        message: "object values not supported in parameters".to_string(),
299                        value: self.value.clone(),
300                    }),
301                }
302            }
303            _ => Self::json_value_to_string(&self.value).map(|s| vec![s]),
304        }
305    }
306}
307
308impl<T> From<T> for ParamValue<T>
309where
310    T: Serialize + ToSchema + Debug + Send + Sync + Clone,
311{
312    fn from(value: T) -> Self {
313        Self::new(value)
314    }
315}
316
317// ToSchema implementations for generating OpenAPI schemas
318impl<T: ToSchema> ToSchema for ParamValue<T>
319where
320    T: Serialize + ToSchema + Debug + Send + Sync + Clone,
321{
322    fn name() -> Cow<'static, str> {
323        T::name()
324    }
325}
326
327impl<T: ToSchema> PartialSchema for ParamValue<T>
328where
329    T: Serialize + ToSchema + Debug + Send + Sync + Clone,
330{
331    fn schema() -> RefOr<Schema> {
332        T::schema()
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use serde::{Deserialize, Serialize};
340    use utoipa::ToSchema;
341    use utoipa::openapi::path::ParameterStyle;
342
343    #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
344    struct TestStruct {
345        id: u32,
346        name: String,
347    }
348
349    // Test ParamStyle enum and conversions
350    #[test]
351    fn test_param_style_from_conversion() {
352        assert_eq!(Option::<ParameterStyle>::from(ParamStyle::Default), None);
353        assert_eq!(
354            Option::<ParameterStyle>::from(ParamStyle::Form),
355            Some(ParameterStyle::Form)
356        );
357        assert_eq!(
358            Option::<ParameterStyle>::from(ParamStyle::Simple),
359            Some(ParameterStyle::Simple)
360        );
361        assert_eq!(
362            Option::<ParameterStyle>::from(ParamStyle::SpaceDelimited),
363            Some(ParameterStyle::SpaceDelimited)
364        );
365        assert_eq!(
366            Option::<ParameterStyle>::from(ParamStyle::PipeDelimited),
367            Some(ParameterStyle::PipeDelimited)
368        );
369        assert_eq!(
370            Option::<ParameterStyle>::from(ParamStyle::Label),
371            Some(ParameterStyle::Label)
372        );
373        assert_eq!(
374            Option::<ParameterStyle>::from(ParamStyle::Matrix),
375            Some(ParameterStyle::Matrix)
376        );
377        assert_eq!(
378            Option::<ParameterStyle>::from(ParamStyle::DeepObject),
379            Some(ParameterStyle::DeepObject)
380        );
381    }
382
383    #[test]
384    fn test_param_style_eq() {
385        assert_eq!(ParamStyle::Default, ParamStyle::Default);
386        assert_eq!(ParamStyle::Form, ParamStyle::Form);
387        assert_ne!(ParamStyle::Form, ParamStyle::Simple);
388    }
389
390    // Test ParamValue creation and methods
391    #[test]
392    fn test_param_value_new() {
393        let param = ParamValue::new(42);
394        assert_eq!(param.value, 42);
395        assert_eq!(param.style, ParamStyle::Default);
396    }
397
398    #[test]
399    fn test_param_value_with_style() {
400        let param = ParamValue::with_style("test", ParamStyle::Form);
401        assert_eq!(param.value, "test");
402        assert_eq!(param.style, ParamStyle::Form);
403    }
404
405    #[test]
406    fn test_param_value_from_conversion() {
407        let param: ParamValue<i32> = 42.into();
408        assert_eq!(param.value, 42);
409        assert_eq!(param.style, ParamStyle::Default);
410    }
411
412    // Test style resolution methods
413    #[test]
414    fn test_param_value_query_style() {
415        let default_param = ParamValue::new(42);
416        assert_eq!(default_param.query_style(), ParamStyle::Form);
417
418        let form_param = ParamValue::with_style(42, ParamStyle::Form);
419        assert_eq!(form_param.query_style(), ParamStyle::Form);
420
421        let simple_param = ParamValue::with_style(42, ParamStyle::Simple);
422        assert_eq!(simple_param.query_style(), ParamStyle::Simple);
423
424        let space_param = ParamValue::with_style(42, ParamStyle::SpaceDelimited);
425        assert_eq!(space_param.query_style(), ParamStyle::SpaceDelimited);
426
427        let pipe_param = ParamValue::with_style(42, ParamStyle::PipeDelimited);
428        assert_eq!(pipe_param.query_style(), ParamStyle::PipeDelimited);
429    }
430
431    #[test]
432    fn test_param_value_path_style() {
433        let default_param = ParamValue::new(42);
434        assert_eq!(default_param.path_style(), ParamStyle::Simple);
435
436        let form_param = ParamValue::with_style(42, ParamStyle::Form);
437        assert_eq!(form_param.path_style(), ParamStyle::Form);
438
439        let simple_param = ParamValue::with_style(42, ParamStyle::Simple);
440        assert_eq!(simple_param.path_style(), ParamStyle::Simple);
441    }
442
443    #[test]
444    fn test_param_value_header_style() {
445        let default_param = ParamValue::new(42);
446        assert_eq!(default_param.header_style(), ParamStyle::Simple);
447
448        let form_param = ParamValue::with_style(42, ParamStyle::Form);
449        assert_eq!(form_param.header_style(), ParamStyle::Form);
450
451        let simple_param = ParamValue::with_style(42, ParamStyle::Simple);
452        assert_eq!(simple_param.header_style(), ParamStyle::Simple);
453    }
454
455    // Test JSON value conversion methods
456    #[test]
457    fn test_param_value_as_query_value() {
458        let string_param = ParamValue::new("test");
459        let query_value = string_param.as_query_value().unwrap();
460        assert_eq!(query_value, serde_json::Value::String("test".to_string()));
461
462        let number_param = ParamValue::new(42);
463        let query_value = number_param.as_query_value().unwrap();
464        assert_eq!(query_value, serde_json::Value::Number(42.into()));
465
466        let bool_param = ParamValue::new(true);
467        let query_value = bool_param.as_query_value().unwrap();
468        assert_eq!(query_value, serde_json::Value::Bool(true));
469
470        let array_param = ParamValue::new(vec!["a", "b", "c"]);
471        let query_value = array_param.as_query_value().unwrap();
472        let expected = serde_json::json!(["a", "b", "c"]);
473        assert_eq!(query_value, expected);
474    }
475
476    #[test]
477    fn test_param_value_as_header_value() {
478        let string_param = ParamValue::new("test");
479        let header_value = string_param.as_header_value().unwrap();
480        assert_eq!(header_value, serde_json::Value::String("test".to_string()));
481
482        let number_param = ParamValue::new(42);
483        let header_value = number_param.as_header_value().unwrap();
484        assert_eq!(header_value, serde_json::Value::Number(42.into()));
485    }
486
487    #[test]
488    fn test_param_value_resolve() {
489        let param = ParamValue::new(42);
490        let resolved = param.resolve(|value| {
491            // Mock schema function
492            assert_eq!(value, serde_json::Value::Number(42.into()));
493            utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
494        });
495
496        assert!(resolved.is_some());
497        let resolved = resolved.unwrap();
498        assert_eq!(resolved.value, serde_json::Value::Number(42.into()));
499        assert_eq!(resolved.style, ParamStyle::Default);
500    }
501
502    #[test]
503    fn test_param_value_resolve_none_on_serialization_error() {
504        // This test simulates a case where as_query_value returns None
505        // In practice, most serializable types will succeed, but this tests the logic
506        let param = ParamValue::new(42); // This will succeed, so test the happy path
507        let resolved = param.resolve(|_| {
508            utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
509        });
510        assert!(resolved.is_some());
511    }
512
513    // Test ResolvedParamValue helper methods
514    #[test]
515    fn test_resolved_param_value_json_value_to_string() {
516        assert_eq!(
517            ResolvedParamValue::json_value_to_string(&serde_json::Value::String(
518                "test".to_string()
519            ))
520            .unwrap(),
521            "test"
522        );
523        assert_eq!(
524            ResolvedParamValue::json_value_to_string(&serde_json::Value::Number(42.into()))
525                .unwrap(),
526            "42"
527        );
528        assert_eq!(
529            ResolvedParamValue::json_value_to_string(&serde_json::Value::Bool(true)).unwrap(),
530            "true"
531        );
532        assert_eq!(
533            ResolvedParamValue::json_value_to_string(&serde_json::Value::Bool(false)).unwrap(),
534            "false"
535        );
536        assert_eq!(
537            ResolvedParamValue::json_value_to_string(&serde_json::Value::Null).unwrap(),
538            ""
539        );
540
541        // Test error cases
542        let array_result = ResolvedParamValue::json_value_to_string(&serde_json::json!(["a", "b"]));
543        assert!(array_result.is_err());
544
545        let object_result =
546            ResolvedParamValue::json_value_to_string(&serde_json::json!({"key": "value"}));
547        assert!(object_result.is_err());
548    }
549
550    #[test]
551    fn test_resolved_param_value_array_to_string_values() {
552        let arr = vec![
553            serde_json::Value::String("a".to_string()),
554            serde_json::Value::Number(42.into()),
555            serde_json::Value::Bool(true),
556        ];
557        let result = ResolvedParamValue::array_to_string_values(&arr).unwrap();
558        assert_eq!(result, vec!["a", "42", "true"]);
559
560        // Test error case with nested array
561        let nested_arr = vec![
562            serde_json::Value::String("a".to_string()),
563            serde_json::json!(["nested"]),
564        ];
565        let result = ResolvedParamValue::array_to_string_values(&nested_arr);
566        assert!(result.is_err());
567    }
568
569    // Test ResolvedParamValue string conversion methods
570    #[test]
571    fn test_resolved_param_value_to_string_value_simple_values() {
572        let resolved = ResolvedParamValue {
573            value: serde_json::Value::String("test".to_string()),
574            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
575            style: ParamStyle::Default,
576        };
577        assert_eq!(resolved.to_string_value().unwrap(), "test");
578
579        let resolved = ResolvedParamValue {
580            value: serde_json::Value::Number(42.into()),
581            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
582            style: ParamStyle::Default,
583        };
584        assert_eq!(resolved.to_string_value().unwrap(), "42");
585    }
586
587    #[test]
588    fn test_resolved_param_value_to_string_value_arrays() {
589        // Test Simple style (comma-separated)
590        let resolved = ResolvedParamValue {
591            value: serde_json::json!(["a", "b", "c"]),
592            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
593            style: ParamStyle::Simple,
594        };
595        assert_eq!(resolved.to_string_value().unwrap(), "a,b,c");
596
597        // Test SpaceDelimited style
598        let resolved = ResolvedParamValue {
599            value: serde_json::json!(["a", "b", "c"]),
600            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
601            style: ParamStyle::SpaceDelimited,
602        };
603        assert_eq!(resolved.to_string_value().unwrap(), "a b c");
604
605        // Test PipeDelimited style
606        let resolved = ResolvedParamValue {
607            value: serde_json::json!(["a", "b", "c"]),
608            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
609            style: ParamStyle::PipeDelimited,
610        };
611        assert_eq!(resolved.to_string_value().unwrap(), "a|b|c");
612
613        // Test Label style (comma-separated for arrays)
614        let resolved = ResolvedParamValue {
615            value: serde_json::json!(["a", "b", "c"]),
616            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
617            style: ParamStyle::Label,
618        };
619        assert_eq!(resolved.to_string_value().unwrap(), "a,b,c");
620
621        // Test Matrix style (comma-separated for arrays)
622        let resolved = ResolvedParamValue {
623            value: serde_json::json!(["a", "b", "c"]),
624            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
625            style: ParamStyle::Matrix,
626        };
627        assert_eq!(resolved.to_string_value().unwrap(), "a,b,c");
628
629        // Test Form style (comma-separated for single values)
630        let resolved = ResolvedParamValue {
631            value: serde_json::json!(["a", "b", "c"]),
632            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
633            style: ParamStyle::Form,
634        };
635        assert_eq!(resolved.to_string_value().unwrap(), "a,b,c");
636
637        // Test Default style (uses comma)
638        let resolved = ResolvedParamValue {
639            value: serde_json::json!(["a", "b", "c"]),
640            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
641            style: ParamStyle::Default,
642        };
643        assert_eq!(resolved.to_string_value().unwrap(), "a,b,c");
644    }
645
646    #[test]
647    fn test_resolved_param_value_to_string_value_object_error() {
648        let resolved = ResolvedParamValue {
649            value: serde_json::json!({"key": "value"}),
650            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
651            style: ParamStyle::Default,
652        };
653        let result = resolved.to_string_value();
654        assert!(result.is_err());
655        assert!(matches!(
656            result.unwrap_err(),
657            ApiClientError::UnsupportedParameterValue { .. }
658        ));
659    }
660
661    #[test]
662    fn test_resolved_param_value_deep_object_style_errors() {
663        // Test DeepObject style with array (should error)
664        let resolved = ResolvedParamValue {
665            value: serde_json::json!(["a", "b", "c"]),
666            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
667            style: ParamStyle::DeepObject,
668        };
669        let result = resolved.to_string_value();
670        assert!(result.is_err());
671        assert!(matches!(
672            result.unwrap_err(),
673            ApiClientError::UnsupportedParameterValue { .. }
674        ));
675
676        // Test DeepObject style with object (should error for to_string_value)
677        let resolved = ResolvedParamValue {
678            value: serde_json::json!({"key": "value"}),
679            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
680            style: ParamStyle::DeepObject,
681        };
682        let result = resolved.to_string_value();
683        assert!(result.is_err());
684        assert!(matches!(
685            result.unwrap_err(),
686            ApiClientError::UnsupportedParameterValue { .. }
687        ));
688    }
689
690    #[test]
691    fn test_resolved_param_value_to_query_values_simple_values() {
692        let resolved = ResolvedParamValue {
693            value: serde_json::Value::String("test".to_string()),
694            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
695            style: ParamStyle::Default,
696        };
697        assert_eq!(resolved.to_query_values().unwrap(), vec!["test"]);
698
699        let resolved = ResolvedParamValue {
700            value: serde_json::Value::Number(42.into()),
701            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
702            style: ParamStyle::Default,
703        };
704        assert_eq!(resolved.to_query_values().unwrap(), vec!["42"]);
705    }
706
707    #[test]
708    fn test_resolved_param_value_to_query_values_arrays() {
709        // Test Form style (repeated parameters)
710        let resolved = ResolvedParamValue {
711            value: serde_json::json!(["a", "b", "c"]),
712            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
713            style: ParamStyle::Form,
714        };
715        assert_eq!(resolved.to_query_values().unwrap(), vec!["a", "b", "c"]);
716
717        // Test Default style (same as Form for queries)
718        let resolved = ResolvedParamValue {
719            value: serde_json::json!(["a", "b", "c"]),
720            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
721            style: ParamStyle::Default,
722        };
723        assert_eq!(resolved.to_query_values().unwrap(), vec!["a", "b", "c"]);
724
725        // Test Simple style (single joined value)
726        let resolved = ResolvedParamValue {
727            value: serde_json::json!(["a", "b", "c"]),
728            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
729            style: ParamStyle::Simple,
730        };
731        assert_eq!(resolved.to_query_values().unwrap(), vec!["a,b,c"]);
732
733        // Test SpaceDelimited style (single joined value)
734        let resolved = ResolvedParamValue {
735            value: serde_json::json!(["a", "b", "c"]),
736            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
737            style: ParamStyle::SpaceDelimited,
738        };
739        assert_eq!(resolved.to_query_values().unwrap(), vec!["a b c"]);
740
741        // Test PipeDelimited style (single joined value)
742        let resolved = ResolvedParamValue {
743            value: serde_json::json!(["a", "b", "c"]),
744            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
745            style: ParamStyle::PipeDelimited,
746        };
747        assert_eq!(resolved.to_query_values().unwrap(), vec!["a|b|c"]);
748    }
749
750    #[test]
751    fn test_resolved_param_value_to_query_values_object_error() {
752        let resolved = ResolvedParamValue {
753            value: serde_json::json!({"key": "value"}),
754            schema: utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default())),
755            style: ParamStyle::Default,
756        };
757        let result = resolved.to_query_values();
758        assert!(result.is_err());
759        assert!(matches!(
760            result.unwrap_err(),
761            ApiClientError::UnsupportedParameterValue { .. }
762        ));
763    }
764
765    // Test ToSchema trait implementations
766    #[test]
767    fn test_param_value_to_schema_name() {
768        assert_eq!(ParamValue::<String>::name(), String::name());
769        assert_eq!(ParamValue::<i32>::name(), i32::name());
770        assert_eq!(ParamValue::<TestStruct>::name(), TestStruct::name());
771    }
772
773    #[test]
774    fn test_param_value_partial_schema() {
775        // Test that the schema is delegated to the inner type
776        let string_schema = ParamValue::<String>::schema();
777        let expected_schema = String::schema();
778        // We can't easily compare schemas directly, but we can verify the method doesn't panic
779        // and returns the same type structure
780        match (string_schema, expected_schema) {
781            (utoipa::openapi::RefOr::T(_), utoipa::openapi::RefOr::T(_)) => {}
782            (utoipa::openapi::RefOr::Ref(_), utoipa::openapi::RefOr::Ref(_)) => {}
783            _ => panic!("Schema types don't match"),
784        }
785    }
786
787    // Test complex scenarios with different types
788    #[test]
789    fn test_param_value_with_complex_types() {
790        let struct_param = ParamValue::new(TestStruct {
791            id: 123,
792            name: "test".to_string(),
793        });
794
795        // Test that it can be serialized to JSON
796        let json_value = struct_param.as_query_value().unwrap();
797        let expected = serde_json::json!({"id": 123, "name": "test"});
798        assert_eq!(json_value, expected);
799
800        // Test resolve functionality
801        let resolved = struct_param.resolve(|value| {
802            assert_eq!(value, expected);
803            utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
804        });
805        assert!(resolved.is_some());
806    }
807
808    #[test]
809    fn test_param_value_with_option_types() {
810        let some_param = ParamValue::new(Some(42));
811        let json_value = some_param.as_query_value().unwrap();
812        assert_eq!(json_value, serde_json::Value::Number(42.into()));
813
814        let none_param: ParamValue<Option<i32>> = ParamValue::new(None);
815        let json_value = none_param.as_query_value().unwrap();
816        assert_eq!(json_value, serde_json::Value::Null);
817    }
818
819    #[test]
820    fn test_param_value_with_mixed_array_types() {
821        // Test array with different numeric types
822        let mixed_numbers = vec![1, 2, 3];
823        let param = ParamValue::with_style(mixed_numbers, ParamStyle::SpaceDelimited);
824        let json_value = param.as_query_value().unwrap();
825        assert_eq!(json_value, serde_json::json!([1, 2, 3]));
826
827        let resolved = param
828            .resolve(|_| {
829                utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
830            })
831            .unwrap();
832
833        assert_eq!(resolved.to_string_value().unwrap(), "1 2 3");
834        assert_eq!(resolved.to_query_values().unwrap(), vec!["1 2 3"]);
835    }
836
837    // Test edge cases and error conditions
838    #[test]
839    fn test_param_value_empty_array() {
840        let empty_array: Vec<String> = vec![];
841        let param = ParamValue::new(empty_array);
842        let json_value = param.as_query_value().unwrap();
843        assert_eq!(json_value, serde_json::json!([]));
844
845        let resolved = param
846            .resolve(|_| {
847                utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
848            })
849            .unwrap();
850
851        assert_eq!(resolved.to_string_value().unwrap(), "");
852        assert_eq!(resolved.to_query_values().unwrap(), Vec::<String>::new());
853    }
854
855    #[test]
856    fn test_param_value_single_item_array() {
857        let single_item = vec!["only"];
858        let param = ParamValue::new(single_item);
859        let resolved = param
860            .resolve(|_| {
861                utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(Default::default()))
862            })
863            .unwrap();
864
865        assert_eq!(resolved.to_string_value().unwrap(), "only");
866        assert_eq!(resolved.to_query_values().unwrap(), vec!["only"]);
867    }
868
869    // Test the ParameterValue trait is properly implemented
870    #[test]
871    fn test_parameter_value_trait_implementation() {
872        fn accepts_parameter_value<T: ParameterValue>(_value: T) {}
873
874        // These should all compile without issues
875        accepts_parameter_value("string");
876        accepts_parameter_value(42i32);
877        accepts_parameter_value(true);
878        accepts_parameter_value(vec!["a", "b"]);
879        accepts_parameter_value(TestStruct {
880            id: 1,
881            name: "test".to_string(),
882        });
883    }
884}