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