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                // Returns optional guard for telemetry providers.
49                // We must hold this guard while the main process runs.
50                // No need to do anything else.
51                let _guard = Settings::init(settings)?;
52
53                let mut tasks = Vec::new();
54
55                info!("Catalyst Gateway - Starting");
56
57                // Start the DB's.
58                CassandraSession::init();
59
60                // Initialize Event DB connection pool
61                db::event::establish_connection_pool();
62
63                // Start the chain indexing follower.
64                start_followers().await?;
65
66                // Start the API service.
67                let handle = tokio::spawn(async move {
68                    match service::run().await {
69                        Ok(()) => info!("Endpoints started ok"),
70                        Err(err) => {
71                            error!("Error starting endpoints {err}");
72                        },
73                    }
74                });
75                tasks.push(handle);
76
77                // Start task to reset the service 'live counter' at a regular interval.
78                let handle = tokio::spawn(async move {
79                    while is_live() {
80                        tokio::time::sleep(Settings::service_live_timeout_interval()).await;
81                        live_counter_reset();
82                    }
83                });
84                tasks.push(handle);
85
86                // Start task to wait for the service 'started' flag to be `true`.
87                let handle = tokio::spawn(async move {
88                    while !service_has_started() {
89                        tokio::time::sleep(Duration::from_secs(1)).await;
90                        if condition_for_started() {
91                            set_to_started();
92                        }
93                    }
94                });
95                tasks.push(handle);
96
97                // Run all asynchronous tasks to completion.
98                for task in tasks {
99                    task.await?;
100                }
101
102                info!("Catalyst Gateway - Shut Down");
103            },
104            Self::Docs { output } => {
105                let docs = service::get_app_docs();
106                match output {
107                    Some(path) => {
108                        let mut docs_file = std::fs::File::create(path)?;
109                        docs_file.write_all(docs.as_bytes())?;
110                    },
111                    None => println!("{docs}"),
112                }
113            },
114        }
115
116        Ok(())
117    }
118}