Skip to content

Commit 22f6fce

Browse files
committed
Feat path params OpenAPI docs generation
1 parent d46282d commit 22f6fce

File tree

3 files changed

+44
-80
lines changed

3 files changed

+44
-80
lines changed

aiscript-runtime/src/ast/mod.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,11 @@ impl HttpMethod {
2121
}
2222
}
2323

24-
#[derive(Debug, Clone)]
25-
pub struct PathParameter {
26-
pub name: String, // Parameter name (e.g., "id")
27-
pub param_type: String, // Parameter type (e.g., "int")
28-
}
29-
3024
#[derive(Debug, Clone)]
3125
pub struct PathSpec {
3226
pub method: HttpMethod,
3327
pub path: String,
34-
pub params: Vec<PathParameter>,
28+
pub params: Vec<String>,
3529
}
3630

3731
#[derive(Debug, Default)]
@@ -105,7 +99,7 @@ pub struct Endpoint {
10599
pub struct Route {
106100
pub annotation: RouteAnnotation,
107101
pub prefix: String,
108-
pub params: Vec<PathParameter>,
102+
pub params: Vec<String>,
109103
pub endpoints: Vec<Endpoint>,
110104
pub docs: String,
111105
}

aiscript-runtime/src/openapi/mod.rs

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ use oas3::{
1111
};
1212
use std::collections::BTreeMap;
1313

14-
use crate::ast::{
15-
BodyKind, Endpoint, Field, FieldType, HttpMethod, PathParameter, PathSpec, Route,
16-
};
14+
use crate::ast::{BodyKind, Endpoint, Field, FieldType, HttpMethod, PathSpec, Route};
1715

1816
pub struct OpenAPIGenerator;
1917

@@ -120,7 +118,7 @@ impl OpenAPIGenerator {
120118
fn create_path_item(route: &Route, endpoint: &Endpoint, path_spec: &PathSpec) -> PathItem {
121119
let mut path_item = PathItem {
122120
summary: Some(endpoint.docs.clone()),
123-
parameters: Self::create_path_parameters(&path_spec.params),
121+
parameters: Self::create_path_parameters(endpoint, &path_spec.params),
124122
..Default::default()
125123
};
126124

@@ -170,7 +168,10 @@ impl OpenAPIGenerator {
170168
}
171169
}
172170

173-
let mut parameters = Self::create_path_parameters(&path_spec.params);
171+
// Create path parameters using our enhanced function that leverages endpoint.path fields
172+
let mut parameters = Self::create_path_parameters(endpoint, &path_spec.params);
173+
174+
// Add query parameters as before
174175
for query_field in &endpoint.query {
175176
parameters.push(Self::create_query_parameter(query_field));
176177
}
@@ -201,23 +202,31 @@ impl OpenAPIGenerator {
201202
..Default::default()
202203
}
203204
}
204-
205-
fn create_path_parameters(params: &[PathParameter]) -> Vec<ObjectOrReference<Parameter>> {
205+
fn create_path_parameters(
206+
endpoint: &Endpoint,
207+
params: &[String],
208+
) -> Vec<ObjectOrReference<Parameter>> {
206209
params
207210
.iter()
208-
.map(|param| {
211+
.map(|param_name| {
212+
// Look for this parameter in the endpoint's path fields
213+
let path_field = endpoint.path.iter().find(|field| &field.name == param_name);
214+
209215
ObjectOrReference::Object(Parameter {
210-
name: param.name.clone(),
216+
name: param_name.clone(),
211217
location: ParameterIn::Path,
212-
description: Some(String::new()),
213-
required: Some(true),
218+
description: Some(path_field.map_or(String::new(), |f| f.docs.clone())),
219+
required: Some(true), // Path parameters are always required
214220
deprecated: None,
215221
allow_empty_value: None,
216222
style: Some(ParameterStyle::Simple),
217223
explode: None,
218224
allow_reserved: None,
219-
schema: Some(Self::create_schema_for_type(&param.param_type)),
220-
example: None,
225+
schema: Some(match path_field {
226+
Some(field) => Self::create_schema_for_field(field),
227+
None => Self::create_default_schema_for_path_param(),
228+
}),
229+
example: path_field.and_then(|f| f.default.clone()),
221230
examples: BTreeMap::new(),
222231
content: None,
223232
extensions: BTreeMap::new(),
@@ -226,6 +235,14 @@ impl OpenAPIGenerator {
226235
.collect()
227236
}
228237

238+
fn create_default_schema_for_path_param() -> ObjectOrReference<ObjectSchema> {
239+
ObjectOrReference::Object(ObjectSchema {
240+
schema_type: Some(SchemaTypeSet::Single(Type::String)),
241+
description: Some(String::new()),
242+
..Default::default()
243+
})
244+
}
245+
229246
fn create_query_parameter(field: &Field) -> ObjectOrReference<Parameter> {
230247
ObjectOrReference::Object(Parameter {
231248
name: field.name.clone(),
@@ -312,20 +329,6 @@ impl OpenAPIGenerator {
312329
ObjectOrReference::Object(schema)
313330
}
314331

315-
fn create_schema_for_type(type_str: &str) -> ObjectOrReference<ObjectSchema> {
316-
ObjectOrReference::Object(ObjectSchema {
317-
schema_type: Some(SchemaTypeSet::Single(match type_str {
318-
"str" => Type::String,
319-
"int" => Type::Integer,
320-
"float" => Type::Number,
321-
"bool" => Type::Boolean,
322-
_ => Type::String,
323-
})),
324-
description: Some(String::new()),
325-
..Default::default()
326-
})
327-
}
328-
329332
fn create_default_responses() -> BTreeMap<String, ObjectOrReference<Response>> {
330333
let mut responses = BTreeMap::new();
331334
responses.insert(

aiscript-runtime/src/parser.rs

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -328,26 +328,26 @@ impl<'a> Parser<'a> {
328328
let mut matched_path_params = std::collections::HashSet::new();
329329

330330
// Check each path spec parameter
331-
for spec_param in &path_spec.params {
331+
for param_name in &path_spec.params {
332332
// Try to find exact match first
333-
let exact_match = endpoint.path.iter().find(|f| f.name == spec_param.name);
333+
let exact_match = endpoint.path.iter().find(|f| &f.name == param_name);
334334

335335
if exact_match.is_some() {
336-
matched_path_params.insert(spec_param.name.clone());
336+
matched_path_params.insert(param_name.clone());
337337
continue;
338338
}
339339

340340
// Try case-insensitive match
341341
let case_insensitive_match = endpoint
342342
.path
343343
.iter()
344-
.find(|f| f.name.to_lowercase() == spec_param.name.to_lowercase());
344+
.find(|f| f.name.to_lowercase() == param_name.to_lowercase());
345345

346346
if let Some(field) = case_insensitive_match {
347-
case_mismatches.push((spec_param.name.clone(), field.name.clone()));
347+
case_mismatches.push((param_name.clone(), field.name.clone()));
348348
matched_path_params.insert(field.name.clone());
349349
} else {
350-
missing_params.push(spec_param.name.clone());
350+
missing_params.push(param_name.clone());
351351
}
352352
}
353353

@@ -358,7 +358,7 @@ impl<'a> Parser<'a> {
358358
let is_case_mismatch = path_spec
359359
.params
360360
.iter()
361-
.any(|p| p.name.to_lowercase() == field.name.to_lowercase());
361+
.any(|p| p.to_lowercase() == field.name.to_lowercase());
362362

363363
if !is_case_mismatch {
364364
extra_params.push(field.name.clone());
@@ -400,40 +400,12 @@ impl<'a> Parser<'a> {
400400
.join(", ")
401401
));
402402
}
403-
404-
// Validate parameter types if specified in path
405-
for path_param in &path_spec.params {
406-
if path_param.param_type != "str" {
407-
// Find the corresponding field in the path block (case-insensitive)
408-
if let Some(field) = endpoint
409-
.path
410-
.iter()
411-
.find(|f| f.name.to_lowercase() == path_param.name.to_lowercase())
412-
{
413-
// Check if the types match
414-
let expected_type = match path_param.param_type.as_str() {
415-
"int" => FieldType::Number,
416-
"float" => FieldType::Number,
417-
"bool" => FieldType::Bool,
418-
_ => FieldType::Str, // Default to string
419-
};
420-
if field._type != expected_type {
421-
return Err(format!(
422-
"Type mismatch for path parameter '{}': expected '{}', got '{}'",
423-
field.name,
424-
path_param.param_type,
425-
field._type.as_str()
426-
));
427-
}
428-
}
429-
}
430-
}
431403
}
432404

433405
Ok(())
434406
}
435407

436-
fn parse_path(&mut self) -> Result<(String, Vec<PathParameter>), String> {
408+
fn parse_path(&mut self) -> Result<(String, Vec<String>), String> {
437409
let mut path = String::new();
438410
let mut params = Vec::new();
439411

@@ -464,11 +436,8 @@ impl<'a> Parser<'a> {
464436
path.push_str(&name);
465437
path.push('}');
466438

467-
// Add parameter to our list
468-
params.push(PathParameter {
469-
name,
470-
param_type: "str".to_string(), // Default type, will be overridden by path block
471-
});
439+
// Add parameter name to our list
440+
params.push(name);
472441
}
473442
TokenType::Identifier => {
474443
path.push_str(self.current.lexeme);
@@ -481,7 +450,6 @@ impl<'a> Parser<'a> {
481450

482451
Ok((path, params))
483452
}
484-
485453
fn consume(&mut self, expected: TokenType, message: &str) -> Result<(), String> {
486454
if self.check(expected) {
487455
self.advance();
@@ -551,8 +519,7 @@ mod tests {
551519
assert_eq!(route.docs, "Test route line1\nTest route line2");
552520
assert_eq!(route.prefix, "/test/{id}");
553521
assert_eq!(route.params.len(), 1);
554-
assert_eq!(route.params[0].name, "id");
555-
assert_eq!(route.params[0].param_type, "str");
522+
assert_eq!(route.params[0], "id");
556523

557524
let endpoint = &route.endpoints[0];
558525
assert_eq!(endpoint.docs, "Test endpoint");
@@ -760,7 +727,7 @@ mod tests {
760727
assert_eq!(endpoint.path_specs[0].method, HttpMethod::Get);
761728
assert_eq!(endpoint.path_specs[0].path, "/users/{id}");
762729
assert_eq!(endpoint.path_specs[0].params.len(), 1);
763-
assert_eq!(endpoint.path_specs[0].params[0].name, "id");
730+
assert_eq!(endpoint.path_specs[0].params[0], "id");
764731

765732
assert_eq!(endpoint.path_specs[1].method, HttpMethod::Post);
766733
assert_eq!(endpoint.path_specs[1].path, "/users");

0 commit comments

Comments
 (0)