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}