1use crate::jobs::{Job, JobID};
22use crate::utils::defaults::DEFAULT_TEXT_ITEM;
23use crate::utils::types::RUMString;
24use askama::PrimitiveType;
25use axum::extract::State;
26use tower_http::cors::CorsLayer;
27pub use phf_macros::phf_ordered_map as rumtk_create_const_ordered_map;
28use crate::defaults::{DEFAULT_LANG_ITEM, DEFAULT_THEME_ITEM};
29use crate::{NestedNestedTextMap, NestedTextMap, PipelineGroup, RootNestedNestedTextMap, TextMap};
30use reqwest::header;
31use rumtk_core::base::RUMVec;
32use rumtk_core::net::tcp::SafeLock;
33use rumtk_core::pipelines::pipeline_types::RUMCommandLine;
34use rumtk_core::serde::{RUMDeJson, RUMSerJson};
35use rumtk_core::types::RUMID;
36use rumtk_core::types::{RUMHashMap, RUMOrderedMap};
37use rumtk_core::{rumtk_generate_id, rumtk_new_lock};
38
39#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
40pub struct FlagsConf {
41 pub custom_css: bool,
42 pub enable_icons: bool,
43}
44#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
45pub struct HeaderConf {
46 pub logo_source: Option<RUMString>,
47 pub icon_source: Option<RUMString>,
48 pub icon_type: Option<RUMString>,
49 pub disable_navlinks: bool,
50 pub disable_logo: bool,
51}
52
53#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
54pub struct FooterConf {
55 pub socials_list: RUMString,
56 pub disable_contact_button: bool,
57}
58
59#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
60pub struct PipelineConf {
61 pub settings: Option<TextMap>,
62 pub data_templates: Option<NestedTextMap>,
63 pub targets: Option<TextMap>,
64 pub categories: Option<RUMHashMap<RUMString, PipelineGroup>>
65}
66
67impl PipelineConf {
68 pub fn get_settings(&self) -> Option<&TextMap> {
69 self.settings.as_ref()
70 }
71
72 pub fn get_pipeline_category(&self, pipeline_category: &str) -> Option<&PipelineGroup> {
73 match self.categories {
74 Some(ref categories) => {
75 match categories.get(pipeline_category) {
76 Some(pipelines) => Some(pipelines),
77 None => None
78 }
79 }
80 None => None,
81 }
82 }
83 pub fn get_available_pipeline_names(&self) -> Vec<&RUMString> {
84 match self.targets.as_ref() {
85 Some(group) => {
86 let mut keys = group.keys().collect::<Vec<&RUMString>>();
87 keys.sort_unstable();
88 keys
89 },
90 None => vec![]
91 }
92 }
93 pub fn get_pipeline(&self, pipeline_category: &str, pipeline_name: &str) -> RUMCommandLine {
94 match self.get_pipeline_category(pipeline_category) {
95 Some(group) => match group.get(pipeline_name) {
96 Some(pipeline) => pipeline.to_owned(),
97 None => RUMCommandLine::new()
98 },
99 None => RUMCommandLine::new()
100 }
101 }
102
103 pub fn get_target(&self, profile: &str) -> RUMString {
104 match self.targets.as_ref() {
105 Some(targets) => match targets.get(profile) {
106 Some(pipeline) => pipeline.to_owned(),
107 None => RUMString::default()
108 },
109 None => RUMString::default()
110 }
111 }
112
113 pub fn get_template(&self, name: &str) -> Option<&TextMap> {
114 match self.data_templates.as_ref() {
115 Some(templates) => templates.get(name),
116 None => None
117 }
118 }
119
120 pub fn get_available_data_templates(&self) -> Vec<&RUMString> {
121 match self.data_templates.as_ref() {
122 Some(group) => {
123 let mut keys = group.keys().collect::<Vec<&RUMString>>();
124 keys.sort_unstable();
125 keys
126 },
127 None => vec![]
128 }
129 }
130}
131
132#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
133pub struct PageConf {
134 pub url: RUMString,
135 pub _static: bool,
136}
137
138pub type PageMap = RUMOrderedMap<RUMString, PageConf>;
139
140#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
141pub struct RouterConf {
142 pub pages: Option<PageMap>,
143 pub redirect: Option<TextMap>,
144 pub service_routes: Option<NestedTextMap>,
145}
146
147impl RouterConf {
148 pub fn get_page(&self, name: &RUMString) -> Option<&PageConf> {
149 match &self.pages {
150 Some(pages) => pages.get(name),
151 None => None
152 }
153 }
154
155 pub fn get_redirect(&self, name: &RUMString) -> Option<&RUMString> {
156 match &self.redirect {
157 Some(redirects) => redirects.get(name),
158 None => None
159 }
160 }
161
162 pub fn get_service_route(&self, name: &RUMString) -> Option<TextMap> {
163 match &self.service_routes {
164 Some(service_routes) => Some(service_routes.get(name)?.clone()),
165 None => None
166 }
167 }
168}
169
170#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
171pub struct CORSConf {
172 pub origins: Option<RUMVec<RUMString>>,
173 pub methods: Option<RUMVec<RUMString>>,
174 pub headers: Option<RUMVec<RUMString>>,
175 pub allow_credentials: bool,
176 pub allow_private_network: bool,
177}
178
179impl CORSConf {
180 pub fn build_cors_layer(&self) -> CorsLayer {
181 use axum::http::HeaderValue;
182 use axum::http::{header, method};
183
184 let mut cors = CorsLayer::new();
185 match &self.origins {
186 Some(origins) => {
187 for origin in origins {
188 let o = origin.parse::<HeaderValue>().unwrap();
189 cors = cors.allow_origin(o);
190 }
191 },
192 None => {},
193 }
194 match &self.methods {
195 Some(methods) => {
196 let m: RUMVec<method::Method> = methods.iter().map(|method| method.parse::<method::Method>().unwrap()).collect();
197 cors = cors.allow_methods(m);
198 },
199 None => {}
200 }
201 match &self.headers {
202 Some(headers) => {
203 let h: RUMVec<header::HeaderName> = headers.iter().map(|header| header.parse::<header::HeaderName>().unwrap()).collect();
204 cors = cors.allow_headers(h);
205 },
206 None => {}
207 }
208 cors = cors.allow_credentials(self.allow_credentials);
209 if self.allow_credentials {
210 cors = cors.allow_headers([header::AUTHORIZATION, header::ACCEPT]);
211 }
212 cors = cors.allow_private_network(self.allow_private_network);
213 cors
214 }
215}
216
217#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone)]
225pub struct AppConf {
226 pub title: RUMString,
227 pub description: RUMString,
228 pub company: RUMString,
229 pub copyright: RUMString,
230 pub lang: RUMString,
231 pub theme: RUMString,
232 pub flags: FlagsConf,
233 pub header_conf: HeaderConf,
234 pub footer_conf: FooterConf,
235
236 strings: RootNestedNestedTextMap,
237 config: NestedNestedTextMap,
238 pipelines: PipelineConf,
239 pub router: RouterConf,
240 pub cors: Option<CORSConf>,
241 }
243
244impl AppConf {
245 pub fn update_site_info(
246 &mut self,
247 title: RUMString,
248 description: RUMString,
249 company: RUMString,
250 copyright: RUMString,
251 ) {
252 if !title.is_empty() {
253 self.title = title;
254 }
255 if !company.is_empty() {
256 self.company = company;
257 }
258 if !description.is_empty() {
259 self.description = description;
260 }
261 if !copyright.is_empty() {
262 self.copyright = copyright;
263 }
264 }
265
266 pub fn get_pipelines(&self) -> &PipelineConf {
267 &self.pipelines
268 }
269
270 pub fn get_text(&self, item: &str) -> NestedTextMap {
271 match self.strings.get(&self.lang) {
272 Some(l) => match l.get(item) {
273 Some(i) => i.clone(),
274 None => NestedTextMap::default(),
275 },
276 None => NestedTextMap::default(),
277 }
278 }
279
280 pub fn get_section(&self, section: &str) -> TextMap {
281 match self.config.get(&self.lang) {
282 Some(l) => match l.get(section) {
283 Some(i) => i.clone(),
284 None => self.get_default_item(section),
285 },
286 None => self.get_default_item(section),
287 }
288 }
289
290 pub fn get_default_item(&self, section: &str) -> TextMap {
291 match self.config.get(DEFAULT_TEXT_ITEM) {
292 Some(l) => match l.get(section) {
293 Some(i) => i.clone(),
294 None => TextMap::default(),
295 },
296 None => TextMap::default(),
297 }
298 }
299}
300
301impl Default for AppConf {
302 fn default() -> Self {
303 AppConf {
304 title: "".to_string(),
305 description: "".to_string(),
306 company: "".to_string(),
307 copyright: "".to_string(),
308 lang: DEFAULT_LANG_ITEM.to_string(),
309 theme: DEFAULT_THEME_ITEM.to_string(),
310 flags: FlagsConf::default(),
311 header_conf: HeaderConf::default(),
312 footer_conf: FooterConf::default(),
313 strings: RootNestedNestedTextMap::default(),
314 config: NestedNestedTextMap::default(),
315 pipelines: PipelineConf::default(),
316 router: RouterConf::default(),
317 cors: Some(CORSConf::default()),
318 }
319 }
320}
321
322pub type ClipboardID = RUMString;
323#[derive(Default, Debug, Clone)]
329pub struct AppState {
330 config: AppConf,
331 clipboard: NestedTextMap,
332 jobs: RUMHashMap<RUMID, Job>,
333}
334
335pub type SharedAppState = SafeLock<AppState>;
336
337impl AppState {
338 pub fn new() -> AppState {
339 AppState {
340 config: AppConf::default(),
341 clipboard: NestedTextMap::default(),
342 jobs: RUMHashMap::default(),
343 }
344 }
345
346 pub fn new_safe() -> SharedAppState {
347 rumtk_new_lock!(AppState::new())
348 }
349
350 pub fn from_safe(conf: AppConf) -> SharedAppState {
351 rumtk_new_lock!(AppState::from(conf))
352 }
353
354 pub fn get_config(&self) -> &AppConf {
355 &self.config
356 }
357
358 pub fn get_config_mut(&mut self) -> &mut AppConf {
359 &mut self.config
360 }
361
362 pub fn has_clipboard(&self, id: &ClipboardID) -> bool {
363 self.clipboard.contains_key(id)
364 }
365
366 pub fn has_job(&self, id: &JobID) -> bool {
367 self.jobs.contains_key(id)
368 }
369
370 pub fn push_job_result(&mut self, id: &JobID, job: Job) {
371 self.jobs.insert(id.clone(), job);
372 }
373
374 pub fn push_to_clipboard(&mut self, data: TextMap) -> ClipboardID {
375 let clipboard_id = rumtk_generate_id!().to_string();
376 self.clipboard.insert(clipboard_id.clone(), data);
377 clipboard_id
378 }
379
380 pub fn request_clipboard_slice(&mut self) -> ClipboardID {
381 let clipboard_id = rumtk_generate_id!().to_string();
382 self.clipboard
383 .insert(clipboard_id.clone(), TextMap::default());
384 clipboard_id
385 }
386
387 pub fn pop_job(&mut self, id: &RUMID) -> Option<Job> {
388 self.jobs.remove(id)
389 }
390
391 pub fn pop_clipboard(&mut self, id: &ClipboardID) -> Option<TextMap> {
392 self.clipboard.shift_remove(id)
393 }
394}
395
396impl From<AppConf> for AppState {
397 fn from(config: AppConf) -> Self {
398 AppState {
399 config,
400 clipboard: NestedTextMap::default(),
401 jobs: RUMHashMap::default(),
402 }
403 }
404}
405
406pub type RouterAppState = State<SharedAppState>;
407
408#[macro_export]
451macro_rules! rumtk_web_load_conf {
452 ( $args:expr ) => {{
453 use $crate::defaults::{DEFAULT_APP_CONFIG};
454 rumtk_web_load_conf!($args, DEFAULT_APP_CONFIG)
455 }};
456 ( $args:expr, $path:expr ) => {{
457 use rumtk_core::rumtk_deserialize;
458 use rumtk_core::strings::RUMStringConversions;
459 use rumtk_core::types::RUMHashMap;
460 use $crate::AppConf;
461 use std::fs;
462
463 use $crate::rumtk_web_save_conf;
464 use $crate::utils::{AppState, TextMap};
465
466 let json = match fs::read_to_string($path) {
467 Ok(json) => json,
468 Err(err) => rumtk_web_save_conf!($path),
469 };
470
471 let mut conf: AppConf = match rumtk_deserialize!(&json) {
472 Ok(conf) => conf,
473 Err(err) => panic!(
474 "The App config file in {} does not meet the expected structure. \
475 See the documentation for more information. Error: {}\n{}",
476 $path, err, json
477 ),
478 };
479 conf.update_site_info(
480 $args.title.clone(),
481 $args.description.clone(),
482 $args.company.clone(),
483 $args.copyright.clone(),
484 );
485 AppState::from_safe(conf)
486 }};
487}
488
489#[macro_export]
521macro_rules! rumtk_web_save_conf {
522 ( ) => {{
523 $crate::utils::defaults::DEFAULT_APP_CONFIG;
524 rumtk_web_save_conf!(DEFAULT_APP_CONFIG)
525 }};
526 ( $path:expr ) => {{
527 use rumtk_core::rumtk_serialize;
528 use rumtk_core::strings::RUMStringConversions;
529 use std::fs;
530 use $crate::utils::AppConf;
531
532 let json = rumtk_serialize!(&AppConf::default()).unwrap_or_default();
533 fs::write($path, &json);
534 json
535 }};
536}
537
538#[macro_export]
543macro_rules! rumtk_web_get_config_string {
544 ( $conf:expr, $item:expr ) => {{
545 use $crate::rumtk_web_get_config;
546 use $crate::AppConf;
547 rumtk_web_get_config!($conf).get_text($item)
548 }};
549}
550
551#[macro_export]
557macro_rules! rumtk_web_get_config_section {
558 ( $conf:expr, $item:expr ) => {{
559 use $crate::rumtk_web_get_config;
560 use $crate::AppConf;
561 rumtk_web_get_config!($conf).get_section($item)
562 }};
563}
564
565#[macro_export]
583macro_rules! rumtk_web_get_pipelines {
584 ( $conf:expr ) => {{
585 use $crate::rumtk_web_get_config;
586 use $crate::AppConf;
587 rumtk_web_get_config!($conf).get_pipelines()
588 }};
589}
590
591#[macro_export]
609macro_rules! rumtk_web_get_config {
610 ( $state:expr ) => {{
611 use rumtk_core::{rumtk_lock_read};
612 rumtk_lock_read!($state.clone()).get_config()
613 }};
614}
615
616#[macro_export]
638macro_rules! rumtk_web_set_config {
639 ( $state:expr ) => {{
640 use rumtk_core::rumtk_lock_write;
641 rumtk_lock_write!($state.clone()).get_config_mut()
642 }};
643}
644
645#[macro_export]
664macro_rules! rumtk_web_modify_state {
665 ( $state:expr ) => {{
666 use rumtk_core::rumtk_lock_write;
667 rumtk_lock_write!($state.clone())
668 }};
669}