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}