credence_lib/render/
rendered_page.rs

1use super::{
2    super::{configuration::*, middleware::*},
3    annotations::*,
4    context::*,
5    templates::*,
6};
7
8use {
9    ::axum::{http::*, response::Response},
10    compris::{normal::*, *},
11    httpdate::*,
12    kutil::{
13        http::*,
14        std::{error::*, immutable::*},
15    },
16    std::{io, path::*, result::Result},
17    tokio::{fs::*, io::*},
18};
19
20//
21// RenderedPageType
22//
23
24/// Rendered page type.
25#[derive(Clone, Copy, Debug)]
26pub enum RenderedPageType {
27    /// Content with optional embedded annotations.
28    ContentWithEmbeddedAnnotations,
29
30    /// Just annotations.
31    Annotations(Format),
32}
33
34//
35// RenderedPage
36//
37
38/// Rendered page.
39#[derive(Clone, Debug)]
40pub struct RenderedPage {
41    /// Headers.
42    pub headers: HeaderMap,
43
44    /// Annotations.
45    pub annotations: Annotations,
46
47    /// Content.
48    pub content: Option<ByteString>,
49}
50
51impl RenderedPage {
52    /// Constructor.
53    pub async fn new_from_response(
54        identifier: &str,
55        rendered_page_type: RenderedPageType,
56        response: Response,
57        configuration: &RenderConfiguration,
58    ) -> Result<Self, StatusCode> {
59        let headers = response.headers().clone();
60        let body = response.into_body();
61
62        let (body, _trailers) = body
63            .read_into_string(configuration.max_content_size.inner.into())
64            .await
65            .map_err_internal_server("read body into string")?;
66
67        let (annotations, content) = Self::split(identifier, rendered_page_type, &body, configuration);
68
69        Ok(Self { headers, annotations, content })
70    }
71
72    /// Constructor.
73    pub async fn new_from_file<PathT>(
74        rendered_page_type: RenderedPageType,
75        path: PathT,
76        configuration: &RenderConfiguration,
77    ) -> io::Result<Self>
78    where
79        PathT: AsRef<Path>,
80    {
81        let path = path.as_ref();
82        let mut file = File::open(path).await.with_path(path)?;
83        let mut string = String::default();
84        file.read_to_string(&mut string).await?;
85
86        let (annotations, content) =
87            Self::split(path.to_string_lossy().as_ref(), rendered_page_type, &string, configuration);
88
89        Ok(Self { headers: Default::default(), annotations, content })
90    }
91
92    /// Create a [RenderContext].
93    pub fn context<'own>(
94        &'own self,
95        socket: Option<Socket>,
96        uri_path: ByteString,
97        original_uri_path: Option<ByteString>,
98        query: Option<QueryMap>,
99        last_modified: Option<HttpDate>,
100        is_json: (bool, bool),
101        templates: &'own Templates,
102        configuration: &'own CredenceConfiguration,
103    ) -> RenderContext<'own> {
104        // Our variables override global variables
105        let mut variables = configuration.render.variables.clone();
106        for (key, value) in &self.annotations.variables {
107            variables.insert(key.clone(), value.clone());
108        }
109
110        RenderContext::new(
111            self,
112            variables,
113            socket,
114            uri_path,
115            original_uri_path,
116            query,
117            last_modified,
118            is_json,
119            self.annotations.renderer(&configuration.render).clone(),
120            templates,
121            configuration,
122        )
123    }
124
125    /// Merge annotations headers into headers.
126    pub fn merged_headers(&self) -> Result<HeaderMap, StatusCode> {
127        let mut headers = self.headers.clone();
128        headers.set_string_values(self.annotations.headers.iter()).map_err_internal_server("header value")?;
129        Ok(headers)
130    }
131
132    /// Get the title from the annotations or fallback to extracting it from the content.
133    pub fn title(&self, configuration: &RenderConfiguration) -> Result<Option<ByteString>, StatusCode> {
134        Ok(match self.annotations.variables.get("title") {
135            Some(title) => match title {
136                Variant::Text(title) => Some(title.inner.clone()),
137                _ => None,
138            },
139
140            None => {
141                let renderer = self.annotations.renderer(configuration);
142                match self.content.as_ref() {
143                    Some(content) => renderer.title_from_content(&content)?,
144                    None => None,
145                }
146            }
147        })
148    }
149
150    /// Split [Annotations] from content.
151    pub fn split(
152        identifier: &str,
153        rendered_page_type: RenderedPageType,
154        string: &str,
155        configuration: &RenderConfiguration,
156    ) -> (Annotations, Option<ByteString>) {
157        match rendered_page_type {
158            RenderedPageType::Annotations(format) => {
159                let annotations = Annotations::parse(identifier, &string, format);
160                (annotations, None)
161            }
162
163            RenderedPageType::ContentWithEmbeddedAnnotations => {
164                let (annotations, content) = configuration.annotations.split(identifier, &string);
165                (annotations, Some(content.into()))
166            }
167        }
168    }
169}