Skip to main content

sofie_http/
chosen.rs

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
12/// `sofie check` holds the template against `T`; JSON callers get the data alone.
13pub 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
33/// Told apart by its type, so no loader's own JSON, whatever its keys, chooses a screen.
34struct 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}