diff --git a/Cargo.lock b/Cargo.lock index 9e9bce5..2e50433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,7 @@ dependencies = [ name = "emfcamp-schedule-cli" version = "0.0.1" dependencies = [ + "anyhow", "ascii_table", "chrono", "clap", diff --git a/adapter/src/metrics.rs b/adapter/src/metrics.rs index 69a9203..a0967b0 100644 --- a/adapter/src/metrics.rs +++ b/adapter/src/metrics.rs @@ -7,6 +7,8 @@ use metrics_exporter_prometheus::{BuildError, PrometheusBuilder}; pub(crate) static REQUESTS: &str = "emf_schedule_adapter_requests"; pub(crate) static ENDPOINT_LABEL: &str = "endpoint"; +pub(crate) static UPSTREAM_API_FAILURES: &str = "emf_schedule_adapter_upstream_api_failures"; + pub(super) fn init(address: SocketAddr) -> Result<(), BuildError> { info!("Starting observability server on {address}"); @@ -16,5 +18,10 @@ pub(super) fn init(address: SocketAddr) -> Result<(), BuildError> { describe_counter!(REQUESTS, "Number of requests made to each API endpoint"); + describe_counter!( + UPSTREAM_API_FAILURES, + "Number of requests to the upstream schedule API that have failed" + ); + result } diff --git a/adapter/src/queries/now_and_next.rs b/adapter/src/queries/now_and_next.rs index 7fad001..461f97c 100644 --- a/adapter/src/queries/now_and_next.rs +++ b/adapter/src/queries/now_and_next.rs @@ -1,5 +1,6 @@ use axum::{ extract::State, + http::StatusCode, response::{IntoResponse, Response}, Json, }; @@ -8,7 +9,7 @@ use chrono::{DateTime, FixedOffset, Local}; use emfcamp_schedule_api::schedule::mutation; use metrics::counter; use serde::{Deserialize, Serialize}; -use tracing::info; +use tracing::{error, info}; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct NowAndNextQueryParams { @@ -50,14 +51,20 @@ pub(crate) async fn now_and_next( counter!(crate::metrics::REQUESTS, crate::metrics::ENDPOINT_LABEL => "now_and_next") .increment(1); - let mut schedule = state.client.get_schedule().await; + match state.client.get_schedule().await { + Ok(mut schedule) => { + let now = query.now.unwrap_or_else(|| Local::now().into()); - let now = query.now.unwrap_or_else(|| Local::now().into()); + let mutators = query.into(); + schedule.mutate(&mutators); - let mutators = query.into(); - schedule.mutate(&mutators); - - let epg = schedule.now_and_next(now); - - Json(epg).into_response() + let epg = schedule.now_and_next(now); + Json(epg).into_response() + } + Err(err) => { + error!("{err}"); + counter!(crate::metrics::UPSTREAM_API_FAILURES).increment(1); + (StatusCode::INTERNAL_SERVER_ERROR).into_response() + } + } } diff --git a/adapter/src/queries/schedule.rs b/adapter/src/queries/schedule.rs index d5fb823..cfbdbe5 100644 --- a/adapter/src/queries/schedule.rs +++ b/adapter/src/queries/schedule.rs @@ -1,5 +1,6 @@ use axum::{ extract::State, + http::StatusCode, response::{IntoResponse, Response}, Json, }; @@ -8,7 +9,7 @@ use chrono::{DateTime, FixedOffset}; use emfcamp_schedule_api::schedule::mutation; use metrics::counter; use serde::{Deserialize, Serialize}; -use tracing::info; +use tracing::{error, info}; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct ScheduleQueryParams { @@ -59,12 +60,18 @@ pub(crate) async fn schedule( info!("Query: schedule: {:?}", query); counter!(crate::metrics::REQUESTS, crate::metrics::ENDPOINT_LABEL => "schedule").increment(1); - let mut schedule = state.client.get_schedule().await; + match state.client.get_schedule().await { + Ok(mut schedule) => { + let mutators = query.into(); + schedule.mutate(&mutators); - let mutators = query.into(); - schedule.mutate(&mutators); - - let events = &mut schedule.events; - - Json(events).into_response() + let events = &mut schedule.events; + Json(events).into_response() + } + Err(err) => { + error!("{err}"); + counter!(crate::metrics::UPSTREAM_API_FAILURES).increment(1); + (StatusCode::INTERNAL_SERVER_ERROR).into_response() + } + } } diff --git a/adapter/src/queries/venues.rs b/adapter/src/queries/venues.rs index 17b8aac..a19b0d2 100644 --- a/adapter/src/queries/venues.rs +++ b/adapter/src/queries/venues.rs @@ -1,19 +1,26 @@ use axum::{ extract::State, + http::StatusCode, response::{IntoResponse, Response}, Json, }; use metrics::counter; -use tracing::info; +use tracing::{error, info}; #[axum::debug_handler] pub(crate) async fn venues(State(state): State) -> Response { info!("Query: venues"); counter!(crate::metrics::REQUESTS, crate::metrics::ENDPOINT_LABEL => "venues").increment(1); - let schedule = state.client.get_schedule().await; - - let venues = schedule.venues(); - - Json(venues).into_response() + match state.client.get_schedule().await { + Ok(schedule) => { + let venues = schedule.venues(); + Json(venues).into_response() + } + Err(err) => { + error!("{err}"); + counter!(crate::metrics::UPSTREAM_API_FAILURES).increment(1); + (StatusCode::INTERNAL_SERVER_ERROR).into_response() + } + } } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bff15fa..4b48c23 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,6 +5,7 @@ version.workspace = true edition.workspace = true [dependencies] +anyhow.workspace = true ascii_table.workspace = true chrono.workspace = true clap.workspace = true diff --git a/cli/src/main.rs b/cli/src/main.rs index 685d10e..06a79a4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,7 @@ mod commands; mod formatting; +use anyhow::Result; use clap::{CommandFactory, Parser, Subcommand}; use clap_complete::Shell; use url::Url; @@ -46,12 +47,12 @@ enum Command { } #[tokio::main] -async fn main() { +async fn main() -> Result<()> { let args = Cli::parse(); let client = emfcamp_schedule_api::Client::new(args.api_url); - let schedule = client.get_schedule().await; + let schedule = client.get_schedule().await?; match args.command { Command::Full(args) => commands::full::run(args, schedule), @@ -61,6 +62,8 @@ async fn main() { Command::Venues => commands::venues::run(schedule), Command::ShellCompletions { shell } => print_shell_completions(shell), } + + Ok(()) } fn print_shell_completions(shell: Shell) { diff --git a/client/src/client.rs b/client/src/client.rs index 6cc3804..542a733 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1,4 +1,5 @@ use crate::schedule::{event::Event, Schedule}; +use reqwest::Error; use url::Url; #[derive(Debug, Clone)] @@ -11,14 +12,12 @@ impl Client { Self { url } } - pub async fn get_schedule(&self) -> Schedule { + pub async fn get_schedule(&self) -> Result { let events = reqwest::get(self.url.clone()) - .await - .unwrap() + .await? .json::>() - .await - .unwrap(); + .await?; - Schedule { events } + Ok(Schedule { events }) } }