sofie_http/
client_page.rs1use http::StatusCode;
5use serde_json::{Map, Value, json};
6use sofie_core::{FieldError, Result};
7use sofie_db::Reply;
8use sofie_engine::{ClientBundle, ClientLinks, ClientShell};
9
10use crate::answer::{self, JSON, Refusal, Response};
11use crate::caller::Caller;
12use crate::language::Language;
13use crate::screen::{ScreenPage, new_nonce, with_policy};
14
15fn envelope(screen: &ScreenPage<'_>, reply: Result<Reply>) -> Result<Value> {
17 let (mut data, typed) = match reply? {
18 Reply::Json(data) => (data, None),
19 typed => (typed.to_json()?, Some(typed)),
20 };
21 screen.withhold(&mut data);
22 let rendering = screen.rendering();
23 #[cfg(feature = "listing")]
24 rendering.translate_listing_errors(&mut data);
25 let instants = typed
26 .as_ref()
27 .and_then(Reply::shown)
28 .map_or(Value::Null, |shown| {
29 sofie_engine::instant_fields(shown, &data)
30 });
31 Ok(json!({
32 "screen": screen.template.name(),
33 "title": screen.title,
34 "language": screen.language.tag(),
35 "data": data,
36 "instants": instants,
37 "view": rendering.client_view(),
38 "navigation": screen.navigation_entries(),
39 }))
40}
41
42pub(crate) fn enveloped(screen: &ScreenPage<'_>, reply: Result<Reply>) -> Response {
43 let answered = envelope(screen, reply)
44 .map(|envelope| answer::response(StatusCode::OK, Some(JSON), envelope.to_string()));
45 answer::or_failure(answered, Caller::Screen)
46}
47
48pub(crate) fn shell(
49 screen: &ScreenPage<'_>,
50 bundle: &ClientBundle,
51 reply: Result<Reply>,
52) -> Result<Response> {
53 let envelope = envelope(screen, reply)?;
54 let nonce = new_nonce()?;
55 let links = links(bundle);
56 let html = ClientShell {
57 title: screen.title,
58 lang: screen.language.tag(),
59 nonce: &nonce,
60 envelope: &envelope,
61 links: &links,
62 #[cfg(feature = "islands")]
63 islands: screen.islands.as_ref(),
64 #[cfg(not(feature = "islands"))]
65 islands: None,
66 }
67 .render();
68 with_policy(html, screen, &nonce)
69}
70
71fn links(bundle: &ClientBundle) -> ClientLinks {
72 #[cfg(all(feature = "development", debug_assertions))]
73 if let Some(links) = crate::development::client_links(bundle.area) {
74 return links;
75 }
76 bundle.links()
77}
78
79pub(crate) fn refused(refusal: &Refusal, language: Language<'_>) -> Response {
82 let errors: Vec<Value> = match refusal {
83 Refusal::Fields(fields) => fields
84 .iter()
85 .map(|error| field_error(error, language))
86 .collect(),
87 Refusal::Message(..) => Vec::new(),
88 };
89 let body = json!({ "errors": errors, "notice": refusal.messages(language) });
90 answer::response(refusal.status(), Some(JSON), body.to_string())
91}
92
93fn field_error(error: &FieldError, language: Language<'_>) -> Value {
94 let arguments: Map<String, Value> = error
95 .arguments
96 .iter()
97 .map(|(name, value)| (name.to_string(), Value::from(value.as_str())))
98 .collect();
99 json!({
100 "field": error.field,
101 "key": error.key,
102 "arguments": arguments,
103 "message": language.field_error(error),
104 })
105}
106
107impl crate::Screens {
108 pub fn clients(&mut self, bundles: Vec<ClientBundle>) -> &mut Self {
110 self.clients = bundles;
111 self
112 }
113
114 pub(crate) fn client(&self, area: &str) -> Option<&ClientBundle> {
115 self.clients.iter().find(|bundle| bundle.area == area)
116 }
117}
118
119impl crate::assets::Assets {
120 pub(crate) fn with_clients(mut self, screens: &crate::Screens) -> Self {
121 for bundle in &screens.clients {
122 for asset in [bundle.script, bundle.stylesheet] {
123 self.served.push((asset.path(), asset));
124 }
125 }
126 self
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn a_refused_form_s_errors_keep_their_key_and_arguments() {
136 let error =
137 FieldError::new("code", "validation.max_length", "must be at most 4").with("max", "4");
138
139 let response = refused(&Refusal::Fields(vec![error]), Language::plain());
140
141 let body: Value =
142 serde_json::from_slice(&response.into_body().into_inner().unwrap()).unwrap();
143 assert_eq!(
144 body["errors"],
145 json!([{
146 "field": "code",
147 "key": "validation.max_length",
148 "arguments": { "max": "4" },
149 "message": "must be at most 4",
150 }])
151 );
152 }
153}