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}