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