Skip to content

Commit dcb7434

Browse files
committed
Merge branch 'main' into add-helm
2 parents 3789c02 + dd819af commit dcb7434

File tree

8 files changed

+181
-131
lines changed

8 files changed

+181
-131
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ async-graphql-axum = "7.0.17"
1414
url = "2.5.7"
1515
config = "0.15.16"
1616
clap = { version = "4.5.48", features = ["derive"] }
17+
18+
[dev-dependencies]
19+
httpmock = "0.8.2"

src/clients.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,60 @@
11
use std::fmt;
22

3-
use crate::model::metadata::Metadata;
3+
use reqwest::Url;
4+
use serde::de::DeserializeOwned;
45

5-
#[cfg(test)]
6-
pub(crate) mod mock_tiled_client;
7-
pub(crate) mod tiled_client;
6+
use crate::model::metadata::Metadata;
87

98
pub trait Client {
109
fn metadata(&self) -> impl Future<Output = Result<Metadata, ClientError>> + Send;
1110
}
1211

1312
pub type ClientResult<T> = Result<T, ClientError>;
1413

14+
pub struct TiledClient {
15+
pub address: Url,
16+
}
17+
18+
impl TiledClient {
19+
async fn request<T: DeserializeOwned>(&self, endpoint: &str) -> ClientResult<T> {
20+
println!("Requesting data from tiled");
21+
let url = self.address.join(endpoint)?;
22+
let response = reqwest::get(url).await?.error_for_status()?;
23+
let body = response.text().await?;
24+
serde_json::from_str(&body).map_err(|e| ClientError::InvalidResponse(e, body))
25+
}
26+
}
27+
impl Client for TiledClient {
28+
async fn metadata(&self) -> ClientResult<Metadata> {
29+
self.request::<Metadata>("/api/v1/").await
30+
}
31+
}
32+
1533
#[derive(Debug)]
1634
pub enum ClientError {
17-
Parse(url::ParseError),
18-
Reqwest(reqwest::Error),
19-
Serde(serde_json::Error),
20-
Io(std::io::Error),
35+
InvalidPath(url::ParseError),
36+
ServerError(reqwest::Error),
37+
InvalidResponse(serde_json::Error, String),
2138
}
2239
impl From<url::ParseError> for ClientError {
2340
fn from(err: url::ParseError) -> ClientError {
24-
ClientError::Parse(err)
41+
ClientError::InvalidPath(err)
2542
}
2643
}
2744
impl From<reqwest::Error> for ClientError {
2845
fn from(err: reqwest::Error) -> ClientError {
29-
ClientError::Reqwest(err)
30-
}
31-
}
32-
impl From<serde_json::Error> for ClientError {
33-
fn from(err: serde_json::Error) -> ClientError {
34-
ClientError::Serde(err)
35-
}
36-
}
37-
impl From<std::io::Error> for ClientError {
38-
fn from(err: std::io::Error) -> ClientError {
39-
ClientError::Io(err)
46+
ClientError::ServerError(err)
4047
}
4148
}
49+
4250
impl std::fmt::Display for ClientError {
4351
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44-
match *self {
45-
ClientError::Parse(ref err) => write!(f, "Parse error: {}", err),
46-
ClientError::Reqwest(ref err) => write!(f, "Request error: {}", err),
47-
ClientError::Serde(ref err) => write!(f, "Serde error: {}", err),
48-
ClientError::Io(ref err) => write!(f, "IO Error: {}", err),
52+
match self {
53+
ClientError::InvalidPath(err) => write!(f, "Invalid URL path: {}", err),
54+
ClientError::ServerError(err) => write!(f, "Tiled server error: {}", err),
55+
ClientError::InvalidResponse(err, actual) => {
56+
write!(f, "Invalid response: {err}, response: {actual}")
57+
}
4958
}
5059
}
5160
}

src/clients/mock_tiled_client.rs

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/clients/tiled_client.rs

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/handlers.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,21 @@
1-
pub mod graphql;
1+
use async_graphql::http::GraphiQLSource;
2+
use async_graphql::*;
3+
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
4+
use axum::Extension;
5+
use axum::response::{Html, IntoResponse};
6+
7+
use crate::clients::Client;
8+
use crate::model::TiledQuery;
9+
10+
pub async fn graphql_handler<T: Client + Send + Sync + 'static>(
11+
schema: Extension<Schema<TiledQuery, EmptyMutation, EmptySubscription>>,
12+
req: GraphQLRequest,
13+
) -> GraphQLResponse {
14+
let query = req.into_inner().query;
15+
16+
schema.execute(query).await.into()
17+
}
18+
19+
pub async fn graphiql_handler() -> impl IntoResponse {
20+
Html(GraphiQLSource::build().endpoint("/graphql").finish())
21+
}

src/handlers/graphql.rs

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ mod model;
1212

1313
use cli::{Cli, Commands};
1414

15-
use crate::clients::tiled_client::TiledClient;
15+
use crate::clients::TiledClient;
1616
use crate::config::GlazedConfig;
17-
use crate::handlers::graphql::{graphiql_handler, graphql_handler};
17+
use crate::handlers::{graphiql_handler, graphql_handler};
1818
use crate::model::TiledQuery;
1919

2020
#[tokio::main]

src/model.rs

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,131 @@ pub(crate) mod metadata;
22

33
use async_graphql::Object;
44

5-
use crate::clients::{Client, ClientError};
5+
use crate::clients::{Client, ClientError, TiledClient};
66

7-
pub(crate) struct TiledQuery<T>(pub T);
7+
pub(crate) struct TiledQuery(pub TiledClient);
88

99
#[Object]
10-
impl<T: Client + Send + Sync + 'static> TiledQuery<T> {
10+
impl TiledQuery {
1111
async fn metadata(&self) -> async_graphql::Result<metadata::Metadata, ClientError> {
1212
self.0.metadata().await
1313
}
1414
}
15+
16+
#[cfg(test)]
17+
mod tests {
18+
use async_graphql::{EmptyMutation, EmptySubscription, Schema, Value};
19+
use httpmock::MockServer;
20+
use url::Url;
21+
22+
use crate::TiledQuery;
23+
use crate::clients::TiledClient;
24+
25+
#[tokio::test]
26+
async fn test_api_version_query() {
27+
let mock_server = MockServer::start();
28+
let mock = mock_server
29+
.mock_async(|when, then| {
30+
when.method("GET").path("/api/v1/");
31+
then.status(200)
32+
.body_from_file("resources/tiled_metadata.json");
33+
})
34+
.await;
35+
36+
let schema = Schema::build(
37+
TiledQuery(TiledClient {
38+
address: Url::parse(&mock_server.base_url()).unwrap(),
39+
}),
40+
EmptyMutation,
41+
EmptySubscription,
42+
)
43+
.finish();
44+
45+
let response = schema.execute("{metadata { apiVersion } }").await;
46+
47+
assert_eq!(response.data.to_string(), "{metadata: {apiVersion: 0}}");
48+
assert_eq!(response.errors, &[]);
49+
mock.assert();
50+
}
51+
52+
#[tokio::test]
53+
async fn test_server_unavailable() {
54+
let schema = Schema::build(
55+
TiledQuery(TiledClient {
56+
address: Url::parse("http://tiled.example.com").unwrap(),
57+
}),
58+
EmptyMutation,
59+
EmptySubscription,
60+
)
61+
.finish();
62+
63+
let response = schema.execute("{metadata { apiVersion } }").await;
64+
assert_eq!(response.data, Value::Null);
65+
assert_eq!(
66+
response.errors[0].message,
67+
"Tiled server error: error sending request for url (http://tiled.example.com/api/v1/)"
68+
);
69+
assert_eq!(response.errors.len(), 1);
70+
}
71+
72+
#[tokio::test]
73+
async fn test_internal_tiled_error() {
74+
let mock_server = MockServer::start();
75+
let mock = mock_server
76+
.mock_async(|when, then| {
77+
when.method("GET").path("/api/v1/");
78+
then.status(503);
79+
})
80+
.await;
81+
82+
let schema = Schema::build(
83+
TiledQuery(TiledClient {
84+
address: Url::parse(&mock_server.base_url()).unwrap(),
85+
}),
86+
EmptyMutation,
87+
EmptySubscription,
88+
)
89+
.finish();
90+
91+
let response = schema.execute("{metadata { apiVersion } }").await;
92+
let actual = &response.errors[0].message;
93+
let expected =
94+
"Tiled server error: HTTP status server error (503 Service Unavailable) for url";
95+
assert_eq!(response.data, Value::Null);
96+
assert!(
97+
actual.starts_with(expected),
98+
"Unexpected error: {actual} \nExpected: {expected} [...]"
99+
);
100+
assert_eq!(response.errors.len(), 1);
101+
mock.assert();
102+
}
103+
104+
#[tokio::test]
105+
async fn test_invalid_server_response() {
106+
let mock_server = MockServer::start();
107+
let mock = mock_server
108+
.mock_async(|when, then| {
109+
when.method("GET").path("/api/v1/");
110+
then.status(200).body("{}");
111+
})
112+
.await;
113+
114+
let schema = Schema::build(
115+
TiledQuery(TiledClient {
116+
address: Url::parse(&mock_server.base_url()).unwrap(),
117+
}),
118+
EmptyMutation,
119+
EmptySubscription,
120+
)
121+
.finish();
122+
123+
let response = schema.execute("{metadata { apiVersion } }").await;
124+
assert_eq!(response.data, Value::Null);
125+
assert_eq!(response.errors.len(), 1);
126+
assert_eq!(
127+
response.errors[0].message,
128+
"Invalid response: missing field `api_version` at line 1 column 2, response: {}"
129+
);
130+
mock.assert();
131+
}
132+
}

0 commit comments

Comments
 (0)