Skip to content

Commit

Permalink
Some improvements (#16)
Browse files Browse the repository at this point in the history
* Fix error and Improvements struct (#1)

* Fixed failing compilation with `serde::export`

* Make the "use" part nicer

thanks @ibraheemdev <3

Co-authored-by: Ibraheem Ahmed <[email protected]>

* Change endpoint types to String, Remove lifetime

Co-authored-by: vyneer <[email protected]>
Co-authored-by: vyneer <[email protected]>
Co-authored-by: Ibraheem Ahmed <[email protected]>

* Add timeout when creating client (#2)

* Derive Clone and Debug (#3)

* Fix unwrap (#4)

* Add unwrap api

* Remove unwrap

* Helpful error message

* Use serde_json and helpful error message

* Helpful error (#5)

* Helpful error message

* Use serde_json and helpful error message

* Fix test

* Wasm support (#6)

* Clippy

* Fix wasm build

* Format

Co-authored-by: vyneer <[email protected]>
Co-authored-by: vyneer <[email protected]>
Co-authored-by: Ibraheem Ahmed <[email protected]>
  • Loading branch information
4 people authored Mar 10, 2022
1 parent f6de656 commit 220c345
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 38 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ maintenance = { status = "actively-developed" }

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json"] }

[dev-dependencies]
Expand Down
112 changes: 81 additions & 31 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use crate::error::{GraphQLError, GraphQLErrorMessage};
use std::collections::HashMap;
use std::str::FromStr;

use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Client,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;

pub struct GQLClient<'a> {
endpoint: &'a str,
use crate::error::{GraphQLError, GraphQLErrorMessage};

#[derive(Clone, Debug)]
pub struct GQLClient {
endpoint: String,
header_map: HeaderMap,
}

#[derive(Serialize)]
struct RequestBody<'a, T: Serialize> {
query: &'a str,
struct RequestBody<T: Serialize> {
query: String,
variables: T,
}

Expand All @@ -24,15 +27,32 @@ struct GraphQLResponse<T> {
errors: Option<Vec<GraphQLErrorMessage>>,
}

impl<'a> GQLClient<'a> {
pub fn new(endpoint: &'a str) -> Self {
impl GQLClient {
#[cfg(target_arch = "wasm32")]
fn client(&self) -> Result<reqwest::Client, GraphQLError> {
Ok(Client::new())
}

#[cfg(not(target_arch = "wasm32"))]
fn client(&self) -> Result<reqwest::Client, GraphQLError> {
Ok(
Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.map_err(|e| GraphQLError::with_text(format!("Can not create client: {:?}", e)))?,
)
}
}

impl GQLClient {
pub fn new(endpoint: impl AsRef<str>) -> Self {
Self {
endpoint,
endpoint: endpoint.as_ref().to_string(),
header_map: HeaderMap::new(),
}
}

pub fn new_with_headers(endpoint: &'a str, headers: HashMap<&str, &str>) -> Self {
pub fn new_with_headers(endpoint: impl AsRef<str>, headers: HashMap<&str, &str>) -> Self {
let mut header_map = HeaderMap::new();

for (str_key, str_value) in headers {
Expand All @@ -43,48 +63,78 @@ impl<'a> GQLClient<'a> {
}

Self {
endpoint,
endpoint: endpoint.as_ref().to_string(),
header_map,
}
}

pub async fn query<K>(&self, query: &'a str) -> Result<K, GraphQLError>
pub async fn query<K>(&self, query: &str) -> Result<Option<K>, GraphQLError>
where
K: for<'de> Deserialize<'de>,
{
self.query_with_vars::<K, ()>(query, ()).await
}

pub async fn query_with_vars<K, T: Serialize>(
pub async fn query_unwrap<K>(&self, query: &str) -> Result<K, GraphQLError>
where
K: for<'de> Deserialize<'de>,
{
self.query_with_vars_unwrap::<K, ()>(query, ()).await
}

pub async fn query_with_vars_unwrap<K, T: Serialize>(
&self,
query: &'a str,
query: &str,
variables: T,
) -> Result<K, GraphQLError>
where
K: for<'de> Deserialize<'de>,
{
let client = Client::new();
let body = RequestBody { query, variables };
match self.query_with_vars(query, variables).await? {
Some(v) => Ok(v),
None => Err(GraphQLError::with_text(
"No data from graphql server for this query",
)),
}
}

pub async fn query_with_vars<K, T: Serialize>(
&self,
query: &str,
variables: T,
) -> Result<Option<K>, GraphQLError>
where
K: for<'de> Deserialize<'de>,
{
let client: reqwest::Client = self.client()?;
let body = RequestBody {
query: query.to_string(),
variables,
};

let request = client
.post(self.endpoint)
.post(&self.endpoint)
.json(&body)
.headers(self.header_map.clone());

let raw_response = request.send().await?;
let json_response = raw_response.json::<GraphQLResponse<K>>().await;

// Check whether JSON is parsed successfully
match json_response {
Ok(json) => {
// Check if error messages have been received
if json.errors.is_some() {
return Err(GraphQLError::from_json(json.errors.unwrap()));
}

Ok(json.data.unwrap())
}
Err(_e) => Err(GraphQLError::from_str("Failed to parse response")),
let response_body_text = raw_response
.text()
.await
.map_err(|e| GraphQLError::with_text(format!("Can not get response: {:?}", e)))?;

let json: GraphQLResponse<K> = serde_json::from_str(&response_body_text).map_err(|e| {
GraphQLError::with_text(format!(
"Failed to parse response: {:?}. The response body is: {}",
e, response_body_text
))
})?;

// Check if error messages have been received
if json.errors.is_some() {
return Err(GraphQLError::with_json(json.errors.unwrap_or_default()));
}

Ok(json.data)
}
}
9 changes: 6 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct GraphQLError {

// https://spec.graphql.org/June2018/#sec-Errors
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
pub struct GraphQLErrorMessage {
message: String,
locations: Option<Vec<GraphQLErrorLocation>>,
Expand All @@ -18,6 +19,7 @@ pub struct GraphQLErrorMessage {
}

#[derive(Deserialize, Debug)]
#[allow(dead_code)]
pub struct GraphQLErrorLocation {
line: u32,
column: u32,
Expand All @@ -31,14 +33,14 @@ pub enum GraphQLErrorPathParam {
}

impl GraphQLError {
pub fn from_str(message: &str) -> Self {
pub fn with_text(message: impl AsRef<str>) -> Self {
Self {
message: String::from(message),
message: message.as_ref().to_string(),
json: None,
}
}

pub fn from_json(json: Vec<GraphQLErrorMessage>) -> Self {
pub fn with_json(json: Vec<GraphQLErrorMessage>) -> Self {
Self {
message: String::from("Look at json field for more details"),
json: Some(json),
Expand Down Expand Up @@ -79,6 +81,7 @@ impl fmt::Display for GraphQLError {
}

impl fmt::Debug for GraphQLError {
#[allow(clippy::needless_borrow)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
format(&self, f)
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
//!
//! let client = Client::new(endpoint);
//! let vars = Vars { id: 1 };
//! let data = client.query_with_vars::<Data, Vars>(query, vars).await.unwrap();
//! let data = client.query_with_vars_unwrap::<Data, Vars>(query, vars).await.unwrap();
//!
//! println!("Id: {}, Name: {}", data.user.id, data.user.name);
//!
Expand Down
2 changes: 1 addition & 1 deletion tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub async fn properly_parses_json_errors() {

let variables = SinglePostVariables { id: 2 };
let errors = client
.query_with_vars::<SinglePost, SinglePostVariables>(query, variables)
.query_with_vars_unwrap::<SinglePost, SinglePostVariables>(query, variables)
.await
.err();

Expand Down
4 changes: 2 additions & 2 deletions tests/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub async fn fetches_one_post() {

let variables = SinglePostVariables { id: 2 };
let data = client
.query_with_vars::<SinglePost, SinglePostVariables>(query, variables)
.query_with_vars_unwrap::<SinglePost, SinglePostVariables>(query, variables)
.await
.unwrap();

Expand All @@ -45,7 +45,7 @@ pub async fn fetches_all_posts() {
}
"#;

let data: AllPosts = client.query::<AllPosts>(query).await.unwrap();
let data: AllPosts = client.query_unwrap::<AllPosts>(query).await.unwrap();

assert!(data.posts.data.len() > 0 as usize);
}

0 comments on commit 220c345

Please sign in to comment.