1mod body;
2mod content_directory;
3mod content_engine;
4mod content_index;
5mod content_item;
6mod content_registry;
7mod handlebars_helpers;
8mod mime;
9mod route;
10mod test_lib;
11
12use crate::bug_message;
13use bytes::Bytes;
14use content_item::RenderingFailedError;
15use futures::Stream;
16use serde::Serialize;
17use std::collections::HashMap;
18use std::io;
19use thiserror::Error;
20
21pub use self::mime::{MediaRange, MediaType};
22pub use content_directory::ContentDirectory;
23pub use content_engine::{
24 ContentEngine, ContentLoadingError, FilesystemBasedContentEngine, TemplateError,
25};
26pub use content_index::ContentIndex;
27pub use content_item::UnregisteredTemplate;
28pub use content_registry::{ContentRepresentations, RegisteredContent};
29pub use route::Route;
30
31pub trait ByteStream: Stream<Item = Result<Bytes, StreamError>>
33where
34 Self: Unpin,
35{
36}
37impl<T> ByteStream for T where T: Stream<Item = Result<Bytes, StreamError>> + Unpin {}
38
39pub struct Media<Content: ByteStream> {
41 pub media_type: MediaType,
42 pub content: Content,
43}
44impl<Content: ByteStream> Media<Content> {
45 fn new(media_type: MediaType, content: Content) -> Self {
46 Self {
47 media_type,
48 content,
49 }
50 }
51}
52
53#[derive(Error, Debug)]
57pub enum RenderError {
58 #[error(transparent)]
59 RenderingFailed(RenderingFailedError),
60
61 #[error("The requested content cannot be rendered as an acceptable media type.")]
62 CannotProvideAcceptableMediaType,
63
64 #[doc(hidden)]
65 #[error("{} This should never happen: {}", bug_message!(), .0)]
66 Bug(String),
67}
68
69#[derive(Error, Debug)]
71pub enum StreamError {
72 #[error(
73 "Process exited with {}{}",
74 match .exit_code {
75 Some(code) => format!("code {code}"),
76 None => String::from("unknown code"),
77 },
78 .stderr_contents.as_ref().map(|message| format!(": {message}")).unwrap_or_default(),
79 )]
80 ExecutableExitedWithNonzero {
81 pid: u32,
82 exit_code: Option<i32>,
83 stderr_contents: Option<String>,
84 },
85
86 #[error("Executable output could not be captured")]
87 ExecutableOutputCouldNotBeCaptured { pid: u32 },
88
89 #[error("Input/output error during rendering")]
90 IOError {
91 #[from]
92 source: io::Error,
93 },
94
95 #[error("Stream was cancelled")]
96 Canceled,
97}
98
99pub trait Render {
100 type Output;
101 fn render<'engine, 'accept, ServerInfo, Engine, Accept>(
102 &self,
103 context: RenderContext<'engine, ServerInfo, Engine>,
104 acceptable_media_ranges: Accept,
105 ) -> Result<Media<Self::Output>, RenderError>
106 where
107 ServerInfo: Clone + Serialize,
108 Engine: ContentEngine<ServerInfo>,
109 Accept: IntoIterator<Item = &'accept MediaRange>,
110 Self::Output: ByteStream;
111}
112
113const TARGET_MEDIA_TYPE_PROPERTY_NAME: &str = "target-media-type";
116const REQUEST_DATA_PROPERTY_NAME: &str = "request";
117const ROUTE_PROPERTY_NAME: &str = "route";
118const QUERY_PARAMETERS_PROPERTY_NAME: &str = "query-parameters";
119const REQUEST_HEADERS_PROPERTY_NAME: &str = "request-headers";
120
121#[derive(Clone, Serialize)]
123#[serde(rename_all = "kebab-case")]
124pub struct RequestData {
125 pub route: Option<Route>,
127
128 pub query_parameters: HashMap<String, String>,
130
131 pub request_headers: HashMap<String, String>,
133}
134
135#[derive(Clone, Serialize)]
139#[serde(rename_all = "kebab-case")]
140pub struct RenderData<ServerInfo: Clone + Serialize> {
141 #[serde(rename = "/")]
144 pub index: ContentIndex,
145
146 pub server_info: ServerInfo,
148
149 pub target_media_type: Option<MediaType>,
152
153 pub request: RequestData,
155
156 pub error_code: Option<u16>,
160}
161
162pub struct RenderContext<'engine, ServerInfo, Engine>
165where
166 ServerInfo: Clone + Serialize,
167 Engine: ContentEngine<ServerInfo>,
168{
169 content_engine: &'engine Engine,
170 data: RenderData<ServerInfo>,
171 handlebars_render_context: Option<handlebars::RenderContext<'engine, 'engine>>,
172}
173
174impl<'engine, ServerInfo, Engine> RenderContext<'engine, ServerInfo, Engine>
175where
176 ServerInfo: Clone + Serialize,
177 Engine: ContentEngine<ServerInfo>,
178{
179 pub fn into_error_context(self, error_code: u16) -> Self {
180 RenderContext {
181 data: RenderData {
182 error_code: Some(error_code),
183 ..self.data
184 },
185 ..self
186 }
187 }
188
189 pub fn with_handlebars_render_context(
190 self,
191 handlebars_render_context: handlebars::RenderContext<'engine, 'engine>,
192 ) -> Self {
193 RenderContext {
194 handlebars_render_context: Some(handlebars_render_context),
195 ..self
196 }
197 }
198}