1use std::marker::PhantomData;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7
8use crate::system_param::{Access, SystemParam};
9use crate::world::{UnsafeWorldCell, World};
10
11pub trait Handler<Req, Resp> {
22 fn name(&self) -> &'static str;
24
25 fn run(&mut self, request: Req, world: &mut World) -> Result<Resp, String>;
27
28 fn access(&self) -> Vec<Access>;
32}
33
34pub trait IntoHandler<Req, Resp, Params> {
45 fn into_handler(self, name: &'static str) -> Box<dyn Handler<Req, Resp>>;
46}
47
48pub(crate) trait HandlerParamFunction<Req, Resp, Params>: 'static {
58 fn run(&mut self, request: Req, world: &mut World) -> Result<Resp, String>;
59 fn param_access() -> Vec<Access>;
60}
61
62struct ParamHandler<F, Req, Resp, Params> {
65 name: &'static str,
66 func: F,
67 _marker: PhantomData<fn(Req) -> (Resp, Params)>,
68}
69
70impl<F, Req, Resp, Params> Handler<Req, Resp> for ParamHandler<F, Req, Resp, Params>
71where
72 F: HandlerParamFunction<Req, Resp, Params>,
73{
74 fn name(&self) -> &'static str {
75 self.name
76 }
77
78 fn run(&mut self, request: Req, world: &mut World) -> Result<Resp, String> {
79 self.func.run(request, world)
80 }
81
82 fn access(&self) -> Vec<Access> {
83 F::param_access()
84 }
85}
86
87fn validate_no_self_conflicts(access: &[Access], handler_name: &'static str) {
93 for (i, a) in access.iter().enumerate() {
94 for b in &access[i + 1..] {
95 if a.conflicts_with(b) {
96 panic!(
97 "handler '{}' has conflicting parameter access: {:?} vs {:?}",
98 handler_name, a, b,
99 );
100 }
101 }
102 }
103}
104
105pub fn run_handler<Req, Resp>(
123 handler: &mut dyn Handler<Req, Resp>,
124 request: Req,
125 world: &mut World,
126) -> Result<Resp, String> {
127 handler.run(request, world)
128}
129
130pub fn run_json_handler<Req, Resp>(
140 handler: &mut dyn Handler<Req, Resp>,
141 json_body: &str,
142 world: &mut World,
143) -> Result<String, String>
144where
145 Req: DeserializeOwned,
146 Resp: Serialize,
147{
148 let request: Req = serde_json::from_str(json_body).map_err(|e| e.to_string())?;
149 let response = run_handler(handler, request, world)?;
150 serde_json::to_string(&response).map_err(|e| e.to_string())
151}
152
153pub fn run_json_handler_value<Req, Resp>(
158 handler: &mut dyn Handler<Req, Resp>,
159 json_body: &str,
160 world: &mut World,
161) -> Result<serde_json::Value, String>
162where
163 Req: DeserializeOwned,
164 Resp: Serialize,
165{
166 let request: Req = serde_json::from_str(json_body).map_err(|e| e.to_string())?;
167 let response = run_handler(handler, request, world)?;
168 serde_json::to_value(&response).map_err(|e| e.to_string())
169}
170
171pub fn run_json_handler_function<F, Req, Resp, Params>(
181 f: F,
182 handler_name: &'static str,
183 json_body: &str,
184 world: &mut World,
185) -> Result<String, String>
186where
187 F: IntoHandler<Req, Resp, Params>,
188 Req: DeserializeOwned + 'static,
189 Resp: Serialize + 'static,
190{
191 let mut handler = f.into_handler(handler_name);
192 run_json_handler(&mut *handler, json_body, world)
193}
194
195impl<Func, Req, Resp, Err> HandlerParamFunction<Req, Resp, ()> for Func
200where
201 Func: FnMut(Req) -> Result<Resp, Err> + 'static,
202 Req: 'static,
203 Resp: 'static,
204 Err: ToString + 'static,
205{
206 fn run(&mut self, request: Req, _world: &mut World) -> Result<Resp, String> {
207 self(request).map_err(|e| e.to_string())
208 }
209
210 fn param_access() -> Vec<Access> {
211 Vec::new()
212 }
213}
214
215impl<Func, Req, Resp> IntoHandler<Req, Resp, ()> for Func
216where
217 Func: HandlerParamFunction<Req, Resp, ()>,
218 Req: 'static,
219 Resp: 'static,
220{
221 fn into_handler(self, name: &'static str) -> Box<dyn Handler<Req, Resp>> {
222 Box::new(ParamHandler {
223 name,
224 func: self,
225 _marker: PhantomData,
226 })
227 }
228}
229
230macro_rules! impl_handler_param_function {
235 ($($P:ident),+) => {
236 #[allow(non_snake_case)]
237 impl<Func, Req, Resp, Err, $($P: SystemParam + 'static),+> HandlerParamFunction<Req, Resp, ($($P,)+)> for Func
238 where
239 Func: FnMut(Req, $($P::Item<'_>),+) -> Result<Resp, Err> + 'static,
240 Req: 'static,
241 Resp: 'static,
242 Err: ToString + 'static,
243 {
244 fn run(&mut self, request: Req, world: &mut World) -> Result<Resp, String> {
245 let cell = unsafe { UnsafeWorldCell::new(world as *mut World) };
253 unsafe { self(request, $($P::fetch(cell),)+) }.map_err(|e| e.to_string())
254 }
255
256 fn param_access() -> Vec<Access> {
257 let mut acc = Vec::new();
258 $(acc.extend($P::access());)+
259 acc
260 }
261 }
262
263 impl<Func, Req, Resp, $($P: SystemParam + 'static),+> IntoHandler<Req, Resp, ($($P,)+)> for Func
264 where
265 Func: HandlerParamFunction<Req, Resp, ($($P,)+)>,
266 Req: 'static,
267 Resp: 'static,
268 {
269 fn into_handler(self, name: &'static str) -> Box<dyn Handler<Req, Resp>> {
270 let access = <Func as HandlerParamFunction<Req, Resp, ($($P,)+)>>::param_access();
271 validate_no_self_conflicts(&access, name);
272 Box::new(ParamHandler {
273 name,
274 func: self,
275 _marker: PhantomData,
276 })
277 }
278 }
279 };
280}
281
282impl_handler_param_function!(P0);
283impl_handler_param_function!(P0, P1);
284impl_handler_param_function!(P0, P1, P2);
285impl_handler_param_function!(P0, P1, P2, P3);
286impl_handler_param_function!(P0, P1, P2, P3, P4);
287impl_handler_param_function!(P0, P1, P2, P3, P4, P5);
288impl_handler_param_function!(P0, P1, P2, P3, P4, P5, P6);
289impl_handler_param_function!(P0, P1, P2, P3, P4, P5, P6, P7);
290
291#[cfg(test)]
296mod tests {
297 use super::*;
298 use crate::component::Component;
299 use crate::system_param::{Query, QueryMut, Res, ResMut};
300 use serde::{Deserialize, Serialize};
301
302 #[derive(Debug)]
307 struct Counter(u32);
308 impl Component for Counter {}
309
310 struct Config {
311 multiplier: f32,
312 }
313
314 #[derive(Debug, Deserialize)]
315 struct SpawnRequest {
316 unit_id: u64,
317 }
318
319 #[derive(Debug, PartialEq, Serialize, Deserialize)]
320 struct SpawnResponse {
321 ok: bool,
322 }
323
324 fn spawn_no_params(req: SpawnRequest) -> Result<SpawnResponse, String> {
329 Ok(SpawnResponse {
330 ok: req.unit_id > 0,
331 })
332 }
333
334 #[test]
335 fn zero_param_handler() {
336 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
337 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
338 spawn_no_params,
339 "zero_param",
340 );
341 let mut world = World::new();
342 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
343 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
344 }
345
346 fn spawn_with_res(_req: SpawnRequest, cfg: Res<'_, Config>) -> Result<SpawnResponse, String> {
347 Ok(SpawnResponse {
348 ok: cfg.multiplier > 0.0,
349 })
350 }
351
352 #[test]
353 fn one_param_res_handler() {
354 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
355 IntoHandler::<SpawnRequest, SpawnResponse, (Res<'_, Config>,)>::into_handler(
356 spawn_with_res,
357 "one_param_res",
358 );
359 let mut world = World::new();
360 world.insert_resource(Config { multiplier: 2.0 });
361 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
362 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
363 }
364
365 fn spawn_with_res_mut(
366 req: SpawnRequest,
367 mut cfg: ResMut<'_, Config>,
368 ) -> Result<SpawnResponse, String> {
369 cfg.multiplier = req.unit_id as f32;
370 Ok(SpawnResponse { ok: true })
371 }
372
373 #[test]
374 fn one_param_res_mut_handler() {
375 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
376 IntoHandler::<SpawnRequest, SpawnResponse, (ResMut<'_, Config>,)>::into_handler(
377 spawn_with_res_mut,
378 "one_param_res_mut",
379 );
380 let mut world = World::new();
381 world.insert_resource(Config { multiplier: 0.0 });
382 let result = handler.run(SpawnRequest { unit_id: 42 }, &mut world);
383 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
384 assert!((world.resource::<Config>().multiplier - 42.0).abs() < f32::EPSILON);
385 }
386
387 fn spawn_with_query(
388 req: SpawnRequest,
389 counters: Query<'_, Counter>,
390 ) -> Result<SpawnResponse, String> {
391 Ok(SpawnResponse {
392 ok: counters.len() as u64 == req.unit_id,
393 })
394 }
395
396 #[test]
397 fn one_param_query_handler() {
398 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
399 IntoHandler::<SpawnRequest, SpawnResponse, (Query<'_, Counter>,)>::into_handler(
400 spawn_with_query,
401 "one_param_query",
402 );
403 let mut world = World::new();
404 world.spawn((Counter(10),));
405 world.spawn((Counter(20),));
406 let result = handler.run(SpawnRequest { unit_id: 2 }, &mut world);
407 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
408 }
409
410 fn spawn_with_query_mut(
411 req: SpawnRequest,
412 mut counters: QueryMut<'_, Counter>,
413 ) -> Result<SpawnResponse, String> {
414 for (_, c) in counters.iter_mut() {
415 c.0 += req.unit_id as u32;
416 }
417 Ok(SpawnResponse { ok: true })
418 }
419
420 #[test]
421 fn one_param_query_mut_handler() {
422 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
423 IntoHandler::<SpawnRequest, SpawnResponse, (QueryMut<'_, Counter>,)>::into_handler(
424 spawn_with_query_mut,
425 "one_param_query_mut",
426 );
427 let mut world = World::new();
428 world.spawn((Counter(0),));
429 world.spawn((Counter(5),));
430 let result = handler.run(SpawnRequest { unit_id: 10 }, &mut world);
431 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
432 let mut vals: Vec<u32> = world.query::<&Counter>().map(|(_, c)| c.0).collect();
433 vals.sort();
434 assert_eq!(vals, vec![10, 15]);
435 }
436
437 fn spawn_two_params(
438 req: SpawnRequest,
439 cfg: Res<'_, Config>,
440 counters: Query<'_, Counter>,
441 ) -> Result<SpawnResponse, String> {
442 Ok(SpawnResponse {
443 ok: cfg.multiplier > 0.0 && counters.len() as u64 == req.unit_id,
444 })
445 }
446
447 #[test]
448 fn two_param_handler() {
449 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> = IntoHandler::<
450 SpawnRequest,
451 SpawnResponse,
452 (Res<'_, Config>, Query<'_, Counter>),
453 >::into_handler(
454 spawn_two_params,
455 "two_param",
456 );
457 let mut world = World::new();
458 world.insert_resource(Config { multiplier: 1.5 });
459 world.spawn((Counter(0),));
460 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
461 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
462 }
463
464 #[test]
465 fn handler_reports_access() {
466 let handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
467 IntoHandler::<SpawnRequest, SpawnResponse, (Res<'_, Config>,)>::into_handler(
468 spawn_with_res,
469 "access_check",
470 );
471 let access = handler.access();
472 assert_eq!(access.len(), 1);
473 assert!(matches!(access[0], Access::ResRead(_)));
474 }
475
476 #[test]
477 fn run_handler_free_function() {
478 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
479 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
480 spawn_no_params,
481 "free_fn",
482 );
483 let mut world = World::new();
484 let result = run_handler(&mut *handler, SpawnRequest { unit_id: 99 }, &mut world);
485 assert_eq!(result.unwrap(), SpawnResponse { ok: true });
486 }
487
488 #[test]
489 fn run_json_handler_round_trip() {
490 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
491 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
492 spawn_no_params,
493 "json_round_trip",
494 );
495 let mut world = World::new();
496 let out = run_json_handler(&mut *handler, r#"{"unit_id":3}"#, &mut world).unwrap();
497 assert_eq!(out, r#"{"ok":true}"#);
498 }
499
500 #[test]
501 fn run_json_handler_function_infers_types() {
502 let mut world = World::new();
503 let out =
504 run_json_handler_function(spawn_no_params, "json_fn", r#"{"unit_id":5}"#, &mut world)
505 .unwrap();
506 assert_eq!(out, r#"{"ok":true}"#);
507 }
508
509 fn json_round_trip_res_mut(
510 req: SpawnRequest,
511 mut cfg: ResMut<'_, Config>,
512 ) -> Result<SpawnResponse, String> {
513 cfg.multiplier = req.unit_id as f32;
514 Ok(SpawnResponse { ok: true })
515 }
516
517 #[test]
518 fn run_json_handler_res_mut_round_trip() {
519 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
520 IntoHandler::<SpawnRequest, SpawnResponse, (ResMut<'_, Config>,)>::into_handler(
521 json_round_trip_res_mut,
522 "res_mut_json",
523 );
524 let mut world = World::new();
525 world.insert_resource(Config { multiplier: 0.0 });
526 let out = run_json_handler(&mut *handler, r#"{"unit_id":7}"#, &mut world).unwrap();
527 assert_eq!(out, r#"{"ok":true}"#);
528 assert!((world.resource::<Config>().multiplier - 7.0).abs() < f32::EPSILON);
529 }
530
531 #[test]
532 fn run_json_handler_rejects_bad_json() {
533 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
534 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
535 spawn_no_params,
536 "bad_json",
537 );
538 let mut world = World::new();
539 let err = run_json_handler(&mut *handler, "not json", &mut world).unwrap_err();
540 assert!(!err.is_empty());
541 }
542
543 fn conflicting_handler(
548 _req: SpawnRequest,
549 _a: Res<'_, Config>,
550 _b: ResMut<'_, Config>,
551 ) -> Result<SpawnResponse, String> {
552 Ok(SpawnResponse { ok: true })
553 }
554
555 #[test]
556 #[should_panic(expected = "conflicting parameter access")]
557 fn self_conflict_panics_on_registration() {
558 let _ = IntoHandler::<
559 SpawnRequest,
560 SpawnResponse,
561 (Res<'_, Config>, ResMut<'_, Config>),
562 >::into_handler(conflicting_handler, "conflict");
563 }
564
565 fn failing_handler(_req: SpawnRequest) -> Result<SpawnResponse, String> {
566 Err("some error".into())
567 }
568
569 #[test]
570 fn handler_error_propagates() {
571 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
572 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
573 failing_handler,
574 "error_handler",
575 );
576 let mut world = World::new();
577 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
578 assert!(result.is_err());
579 assert_eq!(result.unwrap_err(), "some error");
580 }
581
582 #[derive(Debug)]
585 struct ApiError {
586 code: u16,
587 message: String,
588 }
589
590 impl std::fmt::Display for ApiError {
591 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592 write!(f, "ApiError({}): {}", self.code, self.message)
593 }
594 }
595
596 fn handler_with_domain_error(_req: SpawnRequest) -> Result<SpawnResponse, ApiError> {
597 Err(ApiError {
598 code: 404,
599 message: "not found".into(),
600 })
601 }
602
603 #[test]
604 fn domain_error_type_converts_to_string() {
605 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
606 IntoHandler::<SpawnRequest, SpawnResponse, ()>::into_handler(
607 handler_with_domain_error,
608 "domain_error",
609 );
610 let mut world = World::new();
611 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
612 assert!(result.is_err());
613 assert_eq!(result.unwrap_err(), "ApiError(404): not found");
614 }
615
616 fn handler_with_domain_error_and_params(
617 _req: SpawnRequest,
618 _cfg: Res<'_, Config>,
619 ) -> Result<SpawnResponse, ApiError> {
620 Err(ApiError {
621 code: 500,
622 message: "internal".into(),
623 })
624 }
625
626 #[test]
627 fn domain_error_type_with_params() {
628 let mut handler: Box<dyn Handler<SpawnRequest, SpawnResponse>> =
629 IntoHandler::<SpawnRequest, SpawnResponse, (Res<'_, Config>,)>::into_handler(
630 handler_with_domain_error_and_params,
631 "domain_error_params",
632 );
633 let mut world = World::new();
634 world.insert_resource(Config { multiplier: 1.0 });
635 let result = handler.run(SpawnRequest { unit_id: 1 }, &mut world);
636 assert!(result.is_err());
637 assert_eq!(result.unwrap_err(), "ApiError(500): internal");
638 }
639}