diff --git a/gotcha/src/openapi/schematic.rs b/gotcha/src/openapi/schematic.rs index 9eba705..cea9b35 100644 --- a/gotcha/src/openapi/schematic.rs +++ b/gotcha/src/openapi/schematic.rs @@ -413,10 +413,13 @@ impl ParameterProvider for Path<(T1, T2)> { } impl ParameterProvider for Path { - fn generate(_url: String) -> Either, RequestBody> { + fn generate(url: String) -> Either, RequestBody> { let mut ret = vec![]; let mut schema = T::generate_schema(); + + // Check if this is a struct with properties or a simple type if let Some(mut properties) = schema.schema.extras.remove("properties") { + // Case 1: Struct with properties - each property becomes a path parameter if let Some(properties) = properties.as_object_mut() { properties.iter_mut().for_each(|(key, value)| { let schema = serde_json::from_value(value.clone()).unwrap(); @@ -424,6 +427,21 @@ impl ParameterProvider for Path { ret.push(param); }) } + } else { + // Case 2: Simple type like Uuid - extract parameter name from URL + let pattern = regex::Regex::new(r":([^/]+)").unwrap(); + let param_names_in_path: Vec = pattern.captures_iter(&url).map(|digits| digits.get(1).unwrap().as_str().to_string()).collect(); + + if let Some(param_name) = param_names_in_path.first() { + let param = build_param( + param_name.clone(), + ParameterIn::Path, + T::required(), + T::generate_schema().schema, + T::doc(), + ); + ret.push(param); + } } Either::Left(ret) } diff --git a/gotcha/tests/test_path_params.rs b/gotcha/tests/test_path_params.rs new file mode 100644 index 0000000..29c4004 --- /dev/null +++ b/gotcha/tests/test_path_params.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "openapi")] +#[test] +fn test_path_uuid_parameter() { + use either::Either; + use gotcha::{ParameterProvider, Path}; + use uuid::Uuid; + + // Test that Path generates a parameter + let url = "/users/:user_id".to_string(); + let result = as ParameterProvider>::generate(url); + + match result { + Either::Left(params) => { + assert_eq!(params.len(), 1, "Should generate exactly one parameter"); + assert_eq!(params[0].name, "user_id", "Parameter name should be 'user_id'"); + assert_eq!(params[0].required, Some(true), "Path parameter should be required"); + + // Verify it's a path parameter + use oas::ParameterIn; + assert!(matches!(params[0]._in, ParameterIn::Path), "Should be a path parameter"); + } + Either::Right(_) => { + panic!("Path should generate parameters, not a request body"); + } + } +} + +#[cfg(feature = "openapi")] +#[test] +fn test_path_tuple_parameter() { + use either::Either; + use gotcha::{ParameterProvider, Path}; + use uuid::Uuid; + + // Test that Path<(Uuid,)> also works (this should already work) + let url = "/users/:user_id".to_string(); + let result = as ParameterProvider>::generate(url); + + match result { + Either::Left(params) => { + assert_eq!(params.len(), 1, "Should generate exactly one parameter"); + assert_eq!(params[0].name, "user_id", "Parameter name should be 'user_id'"); + assert_eq!(params[0].required, Some(true), "Path parameter should be required"); + } + Either::Right(_) => { + panic!("Path<(Uuid,)> should generate parameters, not a request body"); + } + } +} + +#[cfg(feature = "openapi")] +#[test] +fn test_multiple_path_params() { + use either::Either; + use gotcha::{ParameterProvider, Path}; + use uuid::Uuid; + + // Test multiple parameters with Path<(Uuid, String)> + let url = "/users/:user_id/posts/:post_id".to_string(); + let result = as ParameterProvider>::generate(url); + + match result { + Either::Left(params) => { + assert_eq!(params.len(), 2, "Should generate two parameters"); + assert_eq!(params[0].name, "user_id", "First parameter name should be 'user_id'"); + assert_eq!(params[1].name, "post_id", "Second parameter name should be 'post_id'"); + } + Either::Right(_) => { + panic!("Path<(Uuid, String)> should generate parameters, not a request body"); + } + } +} \ No newline at end of file