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::jobs::JobBuffer;
24use crate::utils::defaults::DEFAULT_TEXT_ITEM;
25use crate::utils::types::RUMString;
26use axum::extract::State;
27use phf::OrderedMap;
28pub use phf_macros::phf_ordered_map as rumtk_create_const_ordered_map;
29use rumtk_core::types::{RUMDeserialize, RUMDeserializer, RUMSerialize, RUMSerializer, RUMID};
30use rumtk_core::types::{RUMHashMap, RUMOrderedMap};
31use std::sync::{Arc, RwLock};
32
33pub type TextMap = RUMOrderedMap<RUMString, RUMString>;
34pub type NestedTextMap = RUMOrderedMap<RUMString, TextMap>;
35pub type NestedNestedTextMap = RUMOrderedMap<RUMString, NestedTextMap>;
36pub type RootNestedNestedTextMap = RUMOrderedMap<RUMString, NestedNestedTextMap>;
37
38pub type ConstTextMap = OrderedMap<&'static str, &'static str>;
39pub type ConstNestedTextMap = OrderedMap<&'static str, &'static ConstTextMap>;
40pub type ConstNestedNestedTextMap = OrderedMap<&'static str, &'static ConstNestedTextMap>;
41
42#[derive(RUMSerialize, RUMDeserialize, PartialEq, Debug, Clone, Default)]
43pub struct HeaderConf {
44    pub logo_size: RUMString,
45    pub disable_navlinks: bool,
46    pub disable_logo: bool,
47}
48
49#[derive(RUMSerialize, RUMDeserialize, PartialEq, Debug, Clone, Default)]
50pub struct FooterConf {
51    pub socials_list: RUMString,
52    pub disable_contact_button: bool,
53}
54
55///
56/// This is a core structure in a web project using the RUMTK framework. This structure contains
57/// a series of fields that represent the web app initial state or configuration. The idea is that
58/// the web app can come bundled with a JSON config file following this structure which we can load
59/// at runtime. The settings will dictate a few key project behaviors such as properly labeling
60/// some components with the company name or use the correct language text.
61///
62#[derive(RUMSerialize, RUMDeserialize, PartialEq, Debug, Clone, Default)]
63pub struct AppConf {
64    pub title: RUMString,
65    pub description: RUMString,
66    pub company: RUMString,
67    pub copyright: RUMString,
68    pub lang: RUMString,
69    pub theme: RUMString,
70    pub custom_css: bool,
71    pub header_conf: HeaderConf,
72    pub footer_conf: FooterConf,
73
74    strings: RootNestedNestedTextMap,
75    config: NestedNestedTextMap,
76    //pub opts: TextMap,
77}
78
79impl AppConf {
80    pub fn update_site_info(
81        &mut self,
82        title: RUMString,
83        description: RUMString,
84        company: RUMString,
85        copyright: RUMString,
86    ) {
87        if !title.is_empty() {
88            self.title = title;
89        }
90        if !company.is_empty() {
91            self.company = company;
92        }
93        if !description.is_empty() {
94            self.description = description;
95        }
96        if !copyright.is_empty() {
97            self.copyright = copyright;
98        }
99    }
100
101    pub fn get_text(&self, item: &str) -> NestedTextMap {
102        match self.strings.get(&self.lang) {
103            Some(l) => match l.get(item) {
104                Some(i) => i.clone(),
105                None => NestedTextMap::default(),
106            },
107            None => NestedTextMap::default(),
108        }
109    }
110
111    pub fn get_conf(&self, section: &str) -> TextMap {
112        match self.config.get(section) {
113            Some(l) => match l.get(&self.lang) {
114                Some(i) => i.clone(),
115                None => match l.get(DEFAULT_TEXT_ITEM) {
116                    Some(i) => i.clone(),
117                    None => TextMap::default(),
118                },
119            },
120            None => TextMap::default(),
121        }
122    }
123}
124
125///
126/// Main internal structure for holding the initial app configuration ([AppConf](crate::utils::AppConf)),
127/// the `clipboard` containing dynamically generated state ([TextMap](crate::utils::TextMap)),
128/// and the `jobs` field containing
129///
130pub struct AppState {
131    pub config: AppConf,
132    pub clipboard: TextMap,
133    pub jobs: RUMHashMap<RUMID, Option<JobBuffer>>,
134}
135
136pub type SharedAppState = Arc<RwLock<AppState>>;
137pub type RouterAppState = State<Arc<RwLock<AppState>>>;
138
139#[macro_export]
140macro_rules! rumtk_web_load_conf {
141    ( $args:expr ) => {{
142        rumtk_web_load_conf!($args, "./app.json")
143    }};
144    ( $args:expr, $path:expr ) => {{
145        use rumtk_core::rumtk_deserialize;
146        use rumtk_core::strings::RUMStringConversions;
147        use rumtk_core::types::RUMHashMap;
148        use std::fs;
149        use std::sync::{Arc, RwLock};
150
151        use $crate::rumtk_web_save_conf;
152        use $crate::utils::{AppConf, AppState, TextMap};
153
154        let json = match fs::read_to_string($path) {
155            Ok(json) => json,
156            Err(err) => rumtk_web_save_conf!($path),
157        };
158
159        let mut conf: AppConf = match rumtk_deserialize!(json) {
160            Ok(conf) => conf,
161            Err(err) => panic!(
162                "The App config file in {} does not meet the expected structure. \
163                    See the documentation for more information. Error: {}\n{}",
164                $path, err, json
165            ),
166        };
167        conf.update_site_info(
168            $args.title.clone(),
169            $args.description.clone(),
170            $args.company.clone(),
171            $args.copyright.clone(),
172        );
173        Arc::new(RwLock::new(AppState {
174            config: conf,
175            clipboard: TextMap::default(),
176            jobs: RUMHashMap::default(),
177        }))
178    }};
179}
180
181#[macro_export]
182macro_rules! rumtk_web_save_conf {
183    ( $path:expr ) => {{
184        use rumtk_core::rumtk_serialize;
185        use rumtk_core::strings::RUMStringConversions;
186        use std::fs;
187        use $crate::utils::AppConf;
188
189        let json = rumtk_serialize!(AppConf::default(), true)?;
190        fs::write($path, &json);
191        json
192    }};
193}
194
195#[macro_export]
196macro_rules! rumtk_web_get_string {
197    ( $conf:expr, $item:expr ) => {{
198        let owned_state = $conf.read().expect("Lock failure");
199        owned_state.config.get_text($item)
200    }};
201}
202
203#[macro_export]
204macro_rules! rumtk_web_get_conf {
205    ( $conf:expr, $item:expr ) => {{
206        let owned_state = $conf.read().expect("Lock failure");
207        owned_state.config.get_conf($item)
208    }};
209}
210
211/*
212   Default non static data to minimize allocations.
213*/
214pub const DEFAULT_TEXT: fn() -> RUMString = || RUMString::default();
215pub const DEFAULT_TEXTMAP: fn() -> TextMap = || TextMap::default();
216pub const DEFAULT_NESTEDTEXTMAP: fn() -> NestedTextMap = || NestedTextMap::default();
217pub const DEFAULT_NESTEDNESTEDTEXTMAP: fn() -> NestedNestedTextMap =
218    || NestedNestedTextMap::default();