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, 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().await;
59                // Test that connection is available
60                if EventDB::connection_is_ok().await {
61                    debug!("Event DB is connected. Liveness set to true");
62                } else {
63                    error!("Event DB connection failed");
64                }
65
66                // Start the chain indexing follower.
67                start_followers().await?;
68
69                // Start the API service.
70                let handle = tokio::spawn(async move {
71                    match service::run().await {
72                        Ok(()) => info!("Endpoints started ok"),
73                        Err(err) => {
74                            error!("Error starting endpoints {err}");
75                        },
76                    }
77                });
78                tasks.push(handle);
79
80                // Start task to reset the service 'live counter' at a regular interval.
81                let handle = tokio::spawn(async move {
82                    while is_live() {
83                        tokio::time::sleep(Settings::service_live_timeout_interval()).await;
84                        live_counter_reset();
85                    }
86                });
87                tasks.push(handle);
88
89                // Start task to wait for the service 'started' flag to be `true`.
90                let handle = tokio::spawn(async move {
91                    while !service_has_started() {
92                        tokio::time::sleep(Duration::from_secs(1)).await;
93                        if condition_for_started() {
94                            set_to_started();
95                        }
96                    }
97                });
98                tasks.push(handle);
99
100                // Run all asynchronous tasks to completion.
101                for task in tasks {
102                    task.await?;
103                }
104
105                info!("Catalyst Gateway - Shut Down");
106            },
107            Self::Docs { output } => {
108                let docs = service::get_app_docs();
109                match output {
110                    Some(path) => {
111                        let mut docs_file = std::fs::File::create(path)?;
112                        docs_file.write_all(docs.as_bytes())?;
113                    },
114                    None => println!("{docs}"),
115                }
116            },
117        }
118
119        Ok(())
120    }
121}