@@ -3,7 +3,7 @@ use aiscript_vm::{ReturnValue, Vm, VmError};
33use axum:: {
44 Form , Json , RequestExt ,
55 body:: Body ,
6- extract:: { self , FromRequest , Request } ,
6+ extract:: { self , FromRequest , RawPathParams , Request } ,
77 http:: { HeaderName , HeaderValue } ,
88 response:: { IntoResponse , Response } ,
99} ;
@@ -57,6 +57,7 @@ pub struct Field {
5757#[ derive( Clone ) ]
5858pub struct Endpoint {
5959 pub annotation : RouteAnnotation ,
60+ pub path_params : Vec < Field > ,
6061 pub query_params : Vec < Field > ,
6162 pub body_type : BodyKind ,
6263 pub body_fields : Vec < Field > ,
@@ -70,6 +71,7 @@ pub struct Endpoint {
7071
7172enum ProcessingState {
7273 ValidatingAuth ,
74+ ValidatingPath ,
7375 ValidatingQuery ,
7476 ValidatingBody ,
7577 Executing ( JoinHandle < Result < ReturnValue , VmError > > ) ,
@@ -79,6 +81,7 @@ pub struct RequestProcessor {
7981 endpoint : Endpoint ,
8082 request : Request < Body > ,
8183 jwt_claim : Option < Value > ,
84+ path_data : HashMap < String , Value > ,
8285 query_data : HashMap < String , Value > ,
8386 body_data : HashMap < String , Value > ,
8487 state : ProcessingState ,
@@ -89,12 +92,13 @@ impl RequestProcessor {
8992 let state = if endpoint. annotation . is_auth_required ( ) {
9093 ProcessingState :: ValidatingAuth
9194 } else {
92- ProcessingState :: ValidatingQuery
95+ ProcessingState :: ValidatingPath
9396 } ;
9497 Self {
9598 endpoint,
9699 request,
97100 jwt_claim : None ,
101+ path_data : HashMap :: new ( ) ,
98102 query_data : HashMap :: new ( ) ,
99103 body_data : HashMap :: new ( ) ,
100104 state,
@@ -307,6 +311,99 @@ impl Future for RequestProcessor {
307311 }
308312 }
309313 }
314+ self . state = ProcessingState :: ValidatingPath ;
315+ }
316+ ProcessingState :: ValidatingPath => {
317+ let raw_path_params = {
318+ // Extract path parameters using Axum's RawPathParams extractor
319+ let future = self . request . extract_parts :: < RawPathParams > ( ) ;
320+
321+ tokio:: pin!( future) ;
322+ match future. poll ( cx) {
323+ Poll :: Pending => return Poll :: Pending ,
324+ Poll :: Ready ( Ok ( params) ) => params,
325+ Poll :: Ready ( Err ( e) ) => {
326+ return Poll :: Ready ( Ok ( format ! (
327+ "Failed to extract path parameters: {}" ,
328+ e
329+ )
330+ . into_response ( ) ) ) ;
331+ }
332+ }
333+ } ;
334+
335+ // Process and validate each path parameter
336+ for ( param_name, param_value) in & raw_path_params {
337+ // Find the corresponding path parameter field
338+ if let Some ( field) = self
339+ . endpoint
340+ . path_params
341+ . iter ( )
342+ . find ( |f| f. name == param_name)
343+ {
344+ // Convert the value to the appropriate type based on the field definition
345+ let value = match field. field_type {
346+ FieldType :: Str => Value :: String ( param_value. to_string ( ) ) ,
347+ FieldType :: Number => {
348+ if let Ok ( num) = param_value. parse :: < i64 > ( ) {
349+ Value :: Number ( num. into ( ) )
350+ } else if let Ok ( float) = param_value. parse :: < f64 > ( ) {
351+ match serde_json:: Number :: from_f64 ( float) {
352+ Some ( n) => Value :: Number ( n) ,
353+ None => {
354+ return Poll :: Ready ( Ok (
355+ format ! ( "Invalid path parameter type for {}: could not convert to number" , param_name)
356+ . into_response ( )
357+ ) ) ;
358+ }
359+ }
360+ } else {
361+ return Poll :: Ready ( Ok ( format ! (
362+ "Invalid path parameter type for {}: expected a number" ,
363+ param_name
364+ )
365+ . into_response ( ) ) ) ;
366+ }
367+ }
368+ FieldType :: Bool => match param_value. to_lowercase ( ) . as_str ( ) {
369+ "true" => Value :: Bool ( true ) ,
370+ "false" => Value :: Bool ( false ) ,
371+ _ => {
372+ return Poll :: Ready ( Ok (
373+ format ! ( "Invalid path parameter type for {}: expected a boolean" , param_name)
374+ . into_response ( )
375+ ) ) ;
376+ }
377+ } ,
378+ _ => {
379+ return Poll :: Ready ( Ok ( format ! (
380+ "Unsupported path parameter type for {}" ,
381+ param_name
382+ )
383+ . into_response ( ) ) ) ;
384+ }
385+ } ;
386+
387+ // Validate the value using our existing validation infrastructure
388+ if let Err ( e) = Self :: validate_field ( field, & value) {
389+ return Poll :: Ready ( Ok ( e. into_response ( ) ) ) ;
390+ }
391+
392+ // Store the validated parameter
393+ self . path_data . insert ( param_name. to_string ( ) , value) ;
394+ }
395+ }
396+
397+ // Check for missing required parameters
398+ for field in & self . endpoint . path_params {
399+ if !self . path_data . contains_key ( & field. name ) && field. required {
400+ return Poll :: Ready ( Ok (
401+ ServerError :: MissingField ( field. name . clone ( ) ) . into_response ( )
402+ ) ) ;
403+ }
404+ }
405+
406+ // Move to the next state
310407 self . state = ProcessingState :: ValidatingQuery ;
311408 }
312409 ProcessingState :: ValidatingQuery => {
@@ -400,6 +497,7 @@ impl Future for RequestProcessor {
400497 } else {
401498 None
402499 } ;
500+ let path_data = mem:: take ( & mut self . path_data ) ;
403501 let query_data = mem:: take ( & mut self . query_data ) ;
404502 let body_data = mem:: take ( & mut self . body_data ) ;
405503 let pg_connection = self . endpoint . pg_connection . clone ( ) ;
@@ -417,6 +515,7 @@ impl Future for RequestProcessor {
417515 vm. eval_function (
418516 0 ,
419517 & [
518+ Value :: Object ( path_data. into_iter ( ) . collect ( ) ) ,
420519 Value :: Object ( query_data. into_iter ( ) . collect ( ) ) ,
421520 Value :: Object ( body_data. into_iter ( ) . collect ( ) ) ,
422521 Value :: Object (
0 commit comments