Skip to main content

sofie_db/
reply.rs

1use std::fmt;
2
3use serde::Serialize;
4use serde_json::Value;
5
6use sofie_core::{Error, Result, Show, Shown};
7
8pub enum Reply {
9    Empty,
10    Json(Value),
11    Data(Box<dyn ReplyData>),
12    List(Vec<Reply>),
13}
14
15pub trait ReplyData: Send + Sync {
16    fn write_json(&self, out: &mut Vec<u8>) -> serde_json::Result<()>;
17
18    fn to_json(&self) -> serde_json::Result<Value>;
19
20    fn type_name(&self) -> &'static str {
21        std::any::type_name::<Self>()
22    }
23
24    /// `None` renders a screen from the JSON it converts to.
25    fn shown(&self) -> Option<Shown<'_>> {
26        None
27    }
28}
29
30impl<T: Serialize + Send + Sync> ReplyData for T {
31    fn write_json(&self, out: &mut Vec<u8>) -> serde_json::Result<()> {
32        serde_json::to_writer(out, self)
33    }
34
35    fn to_json(&self) -> serde_json::Result<Value> {
36        serde_json::to_value(self)
37    }
38}
39
40/// Data a screen reads in place, which `#[derive(ReadModel)]` replies with.
41pub struct Showing<T>(pub T);
42
43impl<T: Show + Serialize + Send + Sync> ReplyData for Showing<T> {
44    fn write_json(&self, out: &mut Vec<u8>) -> serde_json::Result<()> {
45        self.0.write_json(out)
46    }
47
48    fn to_json(&self) -> serde_json::Result<Value> {
49        self.0.to_json()
50    }
51
52    fn shown(&self) -> Option<Shown<'_>> {
53        Some(self.0.show())
54    }
55}
56
57impl Show for Reply {
58    fn show(&self) -> Shown<'_> {
59        self.shown().unwrap_or(Shown::Null)
60    }
61}
62
63impl Reply {
64    #[must_use]
65    pub fn data(data: impl Serialize + Send + Sync + 'static) -> Self {
66        Self::Data(Box::new(data))
67    }
68
69    /// `None` when some data is only `Serialize`: the whole reply then renders from its JSON.
70    #[must_use]
71    pub fn shown(&self) -> Option<Shown<'_>> {
72        match self {
73            Self::Empty => Some(Shown::Null),
74            Self::Json(value) => Some(value.show()),
75            Self::Data(data) => data.shown(),
76            Self::List(replies) => replies
77                .iter()
78                .all(|reply| reply.shown().is_some())
79                .then_some(Shown::List(replies)),
80        }
81    }
82
83    pub fn into_json(self) -> Result<Value> {
84        match self {
85            Self::Json(value) => Ok(value),
86            other => other.to_json(),
87        }
88    }
89
90    pub fn to_json(&self) -> Result<Value> {
91        match self {
92            Self::Empty => Ok(Value::Null),
93            Self::Json(value) => Ok(value.clone()),
94            Self::Data(data) => data
95                .to_json()
96                .map_err(|error| refusal(data.as_ref(), error)),
97            Self::List(replies) => replies.iter().map(Self::to_json).collect(),
98        }
99    }
100
101    pub fn write_json(&self, out: &mut Vec<u8>) -> Result<()> {
102        match self {
103            Self::Empty => out.extend_from_slice(b"null"),
104            Self::Json(value) => serde_json::to_writer(out, value)
105                .map_err(|error| Error::internal("serializing a reply", error))?,
106            Self::Data(data) => data
107                .write_json(out)
108                .map_err(|error| refusal(data.as_ref(), error))?,
109            Self::List(replies) => {
110                out.push(b'[');
111                for (index, reply) in replies.iter().enumerate() {
112                    if index > 0 {
113                        out.push(b',');
114                    }
115                    reply.write_json(out)?;
116                }
117                out.push(b']');
118            }
119        }
120        Ok(())
121    }
122}
123
124fn refusal(data: &dyn ReplyData, error: serde_json::Error) -> Error {
125    Error::internal(format!("serializing a `{}` reply", data.type_name()), error)
126}
127
128impl fmt::Debug for Reply {
129    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::Empty => formatter.write_str("Empty"),
132            Self::Json(value) => formatter.debug_tuple("Json").field(value).finish(),
133            Self::Data(data) => formatter
134                .debug_tuple("Data")
135                .field(&data.type_name())
136                .finish(),
137            Self::List(replies) => formatter.debug_tuple("List").field(replies).finish(),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use std::collections::BTreeMap;
145
146    use serde_json::json;
147
148    use super::*;
149
150    fn written(reply: &Reply) -> String {
151        let mut out = Vec::new();
152        reply.write_json(&mut out).unwrap();
153        String::from_utf8(out).unwrap()
154    }
155
156    #[test]
157    fn typed_data_is_written_as_the_value_it_converts_to() {
158        let reply = Reply::List(vec![
159            Reply::data(BTreeMap::from([("name", "a\"b"), ("total", "1")])),
160            Reply::Empty,
161            Reply::Json(json!({ "id": 2 })),
162        ]);
163
164        let text = written(&reply);
165        assert_eq!(text, r#"[{"name":"a\"b","total":"1"},null,{"id":2}]"#);
166        assert_eq!(
167            serde_json::from_str::<Value>(&text).unwrap(),
168            reply.into_json().unwrap()
169        );
170    }
171
172    #[test]
173    fn data_json_cannot_hold_is_an_internal_error_naming_its_type() {
174        let reply = Reply::data(BTreeMap::from([((1, 2), "pair")]));
175
176        let error = reply.write_json(&mut Vec::new()).unwrap_err();
177        assert!(matches!(error, Error::Internal { .. }));
178        assert!(error.to_string().contains("BTreeMap"), "{error}");
179        assert!(matches!(reply.to_json(), Err(Error::Internal { .. })));
180    }
181}