@@ -73,53 +73,78 @@ pub struct AppState {
7373// Helper parsers
7474// ---------------------------------------------------------------------------
7575
76- fn parse_utility ( s : & str ) -> UtilityFunction {
76+ fn parse_utility ( s : & str ) -> Result < UtilityFunction , String > {
7777 match s {
78- "leontief" => UtilityFunction :: Leontief ,
79- "linear" => UtilityFunction :: Linear ,
80- _ => UtilityFunction :: CobbDouglas ,
78+ "cobb_douglas" => Ok ( UtilityFunction :: CobbDouglas ) ,
79+ "leontief" => Ok ( UtilityFunction :: Leontief ) ,
80+ "linear" => Ok ( UtilityFunction :: Linear ) ,
81+ _ => Err ( "Invalid optimize request: unknown utility function" . to_string ( ) ) ,
8182 }
8283}
8384
84- fn parse_decay ( s : & str ) -> DistanceDecay {
85+ fn parse_decay ( s : & str ) -> Result < DistanceDecay , String > {
8586 match s {
86- "exponential" => DistanceDecay :: Exponential ,
87- "power_law" => DistanceDecay :: PowerLaw ,
88- "linear" => DistanceDecay :: Linear ,
89- "logistic" => DistanceDecay :: LogisticStep ,
90- _ => DistanceDecay :: Gaussian ,
87+ "gaussian" => Ok ( DistanceDecay :: Gaussian ) ,
88+ "exponential" => Ok ( DistanceDecay :: Exponential ) ,
89+ "power_law" => Ok ( DistanceDecay :: PowerLaw ) ,
90+ "linear" => Ok ( DistanceDecay :: Linear ) ,
91+ "logistic" => Ok ( DistanceDecay :: LogisticStep ) ,
92+ _ => Err ( "Invalid optimize request: unknown decay function" . to_string ( ) ) ,
9193 }
9294}
9395
94- fn parse_purity ( s : & str ) -> PurityOverride {
96+ fn parse_purity ( s : & str ) -> Result < PurityOverride , String > {
9597 match s {
96- "impure" => PurityOverride :: Impure ,
97- "normal" => PurityOverride :: Normal ,
98- "pure" => PurityOverride :: Pure ,
99- _ => PurityOverride :: Default ,
98+ "default" => Ok ( PurityOverride :: Default ) ,
99+ "impure" => Ok ( PurityOverride :: Impure ) ,
100+ "normal" => Ok ( PurityOverride :: Normal ) ,
101+ "pure" => Ok ( PurityOverride :: Pure ) ,
102+ _ => Err ( "Invalid optimize request: unknown purity override" . to_string ( ) ) ,
100103 }
101104}
102105
103- fn parse_strategy ( s : & str ) -> SearchStrategy {
106+ fn parse_strategy ( s : & str ) -> Result < SearchStrategy , String > {
104107 match s {
105- "fast" => SearchStrategy :: Fast ,
106- "slow" => SearchStrategy :: Slow ,
107- _ => SearchStrategy :: Hybrid ,
108+ "hybrid" => Ok ( SearchStrategy :: Hybrid ) ,
109+ "fast" => Ok ( SearchStrategy :: Fast ) ,
110+ "slow" => Ok ( SearchStrategy :: Slow ) ,
111+ _ => Err ( "Invalid optimize request: unknown search strategy" . to_string ( ) ) ,
108112 }
109113}
110114
111- fn parse_phase ( s : & str ) -> GamePhase {
115+ fn parse_phase ( s : & str ) -> Result < GamePhase , String > {
112116 match s {
113- "phase2" => GamePhase :: Phase2 ,
114- "phase3" => GamePhase :: Phase3 ,
115- "phase4" => GamePhase :: Phase4 ,
116- "phase5" => GamePhase :: Phase5 ,
117- "collectibles" => GamePhase :: Phase5 ,
118- _ => GamePhase :: Phase1 ,
117+ "phase1" => Ok ( GamePhase :: Phase1 ) ,
118+ "phase2" => Ok ( GamePhase :: Phase2 ) ,
119+ "phase3" => Ok ( GamePhase :: Phase3 ) ,
120+ "phase4" => Ok ( GamePhase :: Phase4 ) ,
121+ "phase5" => Ok ( GamePhase :: Phase5 ) ,
122+ "collectibles" => Ok ( GamePhase :: Phase5 ) ,
123+ _ => Err ( "Invalid optimize request: unknown game phase" . to_string ( ) ) ,
119124 }
120125}
121126
127+ struct ParsedOptimizeRequest {
128+ utility_func : UtilityFunction ,
129+ decay_func : DistanceDecay ,
130+ purity_override : PurityOverride ,
131+ strategy : SearchStrategy ,
132+ game_phase : GamePhase ,
133+ }
134+
135+ fn parse_optimize_request ( req : & OptimizeRequest ) -> Result < ParsedOptimizeRequest , String > {
136+ Ok ( ParsedOptimizeRequest {
137+ utility_func : parse_utility ( & req. utility_func ) ?,
138+ decay_func : parse_decay ( & req. decay_func ) ?,
139+ purity_override : parse_purity ( & req. purity_override ) ?,
140+ strategy : parse_strategy ( & req. strategy ) ?,
141+ game_phase : parse_phase ( & req. game_phase ) ?,
142+ } )
143+ }
144+
122145fn validate_optimize_request ( req : & OptimizeRequest , nodes : & [ ResourceNode ] ) -> Result < ( ) , String > {
146+ parse_optimize_request ( req) ?;
147+
123148 if !req. sigma . is_finite ( ) {
124149 return Err ( "Invalid optimize request: sigma must be finite" . to_string ( ) ) ;
125150 }
@@ -229,16 +254,20 @@ async fn post_optimize(
229254 if let Err ( message) = validate_optimize_request ( & req, & state. nodes ) {
230255 return HttpResponse :: BadRequest ( ) . body ( message) ;
231256 }
257+ let parsed = match parse_optimize_request ( & req) {
258+ Ok ( parsed) => parsed,
259+ Err ( message) => return HttpResponse :: BadRequest ( ) . body ( message) ,
260+ } ;
232261
233262 // Build OptimizerConfig from request
234263 let mut config = OptimizerConfig {
235264 sigma : req. sigma . clamp ( 50.0 , 1000.0 ) ,
236265 weights : req. weights . clone ( ) ,
237- purity_override : parse_purity ( & req . purity_override ) ,
238- strategy : parse_strategy ( & req . strategy ) ,
239- utility_func : parse_utility ( & req . utility_func ) ,
240- decay_func : parse_decay ( & req . decay_func ) ,
241- game_phase : parse_phase ( & req . game_phase ) ,
266+ purity_override : parsed . purity_override ,
267+ strategy : parsed . strategy ,
268+ utility_func : parsed . utility_func ,
269+ decay_func : parsed . decay_func ,
270+ game_phase : parsed . game_phase ,
242271 ignore_spawns : req. ignore_spawns ,
243272 } ;
244273
@@ -529,6 +558,108 @@ mod tests {
529558 assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
530559 }
531560
561+ #[ test]
562+ fn validation_rejects_unknown_utility_func ( ) {
563+ let nodes = test_nodes ( ) ;
564+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
565+ req. utility_func = "weighted_sum" . to_string ( ) ;
566+
567+ assert_eq ! (
568+ validate_optimize_request( & req, & nodes) ,
569+ Err ( "Invalid optimize request: unknown utility function" . to_string( ) )
570+ ) ;
571+ }
572+
573+ #[ test]
574+ fn validation_rejects_unknown_decay_func ( ) {
575+ let nodes = test_nodes ( ) ;
576+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
577+ req. decay_func = "inverse_square" . to_string ( ) ;
578+
579+ assert_eq ! (
580+ validate_optimize_request( & req, & nodes) ,
581+ Err ( "Invalid optimize request: unknown decay function" . to_string( ) )
582+ ) ;
583+ }
584+
585+ #[ test]
586+ fn validation_rejects_unknown_purity_override ( ) {
587+ let nodes = test_nodes ( ) ;
588+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
589+ req. purity_override = "mixed" . to_string ( ) ;
590+
591+ assert_eq ! (
592+ validate_optimize_request( & req, & nodes) ,
593+ Err ( "Invalid optimize request: unknown purity override" . to_string( ) )
594+ ) ;
595+ }
596+
597+ #[ test]
598+ fn validation_rejects_unknown_strategy ( ) {
599+ let nodes = test_nodes ( ) ;
600+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
601+ req. strategy = "medium" . to_string ( ) ;
602+
603+ assert_eq ! (
604+ validate_optimize_request( & req, & nodes) ,
605+ Err ( "Invalid optimize request: unknown search strategy" . to_string( ) )
606+ ) ;
607+ }
608+
609+ #[ test]
610+ fn validation_rejects_unknown_game_phase ( ) {
611+ let nodes = test_nodes ( ) ;
612+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
613+ req. game_phase = "phase6" . to_string ( ) ;
614+
615+ assert_eq ! (
616+ validate_optimize_request( & req, & nodes) ,
617+ Err ( "Invalid optimize request: unknown game phase" . to_string( ) )
618+ ) ;
619+ }
620+
621+ #[ test]
622+ fn validation_accepts_current_ui_enum_values ( ) {
623+ let nodes = test_nodes ( ) ;
624+
625+ for utility_func in [ "cobb_douglas" , "leontief" , "linear" ] {
626+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
627+ req. utility_func = utility_func. to_string ( ) ;
628+ assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
629+ }
630+
631+ for decay_func in [ "gaussian" , "exponential" , "power_law" , "linear" , "logistic" ] {
632+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
633+ req. decay_func = decay_func. to_string ( ) ;
634+ assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
635+ }
636+
637+ for purity_override in [ "default" , "impure" , "normal" , "pure" ] {
638+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
639+ req. purity_override = purity_override. to_string ( ) ;
640+ assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
641+ }
642+
643+ for strategy in [ "hybrid" , "fast" , "slow" ] {
644+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
645+ req. strategy = strategy. to_string ( ) ;
646+ assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
647+ }
648+
649+ for game_phase in [
650+ "phase1" ,
651+ "phase2" ,
652+ "phase3" ,
653+ "phase4" ,
654+ "phase5" ,
655+ "collectibles" ,
656+ ] {
657+ let mut req = test_request ( & [ ( "iron" , 1.0 ) ] ) ;
658+ req. game_phase = game_phase. to_string ( ) ;
659+ assert ! ( validate_optimize_request( & req, & nodes) . is_ok( ) ) ;
660+ }
661+ }
662+
532663 #[ test]
533664 fn validation_rejects_resource_universe_over_fixed_limit ( ) {
534665 let nodes = ( 0 ..129 )
@@ -576,6 +707,38 @@ mod tests {
576707 assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
577708 }
578709
710+ #[ actix_web:: test]
711+ async fn invalid_optimize_enum_request_returns_bad_request ( ) {
712+ let app_state = Arc :: new ( AppState {
713+ nodes : test_nodes ( ) ,
714+ } ) ;
715+ let app = actix_test:: init_service (
716+ App :: new ( )
717+ . app_data ( web:: Data :: new ( app_state) )
718+ . route ( "/api/optimize" , web:: post ( ) . to ( post_optimize) ) ,
719+ )
720+ . await ;
721+
722+ let request = actix_test:: TestRequest :: post ( )
723+ . uri ( "/api/optimize" )
724+ . set_json ( serde_json:: json!( {
725+ "utility_func" : "cobb_douglas" ,
726+ "decay_func" : "inverse_square" ,
727+ "purity_override" : "default" ,
728+ "strategy" : "hybrid" ,
729+ "game_phase" : "phase1" ,
730+ "sigma" : 200.0 ,
731+ "ignore_spawns" : false ,
732+ "weights" : {
733+ "iron" : 1.0
734+ }
735+ } ) )
736+ . to_request ( ) ;
737+ let response = actix_test:: call_service ( & app, request) . await ;
738+
739+ assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
740+ }
741+
579742 #[ actix_web:: test]
580743 async fn valid_optimize_request_returns_ok ( ) {
581744 let app_state = Arc :: new ( AppState {
0 commit comments