Skip to content

Commit d0f52b9

Browse files
committed
Reject invalid optimize enum values
1 parent 9bb21f3 commit d0f52b9

1 file changed

Lines changed: 194 additions & 31 deletions

File tree

src/server.rs

Lines changed: 194 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
122145
fn 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

Comments
 (0)