1use serde_json::{Value, json};
2use sofie_core::Result;
3use sofie_db::{Outcome, ReadOnly, Reply, ReplyData};
4
5use crate::answer::Response;
6use crate::routes::ScreenView;
7use crate::screen;
8use crate::view::RouteAnswer;
9
10const CHOSEN_SCREEN: &str = ".screen";
11
12pub struct Screen<T> {
14 name: &'static str,
15 data: T,
16}
17
18impl<T> Screen<T> {
19 pub fn render(name: &'static str, data: T) -> Self {
20 Self { name, data }
21 }
22}
23
24impl<T: Outcome> Outcome for Screen<T> {
25 fn reply(self) -> Result<Reply> {
26 let data = self.data.reply()?.into_json()?;
27 Ok(Reply::Data(Box::new(Chosen(
28 json!({ CHOSEN_SCREEN: self.name, "data": data }),
29 ))))
30 }
31}
32
33struct Chosen(Value);
35
36impl ReplyData for Chosen {
37 fn write_json(&self, out: &mut Vec<u8>) -> serde_json::Result<()> {
38 serde_json::to_writer(out, &self.0)
39 }
40
41 fn to_json(&self) -> serde_json::Result<Value> {
42 Ok(self.0.clone())
43 }
44}
45
46impl<T: ReadOnly> ReadOnly for Screen<T> {}
47
48fn chosen(result: &Result<Reply>) -> Option<(String, Value)> {
49 let Ok(Reply::Data(reply)) = result else {
50 return None;
51 };
52 if reply.type_name() != std::any::type_name::<Chosen>() {
53 return None;
54 }
55 let Ok(Value::Object(mut fields)) = reply.to_json() else {
56 return None;
57 };
58 let name = fields.get(CHOSEN_SCREEN)?.as_str()?.to_owned();
59 Some((name, fields.remove("data")?))
60}
61
62pub(crate) fn answer(
63 route: &RouteAnswer<'_>,
64 result: Result<Reply>,
65) -> std::result::Result<Response, Result<Reply>> {
66 let Some((name, data)) = chosen(&result) else {
67 return Err(result);
68 };
69 let target = route.target;
70 let view = ScreenView {
71 #[cfg(feature = "resources")]
72 builtin: None,
73 key: crate::screen_key(target.module, target.audience.name, &name),
74 title: sofie_engine::humanize(&name),
75 };
76 let caller = route.asked.caller;
77 Ok(route.on_screen(&view, |page| {
78 screen::answer(Ok(Reply::Json(data)), caller, page)
79 }))
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn only_a_handler_s_screen_chooses_one_not_json_shaped_like_it() {
88 let shaped = Ok(Reply::Json(json!({ CHOSEN_SCREEN: "orders", "data": [] })));
89 let screen = Screen::render("orders", ()).reply();
90
91 assert!(chosen(&shaped).is_none());
92 assert_eq!(chosen(&screen), Some(("orders".to_owned(), Value::Null)));
93 }
94}