cat_gateway/
cli.rs

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