rumtk_web/utils/
conf.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2025  Luis M. Santos, M.D.
5 * Copyright (C) 2025  MedicalMasses L.L.C.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21use crate::utils::defaults::DEFAULT_TEXT_ITEM;
22use crate::utils::types::RUMString;
23use axum::extract::State;
24use phf::OrderedMap;
25pub use phf_macros::phf_ordered_map as rumtk_create_const_ordered_map;
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28use std::sync::{Arc, Mutex};
29
30pub type TextMap = HashMap<RUMString, RUMString>;
31pub type NestedTextMap = HashMap<RUMString, TextMap>;
32pub type NestedNestedTextMap = HashMap<RUMString, NestedTextMap>;
33pub type RootNestedNestedTextMap = HashMap<RUMString, NestedNestedTextMap>;
34pub type RootRootNestedNestedTextMap = HashMap<RUMString, RootNestedNestedTextMap>;
35
36pub type ConstTextMap = OrderedMap<&'static str, &'static str>;
37pub type ConstNestedTextMap = OrderedMap<&'static str, &'static ConstTextMap>;
38pub type ConstNestedNestedTextMap = OrderedMap<&'static str, &'static ConstNestedTextMap>;
39
40///
41/// This is a core structure in a web project using the RUMTK framework. This structure contains
42/// a series of fields that represent the web app initial state or configuration. The idea is that
43/// the web app can come bundled with a JSON config file following this structure which we can load
44/// at runtime. The settings will dictate a few key project behaviors such as properly labeling
45/// some components with the company name or use the correct language text.
46///
47#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
48pub struct AppConf {
49    pub title: RUMString,
50    pub description: RUMString,
51    pub copyright: RUMString,
52    pub lang: RUMString,
53    pub theme: RUMString,
54    pub custom_css: bool,
55
56    strings: RootRootNestedNestedTextMap,
57    config: NestedNestedTextMap,
58    //pub opts: TextMap,
59}
60
61impl AppConf {
62    pub fn update_site_info(
63        &mut self,
64        title: RUMString,
65        description: RUMString,
66        copyright: RUMString,
67    ) {
68        if !title.is_empty() {
69            self.title = title;
70        }
71        if !description.is_empty() {
72            self.description = description;
73        }
74        if !copyright.is_empty() {
75            self.copyright = copyright;
76        }
77    }
78
79    pub fn get_text(&self, item: &str) -> NestedNestedTextMap {
80        match self.strings.get(&self.lang) {
81            Some(l) => match l.get(item) {
82                Some(i) => i.clone(),
83                None => NestedNestedTextMap::default(),
84            },
85            None => NestedNestedTextMap::default(),
86        }
87    }
88
89    pub fn get_conf(&self, section: &str) -> TextMap {
90        match self.config.get(section) {
91            Some(l) => match l.get(&self.lang) {
92                Some(i) => i.clone(),
93                None => match l.get(DEFAULT_TEXT_ITEM) {
94                    Some(i) => i.clone(),
95                    None => TextMap::default(),
96                },
97            },
98            None => TextMap::default(),
99        }
100    }
101}
102
103pub type SharedAppConf = Arc<Mutex<AppConf>>;
104pub type RouterAppConf = State<Arc<Mutex<AppConf>>>;
105
106#[macro_export]
107macro_rules! rumtk_web_load_conf {
108    ( $args:expr ) => {{
109        rumtk_web_load_conf!($args, "./app.json")
110    }};
111    ( $args:expr, $path:expr ) => {{
112        use rumtk_core::strings::RUMStringConversions;
113        use rumtk_core::{rumtk_deserialize, rumtk_serialize};
114        use std::fs::read_to_string;
115        use std::sync::{Arc, Mutex};
116        use $crate::utils::AppConf;
117
118        let json = match read_to_string($path) {
119            Ok(json) => json,
120            Err(err) => rumtk_serialize!(AppConf::default())?,
121        };
122
123        let mut conf: AppConf = match rumtk_deserialize!(json) {
124            Ok(conf) => conf,
125            Err(err) => panic!(
126                "The App config file in {} does not meet the expected structure. \
127                    See the documentation for more information. Error: {}\n{}",
128                $path, err, json
129            ),
130        };
131        conf.update_site_info(
132            $args.title.clone(),
133            $args.description.clone(),
134            $args.copyright.clone(),
135        );
136        Arc::new(Mutex::new(conf))
137    }};
138}
139
140#[macro_export]
141macro_rules! rumtk_web_get_string {
142    ( $conf:expr, $item:expr ) => {{
143        let owned_state = $conf.lock().expect("Lock failure");
144        owned_state.get_text($item)
145    }};
146}
147
148#[macro_export]
149macro_rules! rumtk_web_get_conf {
150    ( $conf:expr, $item:expr ) => {{
151        let owned_state = $conf.lock().expect("Lock failure");
152        owned_state.get_conf($item)
153    }};
154}
155
156/*
157   Default non static data to minimize allocations.
158*/
159pub const DEFAULT_TEXT: fn() -> RUMString = || RUMString::default();
160pub const DEFAULT_TEXTMAP: fn() -> TextMap = || TextMap::default();
161pub const DEFAULT_NESTEDTEXTMAP: fn() -> NestedTextMap = || NestedTextMap::default();
162pub const DEFAULT_NESTEDNESTEDTEXTMAP: fn() -> NestedNestedTextMap =
163    || NestedNestedTextMap::default();