Handle errors from the API more gracefully

This commit is contained in:
Joey Hines 2025-05-23 17:32:13 -06:00
parent 6f4c408a26
commit 353eb865db
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
7 changed files with 50 additions and 10 deletions

2
Cargo.lock generated
View File

@ -101,7 +101,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cta-api"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"reqwest",
"serde",

View File

@ -1,6 +1,6 @@
[package]
name = "cta-api"
version = "0.2.0"
version = "0.3.0"
edition = "2024"
description = "CTA API Client"
authors = ["Joey Hines joey@ahines.net"]

View File

@ -1,7 +1,7 @@
use crate::models::Response;
use crate::models::eta::{EtaRequest, EtaResp};
use crate::models::follow::{FollowRequest, FollowResp};
use crate::models::route::{RouteRequest, RouteResp};
use crate::models::{CTAResponse, Response};
use reqwest::{Client, Url};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@ -18,6 +18,8 @@ pub enum Error {
ReqwestFailure(#[from] reqwest::Error),
#[error("URL parse failure: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("Got error number '{0}': {1}")]
CtaError(String, String),
}
pub struct CTAClient {
@ -35,7 +37,7 @@ impl CTAClient {
}
}
pub async fn make_request<T: Serialize, V: for<'a> Deserialize<'a>>(
pub async fn make_request<T: Serialize, V: for<'a> Deserialize<'a> + CTAResponse>(
&mut self,
endpoint: &str,
req: T,
@ -53,6 +55,8 @@ impl CTAClient {
.json()
.await?;
resp.ctatt.check_error()?;
Ok(resp.ctatt)
}

View File

@ -1,4 +1,4 @@
use crate::models::Ctatt;
use crate::models::{CTAResponse, Ctatt};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug)]
@ -13,9 +13,16 @@ pub struct EtaRequest {
pub struct EtaResp {
#[serde(flatten)]
pub header: Ctatt,
#[serde(default)]
pub eta: Vec<Eta>,
}
impl CTAResponse for EtaResp {
fn get_header(&self) -> Ctatt {
self.header.clone()
}
}
#[derive(Deserialize, Debug)]
pub struct Eta {
#[serde(rename = "staId")]

View File

@ -1,4 +1,4 @@
use crate::models::Ctatt;
use crate::models::{CTAResponse, Ctatt};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug)]
@ -7,7 +7,7 @@ pub struct FollowRequest {
pub run_number: u32,
}
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Default)]
pub struct Position {
pub lat: String,
pub lon: String,
@ -48,6 +48,14 @@ pub struct ScheduledStop {
pub struct FollowResp {
#[serde(flatten)]
pub header: Ctatt,
#[serde(default)]
pub position: Position,
#[serde(default)]
pub eta: Vec<ScheduledStop>,
}
impl CTAResponse for FollowResp {
fn get_header(&self) -> Ctatt {
self.header.clone()
}
}

View File

@ -3,9 +3,10 @@ pub mod follow;
pub mod route;
pub mod train;
use crate::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct Ctatt {
pub tmst: String,
#[serde(rename = "errCd")]
@ -14,7 +15,20 @@ pub struct Ctatt {
pub err_nm: Option<String>,
}
pub trait CTAResponse {
fn get_header(&self) -> Ctatt;
fn check_error(&self) -> Result<(), Error> {
let header = self.get_header();
if let Some(err_number) = header.err_nm {
Err(Error::CtaError(err_number, header.err_cd))
} else {
Ok(())
}
}
}
#[derive(Debug, Deserialize)]
pub struct Response<T> {
pub struct Response<T: CTAResponse> {
pub ctatt: T,
}

View File

@ -1,5 +1,5 @@
use crate::models::Ctatt;
use crate::models::train::Train;
use crate::models::{CTAResponse, Ctatt};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug)]
@ -18,5 +18,12 @@ pub struct Route {
pub struct RouteResp {
#[serde(flatten)]
pub header: Ctatt,
#[serde(default)]
pub route: Vec<Route>,
}
impl CTAResponse for RouteResp {
fn get_header(&self) -> Ctatt {
self.header.clone()
}
}