diff --git a/src/clients.rs b/src/clients.rs index e7ca8fa..1754ac0 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -18,13 +18,10 @@ pub struct TiledClient { impl TiledClient { async fn request(&self, endpoint: &str) -> ClientResult { println!("Requesting data from tiled"); - let url = self.address.join(endpoint)?; - - let response = reqwest::get(url).await?; - let json = response.json().await?; - - Ok(serde_json::from_value(json)?) + let response = reqwest::get(url).await?.error_for_status()?; + let body = response.text().await?; + serde_json::from_str(&body).map_err(|e| ClientError::InvalidResponse(e, body)) } } impl Client for TiledClient { @@ -35,38 +32,29 @@ impl Client for TiledClient { #[derive(Debug)] pub enum ClientError { - Parse(url::ParseError), - Reqwest(reqwest::Error), - Serde(serde_json::Error), - Io(std::io::Error), + InvalidPath(url::ParseError), + ServerError(reqwest::Error), + InvalidResponse(serde_json::Error, String), } impl From for ClientError { fn from(err: url::ParseError) -> ClientError { - ClientError::Parse(err) + ClientError::InvalidPath(err) } } impl From for ClientError { fn from(err: reqwest::Error) -> ClientError { - ClientError::Reqwest(err) - } -} -impl From for ClientError { - fn from(err: serde_json::Error) -> ClientError { - ClientError::Serde(err) - } -} -impl From for ClientError { - fn from(err: std::io::Error) -> ClientError { - ClientError::Io(err) + ClientError::ServerError(err) } } + impl std::fmt::Display for ClientError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ClientError::Parse(ref err) => write!(f, "Parse error: {}", err), - ClientError::Reqwest(ref err) => write!(f, "Request error: {}", err), - ClientError::Serde(ref err) => write!(f, "Serde error: {}", err), - ClientError::Io(ref err) => write!(f, "IO Error: {}", err), + match self { + ClientError::InvalidPath(err) => write!(f, "Invalid URL path: {}", err), + ClientError::ServerError(err) => write!(f, "Tiled server error: {}", err), + ClientError::InvalidResponse(err, actual) => { + write!(f, "Invalid response: {err}, response: {actual}") + } } } } diff --git a/src/handlers.rs b/src/handlers.rs index 55d437d..b0270fe 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -19,40 +19,3 @@ pub async fn graphql_handler( pub async fn graphiql_handler() -> impl IntoResponse { Html(GraphiQLSource::build().endpoint("/graphql").finish()) } - -#[cfg(test)] -mod tests { - use async_graphql::{EmptyMutation, EmptySubscription, Schema}; - use httpmock::MockServer; - use url::Url; - - use crate::TiledQuery; - use crate::clients::TiledClient; - - #[tokio::test] - async fn test_api_version_query() { - let mock_server = MockServer::start(); - - let mock = mock_server - .mock_async(|when, then| { - when.method("GET").path("/api/v1/"); - then.status(200) - .body_from_file("resources/tiled_metadata.json"); - }) - .await; - - let schema = Schema::build( - TiledQuery(TiledClient { - address: Url::parse(&mock_server.base_url()).unwrap(), - }), - EmptyMutation, - EmptySubscription, - ) - .finish(); - - let response = schema.execute("{metadata { apiVersion } }").await; - - assert_eq!(response.data.to_string(), "{metadata: {apiVersion: 0}}"); - mock.assert(); - } -} diff --git a/src/model.rs b/src/model.rs index 3dc83e8..b953ebc 100644 --- a/src/model.rs +++ b/src/model.rs @@ -12,3 +12,121 @@ impl TiledQuery { self.0.metadata().await } } + +#[cfg(test)] +mod tests { + use async_graphql::{EmptyMutation, EmptySubscription, Schema, Value}; + use httpmock::MockServer; + use url::Url; + + use crate::TiledQuery; + use crate::clients::TiledClient; + + #[tokio::test] + async fn test_api_version_query() { + let mock_server = MockServer::start(); + let mock = mock_server + .mock_async(|when, then| { + when.method("GET").path("/api/v1/"); + then.status(200) + .body_from_file("resources/tiled_metadata.json"); + }) + .await; + + let schema = Schema::build( + TiledQuery(TiledClient { + address: Url::parse(&mock_server.base_url()).unwrap(), + }), + EmptyMutation, + EmptySubscription, + ) + .finish(); + + let response = schema.execute("{metadata { apiVersion } }").await; + + assert_eq!(response.data.to_string(), "{metadata: {apiVersion: 0}}"); + assert_eq!(response.errors, &[]); + mock.assert(); + } + + #[tokio::test] + async fn test_server_unavailable() { + let schema = Schema::build( + TiledQuery(TiledClient { + address: Url::parse("http://tiled.example.com").unwrap(), + }), + EmptyMutation, + EmptySubscription, + ) + .finish(); + + let response = schema.execute("{metadata { apiVersion } }").await; + assert_eq!(response.data, Value::Null); + assert_eq!( + response.errors[0].message, + "Tiled server error: error sending request for url (http://tiled.example.com/api/v1/)" + ); + assert_eq!(response.errors.len(), 1); + } + + #[tokio::test] + async fn test_internal_tiled_error() { + let mock_server = MockServer::start(); + let mock = mock_server + .mock_async(|when, then| { + when.method("GET").path("/api/v1/"); + then.status(503); + }) + .await; + + let schema = Schema::build( + TiledQuery(TiledClient { + address: Url::parse(&mock_server.base_url()).unwrap(), + }), + EmptyMutation, + EmptySubscription, + ) + .finish(); + + let response = schema.execute("{metadata { apiVersion } }").await; + let actual = &response.errors[0].message; + let expected = + "Tiled server error: HTTP status server error (503 Service Unavailable) for url"; + assert_eq!(response.data, Value::Null); + assert!( + actual.starts_with(expected), + "Unexpected error: {actual} \nExpected: {expected} [...]" + ); + assert_eq!(response.errors.len(), 1); + mock.assert(); + } + + #[tokio::test] + async fn test_invalid_server_response() { + let mock_server = MockServer::start(); + let mock = mock_server + .mock_async(|when, then| { + when.method("GET").path("/api/v1/"); + then.status(200).body("{}"); + }) + .await; + + let schema = Schema::build( + TiledQuery(TiledClient { + address: Url::parse(&mock_server.base_url()).unwrap(), + }), + EmptyMutation, + EmptySubscription, + ) + .finish(); + + let response = schema.execute("{metadata { apiVersion } }").await; + assert_eq!(response.data, Value::Null); + assert_eq!(response.errors.len(), 1); + assert_eq!( + response.errors[0].message, + "Invalid response: missing field `api_version` at line 1 column 2, response: {}" + ); + mock.assert(); + } +}