rumtk_web/utils/
app.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 rumtk_core::strings::RUMString;
22
23use crate::css::DEFAULT_OUT_CSS_DIR;
24use crate::utils::defaults::DEFAULT_LOCAL_LISTENING_ADDRESS;
25use crate::utils::matcher::*;
26use crate::{rumtk_web_fetch, rumtk_web_load_conf};
27
28use axum::routing::get;
29use axum::Router;
30use clap::Parser;
31use tower_http::compression::{CompressionLayer, DefaultPredicate};
32use tower_http::services::ServeDir;
33use tracing::error;
34
35///
36/// RUMTK WebApp CLI Args
37///
38#[derive(Parser, Debug)]
39#[command(author, version, about, long_about = None)]
40pub struct Args {
41    ///
42    /// Website title to use internally. It can be omitted if defined in the app.json config file
43    /// bundled with your app.
44    ///
45    #[arg(short, long, default_value = "")]
46    title: RUMString,
47    ///
48    /// Website description string. It can be omitted if defined in the app.json config file
49    /// bundled with your app.
50    ///
51    #[arg(short, long, default_value = "")]
52    description: RUMString,
53    ///
54    /// Copyright year to display in website.
55    ///
56    #[arg(short, long, default_value = "")]
57    copyright: RUMString,
58    ///
59    /// Directory to scan on startup to find custom CSS sources to bundle into a minified CSS file
60    /// that can be quickly pulled by the app client side.
61    ///
62    /// This option can provide an alternative to direct component retrieval of CSS fragments.
63    /// Meaning, you could bundle all of your fragments into the master bundle at startup and
64    /// turn off component level ```custom_css_enabled``` option in the ```app.json``` config.
65    ///
66    #[arg(short, long, default_value = DEFAULT_OUT_CSS_DIR)]
67    css_source_dir: RUMString,
68    ///
69    /// Is the interface meant to be bound to the loopback address and remain hidden from the
70    /// outside world.
71    ///
72    /// It follows the format ```IPv4:port``` and it is a string.
73    ///
74    /// If a NIC IP is defined via `--ip`, that value will override this flag.
75    ///
76    #[arg(short, long, default_value = DEFAULT_LOCAL_LISTENING_ADDRESS)]
77    ip: RUMString,
78}
79
80pub async fn run_app(args: &Args) {
81    let state = rumtk_web_load_conf!(&args);
82    let comression_layer: CompressionLayer = CompressionLayer::new()
83        .br(true)
84        .deflate(true)
85        .gzip(true)
86        .zstd(true)
87        .compress_when(DefaultPredicate::new());
88    let app = Router::new()
89        /* Robots.txt */
90        .route("/robots.txt", get(rumtk_web_fetch!(default_robots_matcher)))
91        /* Components */
92        .route(
93            "/component/{*name}",
94            get(rumtk_web_fetch!(default_component_matcher)),
95        )
96        /* Pages */
97        .route("/", get(rumtk_web_fetch!(default_page_matcher)))
98        .route("/{*page}", get(rumtk_web_fetch!(default_page_matcher)))
99        /* Services */
100        .nest_service("/static", ServeDir::new("static"))
101        .with_state(state)
102        .layer(comression_layer);
103
104    let listener = tokio::net::TcpListener::bind(&args.ip.as_str())
105        .await
106        .expect("There was an issue biding the listener.");
107    println!("listening on {}", listener.local_addr().unwrap());
108
109    axum::serve(listener, app)
110        .await
111        .expect("There was an issue with the server.");
112}
113
114#[macro_export]
115macro_rules! rumtk_web_run_app {
116    (  ) => {{
117        use $crate::{
118            rumtk_web_compile_css_bundle, rumtk_web_init_components, rumtk_web_init_pages,
119        };
120        let args = Args::parse();
121
122        rumtk_web_init_components!(vec![]);
123        rumtk_web_init_pages!(vec![]);
124        rumtk_web_compile_css_bundle!(&args.css_source_dir);
125
126        run_app(args).await;
127    }};
128    ( $pages:expr ) => {{
129        use $crate::{
130            rumtk_web_compile_css_bundle, rumtk_web_init_components, rumtk_web_init_pages,
131        };
132        let args = Args::parse();
133
134        rumtk_web_init_components!(vec![]);
135        rumtk_web_init_pages!($pages);
136        rumtk_web_compile_css_bundle!(&args.css_source_dir);
137
138        run_app(args).await;
139    }};
140    ( $pages:expr, $components:expr ) => {{
141        use $crate::{
142            rumtk_web_compile_css_bundle, rumtk_web_init_components, rumtk_web_init_pages,
143        };
144        let args = Args::parse();
145
146        rumtk_web_init_components!($components);
147        rumtk_web_init_pages!($pages);
148        rumtk_web_compile_css_bundle!(&args.css_source_dir);
149
150        run_app(args).await;
151    }};
152}