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