Skip to main content

lib_humus/
engine.rs

1// SPDX-FileCopyrightText: 2026 Slatian <baschdel@disroot.org>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5use axum::response::Response;
6
7use std::collections::HashSet;
8use std::marker::PhantomData;
9use std::sync::Arc;
10
11use crate::HumusQuerySettings;
12use crate::HumusView;
13use crate::language::LanguageEngine;
14use crate::middleware::TemplateSettingsLayer;
15use crate::templating::{
16	FormatChooser, HumusApiFormat, HumusFormatIdentifier, TemplatesManifest, TemplatingEngine,
17};
18
19/* The engine itself */
20
21#[allow(clippy::doc_overindented_list_items)]
22/// 🌱 An all in one datatype that takes care of frontend rendering.
23///
24/// You should construct one using a [crate::HumusEngineLoader] unless you have a very
25/// good reason not to.
26///
27/// The `ApiFormat` type defines which API responses your implementation can produce.
28/// For a good default you can use [JsonOnlyApiFormat][crate::templating::JsonOnlyApiFormat].
29///
30/// <b>Note:</b> The private field is a [PhantomData] because the engine only works
31///              correctly when `ApiFormat` stays the same throuout its life, but
32///              it doesn't have to store one of those.
33///
34/// The HumusEngine itself is a container for the [TemplatingEngine] which takes care of
35/// rendering text templates and API responses and the [LanguageEngine] which can be used
36/// by the TemplateEngine and your code to provide localized results.
37///
38/// Your main point of interaction will be the [render_view][Self::render_view] function
39/// which you can use after fetching all the data your frontend needs to tun the data into
40/// an HTTP response.
41#[derive(Debug, Clone)]
42pub struct HumusEngine<ApiFormat>
43where
44	ApiFormat: HumusApiFormat,
45{
46	/// The templating engine responsible for rendering text templates
47	pub templating_engine: TemplatingEngine<ApiFormat>,
48
49	/// The language engine that is responsible for localizing this template
50	pub language_engine: Arc<LanguageEngine>,
51
52	phantom_format: PhantomData<ApiFormat>,
53}
54
55impl<ApiFormat> HumusEngine<ApiFormat>
56where
57	ApiFormat: HumusApiFormat,
58{
59	/// Creates a new Templating Engine.
60	///
61	/// An alternative would be converting from a [HumusProtoEngine].
62	///
63	/// [HumusProtoEngine]: ./struct.HumusProtoEngine.html
64	pub fn new(
65		templating_engine: TemplatingEngine<ApiFormat>,
66		language_engine: Arc<LanguageEngine>,
67	) -> Self {
68		Self {
69			templating_engine,
70			language_engine,
71			phantom_format: PhantomData,
72		}
73	}
74
75	/// Takes settings and a view, converting it to a serveable response.
76	///
77	/// Example:
78	/// ```rust,ignore
79	/// async fn hello_world_handler(
80	/// 	State(arc_state): State<Arc<ServiceSharedState>>,
81	/// 	Extension(settings): Extension<QuerySettings>,
82	/// ) -> Response {
83	/// 	let state = Arc::clone(&arc_state);
84	///
85	/// 	state.templating_engine.render_view(
86	/// 		&settings,
87	/// 		View::Message{
88	/// 			title: "Hey There!".to_string(),
89	/// 			message: "You are an awesome creature!".to_string()
90	/// 		},
91	/// 	)
92	/// }
93	///
94	/// ```
95	pub fn render_view<S: HumusQuerySettings>(
96		&self,
97		settings: &S,
98		view: impl HumusView<S, ApiFormat>,
99	) -> Response {
100		self.templating_engine
101			.render_view(settings, view, &self.language_engine)
102	}
103
104	/// Accessor for the localization engine inside this HumusEngine
105	///
106	/// You can use this to localize text elsewhere in your code.
107	pub fn language_engine(&self) -> &LanguageEngine {
108		&self.language_engine
109	}
110
111	/// Accessor for the templates manifest inside this HumusEngine
112	#[inline]
113	pub fn templates_manifest(&self) -> &TemplatesManifest {
114		&self.templating_engine.templates_manifest
115	}
116
117	/// Returns a HashSet of all valid format identifiers this HumusEngine can render
118	pub fn list_available_formats(&self) -> HashSet<HumusFormatIdentifier> {
119		let mut set: HashSet<HumusFormatIdentifier> = ApiFormat::get_all()
120			.into_iter()
121			.map(|f| f.get_name())
122			.collect();
123
124		for identifier in self.templates_manifest().format.keys() {
125			set.insert(*identifier);
126		}
127
128		set
129	}
130
131	/// Returns a [FormatChooser] matching this HumusEngine.
132	pub fn get_format_chooser(&self) -> FormatChooser {
133		FormatChooser::new::<ApiFormat>(self.templates_manifest())
134	}
135
136	/// Constructs a ready to use [TemplateSettingsLayer] that matches the configuration of this engine.
137	pub fn get_template_settings_layer(&self) -> TemplateSettingsLayer {
138		TemplateSettingsLayer::new(
139			self.templates_manifest().clone(),
140			self.language_engine.language_manifest().clone(),
141			self.get_format_chooser(),
142		)
143	}
144}