@@ -1301,7 +1301,16 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
13011301 }
13021302
13031303 // parameters are ready !!!
1304-
1304+
1305+ // Validate parameters before any further processing (authorization, proxy, etc.)
1306+ if ( endpoint . ParameterValidations is not null )
1307+ {
1308+ if ( ! await ValidateParametersAsync ( command . Parameters , endpoint , context ) )
1309+ {
1310+ return ;
1311+ }
1312+ }
1313+
13051314 // authorization check
13061315 if ( endpoint . Login is false )
13071316 {
@@ -2363,4 +2372,168 @@ private async ValueTask ReturnErrorAsync(
23632372 await context . Response . WriteAsync ( message ) ;
23642373 await context . Response . CompleteAsync ( ) ;
23652374 }
2375+
2376+ private async ValueTask < bool > ValidateParametersAsync (
2377+ NpgsqlParameterCollection parameters ,
2378+ RoutineEndpoint endpoint ,
2379+ HttpContext context )
2380+ {
2381+ if ( endpoint . ParameterValidations is null )
2382+ {
2383+ return true ;
2384+ }
2385+
2386+ foreach ( var kvp in endpoint . ParameterValidations )
2387+ {
2388+ var originalParamName = kvp . Key ;
2389+ var rules = kvp . Value ;
2390+
2391+ // Find the parameter by original name
2392+ NpgsqlRestParameter ? parameter = null ;
2393+ for ( var i = 0 ; i < parameters . Count ; i ++ )
2394+ {
2395+ var p = parameters [ i ] as NpgsqlRestParameter ;
2396+ if ( p is not null && string . Equals ( p . ActualName , originalParamName , StringComparison . Ordinal ) )
2397+ {
2398+ parameter = p ;
2399+ break ;
2400+ }
2401+ }
2402+
2403+ if ( parameter is null )
2404+ {
2405+ continue ;
2406+ }
2407+
2408+ // Get the value to validate - use OriginalStringValue for consistency
2409+ var valueToValidate = parameter . OriginalStringValue ;
2410+ var convertedParamName = parameter . ConvertedName ;
2411+
2412+ // Find rule names for error messages
2413+ foreach ( var rule in rules )
2414+ {
2415+ // Find the rule name by looking it up in ValidationOptions
2416+ var ruleName = FindRuleName ( rule ) ;
2417+
2418+ var ( isValid , message ) = ValidateParameterValue (
2419+ valueToValidate ,
2420+ parameter . Value ,
2421+ rule ,
2422+ originalParamName ,
2423+ convertedParamName ,
2424+ ruleName ) ;
2425+
2426+ if ( ! isValid )
2427+ {
2428+ var urlInfo = string . Concat ( endpoint . Method . ToString ( ) , " " , endpoint . Path ) ;
2429+ Logger ? . ValidationFailed ( urlInfo , originalParamName , message ) ;
2430+
2431+ context . Response . StatusCode = rule . StatusCode ;
2432+ context . Response . ContentType = Text . Plain ;
2433+ await context . Response . WriteAsync ( message ) ;
2434+ await context . Response . CompleteAsync ( ) ;
2435+ return false ;
2436+ }
2437+ }
2438+ }
2439+
2440+ return true ;
2441+ }
2442+
2443+ private static string FindRuleName ( ValidationRule rule )
2444+ {
2445+ foreach ( var kvp in Options . ValidationOptions . Rules )
2446+ {
2447+ if ( ReferenceEquals ( kvp . Value , rule ) )
2448+ {
2449+ return kvp . Key ;
2450+ }
2451+ }
2452+ return string . Empty ;
2453+ }
2454+
2455+ private static ( bool IsValid , string Message ) ValidateParameterValue (
2456+ string ? originalStringValue ,
2457+ object ? value ,
2458+ ValidationRule rule ,
2459+ string originalParamName ,
2460+ string convertedParamName ,
2461+ string ruleName )
2462+ {
2463+ // Message format: {0} = original param name, {1} = converted param name, {2} = rule name
2464+ var message = string . Format ( rule . Message , originalParamName , convertedParamName , ruleName ) ;
2465+
2466+ switch ( rule . Type )
2467+ {
2468+ case ValidationType . NotNull :
2469+ // Check if value is null or DBNull
2470+ if ( value is null || value == DBNull . Value )
2471+ {
2472+ return ( false , message ) ;
2473+ }
2474+ break ;
2475+
2476+ case ValidationType . NotEmpty :
2477+ // Check if value is an empty string (null values pass - use NotNull for null check)
2478+ if ( originalStringValue is not null && originalStringValue . Length == 0 )
2479+ {
2480+ return ( false , message ) ;
2481+ }
2482+ break ;
2483+
2484+ case ValidationType . Required :
2485+ // Combines NotNull and NotEmpty - value cannot be null or empty string
2486+ if ( value is null || value == DBNull . Value )
2487+ {
2488+ return ( false , message ) ;
2489+ }
2490+ if ( originalStringValue is not null && originalStringValue . Length == 0 )
2491+ {
2492+ return ( false , message ) ;
2493+ }
2494+ break ;
2495+
2496+ case ValidationType . Regex :
2497+ if ( rule . Pattern is null )
2498+ {
2499+ return ( true , string . Empty ) ;
2500+ }
2501+ // Null/empty values don't match regex (use NotEmpty rule first if required)
2502+ if ( string . IsNullOrEmpty ( originalStringValue ) )
2503+ {
2504+ return ( false , message ) ;
2505+ }
2506+ if ( ! System . Text . RegularExpressions . Regex . IsMatch ( originalStringValue , rule . Pattern ) )
2507+ {
2508+ return ( false , message ) ;
2509+ }
2510+ break ;
2511+
2512+ case ValidationType . MinLength :
2513+ if ( rule . MinLength is null )
2514+ {
2515+ return ( true , string . Empty ) ;
2516+ }
2517+ var strValue = originalStringValue ?? string . Empty ;
2518+ if ( strValue . Length < rule . MinLength . Value )
2519+ {
2520+ return ( false , message ) ;
2521+ }
2522+ break ;
2523+
2524+ case ValidationType . MaxLength :
2525+ if ( rule . MaxLength is null )
2526+ {
2527+ return ( true , string . Empty ) ;
2528+ }
2529+ var strVal = originalStringValue ?? string . Empty ;
2530+ if ( strVal . Length > rule . MaxLength . Value )
2531+ {
2532+ return ( false , message ) ;
2533+ }
2534+ break ;
2535+ }
2536+
2537+ return ( true , string . Empty ) ;
2538+ }
23662539}
0 commit comments