cat_gateway/
cli.rs

1//! CLI interpreter for the service
2use std::{io::Write, path::PathBuf, time::Duration};
3
4use clap::Parser;
5use tracing::{error, info};
6
7use crate::{
8    cardano::start_followers,
9    db::{self, index::session::CassandraSession},
10    service::{
11        self,
12        utilities::health::{
13            condition_for_started, is_live, live_counter_reset, service_has_started, set_to_started,
14        },
15    },
16    settings::{ServiceSettings, Settings},
17};
18
19#[derive(Parser)]
20#[clap(rename_all = "kebab-case")]
21/// Simple service CLI options
22pub(crate) enum Cli {
23    /// Run the service
24    Run(ServiceSettings),
25    /// Build API docs of the service in the JSON format
26    Docs {
27        /// The output path to the generated docs file, if omitted prints to stdout.
28        output: Option<PathBuf>,
29    },
30}
31
32impl Cli {
33    /// Execute the specified operation.
34    ///
35    /// This method is asynchronous and returns a `Result` indicating whether the
36    /// operation was successful or if an error occurred.
37    ///
38    /// # Errors
39    ///
40    /// This method can return an error if:
41    ///
42    /// - Failed to initialize the logger with the specified log level.
43    /// - Failed to create a new `State` with the provided database URL.
44    /// - Failed to run the service on the specified address.
45    pub(crate) async fn exec(self) -> anyhow::Result<()> {
46        match self {
47            Self::Run(settings) => {
48                Settings::init(settings)?;
49
50                let mut tasks = Vec::new();
51
52                info!("Catalyst Gateway - Starting");
53
54                // Start the DB's.
55                CassandraSession::init();
56
57                // Initialize Event DB connection pool
58                db::event::establish_connection_pool();
59
60                // Start the chain indexing follower.
61                start_followers().await?;
62
63                // Start the API service.
64                let handle = tokio::spawn(async move {
65                    match service::run().await {
66                        Ok(()) => info!("Endpoints started ok"),
67                        Err(err) => {
68                            error!("Error starting endpoints {err}");
69                        },
70                    }
71                });
72                tasks.push(handle);
73
74                // Start task to reset the service 'live counter' at a regular interval.
75                let handle = tokio::spawn(async move {
76                    while is_live() {
77                        tokio::time::sleep(Settings::service_live_timeout_interval()).await;
78                        live_counter_reset();
79                    }
80                });
81                tasks.push(handle);
82
83                // Start task to wait for the service 'started' flag to be `true`.
84                let handle = tokio::spawn(async move {
85                    while !service_has_started() {
86                        tokio::time::sleep(Duration::from_secs(1)).await;
87                        if condition_for_started() {
88                            set_to_started();
89                        }
90                    }
91                });
92                tasks.push(handle);
93
94                // Run all asynchronous tasks to completion.
95                for task in tasks {
96                    task.await?;
97                }
98
99                info!("Catalyst Gateway - Shut Down");
100            },
101            Self::Docs { output } => {
102                let docs = service::get_app_docs();
103                match output {
104                    Some(path) => {
105                        let mut docs_file = std::fs::File::create(path)?;
106                        docs_file.write_all(docs.as_bytes())?;
107                    },
108                    None => println!("{docs}"),
109                }
110            },
111        }
112
113        Ok(())
114    }
115}