Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion gotcha/src/openapi/schematic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,17 +413,35 @@ impl<T1: Schematic, T2: Schematic> ParameterProvider for Path<(T1, T2)> {
}

impl<T: Schematic> ParameterProvider for Path<T> {
fn generate(_url: String) -> Either<Vec<Parameter>, RequestBody> {
fn generate(url: String) -> Either<Vec<Parameter>, 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();
let param = build_param(key.to_string(), ParameterIn::Path, T::required(), schema, T::doc());
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<String> = 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)
}
Expand Down
72 changes: 72 additions & 0 deletions gotcha/tests/test_path_params.rs
Original file line number Diff line number Diff line change
@@ -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<Uuid> generates a parameter
let url = "/users/:user_id".to_string();
let result = <Path<Uuid> 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<Uuid> 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 = <Path<(Uuid,)> 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 = <Path<(Uuid, String)> 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");
}
}
}
Loading