Skip to content

Commit 36610a8

Browse files
committed
Deprecate graphql_client_web, improve web example
The web client now lives in `graphql_client::web`, behind the `web` feature. The crate is still present but deprecated. It now reexports from graphql_client.
1 parent c9f3045 commit 36610a8

File tree

16 files changed

+246
-237
lines changed

16 files changed

+246
-237
lines changed

examples/web/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ crate-type = ["cdylib"]
1313
[dependencies]
1414
graphql_client = { path = "../../graphql_client" }
1515
graphql_client_web = { path = "../../graphql_client_web" }
16-
wasm-bindgen = "0.2.12"
16+
wasm-bindgen = "0.2.43"
1717
serde = { version = "1.0.67", features = ["derive"] }
1818
serde_json = "1.0.22"
1919
lazy_static = "1.0.1"

examples/web/README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
# call from JS example
22

3-
This is a demo of the library compiled to webassembly for use in a browser.
3+
This is a demo of the library compiled to WebAssembly for use in a browser.
44

55
## Build
66

7-
You will need the Rust toolchain, npm and the wasm-bindgen cli (`cargo install wasm-bindgen-cli`) for this to work. Just run:
7+
You will need the Rust toolchain and the
8+
[`wasm-pack`](https://rustwasm.github.io/wasm-pack/) CLI
9+
(`cargo install --force wasm-pack`) for this to work. To build
10+
the project, run the following command in this directory:
811

12+
```bash
13+
wasm-pack build --target=web
914
```
10-
./build.sh
15+
16+
The compiled WebAssembly program and the glue JS code will be
17+
located in the `./pkg` directory. To run the app, start an
18+
HTTP server in this directory - it contains an `index.html`
19+
file. For example, if you have Python 3:
20+
21+
```bash
22+
python3 -m http.server 8000
1123
```

examples/web/build.sh

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

examples/web/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
</head>
99

1010
<body>
11-
<script src='./index.js'></script>
11+
<script type="module">
12+
import init from '/pkg/web.js';
13+
init("/pkg/web_bg.wasm");
14+
</script>
1215
</body>
1316

1417
</html>

examples/web/index.js

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

examples/web/package.json

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

examples/web/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use futures::Future;
2-
use graphql_client_web::*;
2+
use graphql_client::GraphQLQuery;
33
use lazy_static::*;
44
use std::cell::RefCell;
55
use std::sync::Mutex;
66
use wasm_bindgen::prelude::*;
7+
use wasm_bindgen::JsCast;
78
use wasm_bindgen_futures::future_to_promise;
89

910
#[derive(GraphQLQuery)]
@@ -23,7 +24,7 @@ lazy_static! {
2324
}
2425

2526
fn load_more() -> impl Future<Item = JsValue, Error = JsValue> {
26-
let client = graphql_client_web::Client::new("https://www.graphqlhub.com/graphql");
27+
let client = graphql_client::web::Client::new("https://www.graphqlhub.com/graphql");
2728
let variables = puppy_smiles::Variables {
2829
after: LAST_ENTRY
2930
.lock()
@@ -63,7 +64,10 @@ fn add_load_more_button() {
6364
);
6465
btn.add_event_listener_with_callback(
6566
"click",
66-
js_sys::Function::try_from(&on_click.as_ref()).expect("on click is not a Function"),
67+
&on_click
68+
.as_ref()
69+
.dyn_ref()
70+
.expect("on click is not a Function"),
6771
)
6872
.expect("could not add event listener to load more button");
6973

@@ -126,7 +130,7 @@ fn render_response(response: graphql_client_web::Response<puppy_smiles::Response
126130
.expect("could not append response");
127131
}
128132

129-
#[wasm_bindgen]
133+
#[wasm_bindgen(start)]
130134
pub fn run() {
131135
log("Hello there");
132136
let message_area = document()

examples/web/webpack.config.js

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

graphql_client/Cargo.toml

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,49 @@ serde = { version = "^1.0.78", features = ["derive"] }
1616
serde_json = "1.0"
1717
doc-comment = "0.3.1"
1818

19-
[dev-dependencies]
20-
reqwest = "*"
19+
[dependencies.futures]
20+
version = "0.1"
21+
optional = true
22+
23+
[dependencies.js-sys]
24+
version = "0.3.5"
25+
optional = true
26+
27+
[dependencies.log]
28+
version = "0.4.6"
29+
optional = true
30+
31+
[dependencies.web-sys]
32+
version = "0.3.2"
33+
optional = true
34+
features = [
35+
"Headers",
36+
"Request",
37+
"RequestInit",
38+
"Response",
39+
"Window",
40+
]
41+
42+
[dependencies.wasm-bindgen]
43+
version = "0.2.43"
44+
optional = true
45+
46+
[dependencies.wasm-bindgen-futures]
47+
version = "0.3.2"
48+
optional = true
49+
50+
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.reqwest]
51+
version = "*"
52+
53+
[dev-dependencies.wasm-bindgen-test]
54+
version = "0.2.43"
55+
56+
[features]
57+
web = [
58+
"futures",
59+
"js-sys",
60+
"log",
61+
"wasm-bindgen",
62+
"wasm-bindgen-futures",
63+
"web-sys",
64+
]

graphql_client/src/lib.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@ pub use graphql_query_derive::*;
1515

1616
use serde::*;
1717

18-
#[cfg(test)]
19-
use serde_json::json;
20-
21-
#[doc(hidden)]
22-
pub use graphql_query_derive::*;
18+
#[cfg(feature = "web")]
19+
pub mod web;
2320

2421
use std::collections::HashMap;
2522
use std::fmt::{self, Display};
@@ -289,6 +286,7 @@ pub struct Response<Data> {
289286
#[cfg(test)]
290287
mod tests {
291288
use super::*;
289+
use serde_json::json;
292290

293291
#[test]
294292
fn graphql_error_works_with_just_message() {

graphql_client/src/web.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//! Use graphql_client inside browsers with
2+
//! [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen).
3+
4+
use crate::*;
5+
use failure::*;
6+
use futures::{Future, IntoFuture};
7+
use log::*;
8+
use std::collections::HashMap;
9+
use wasm_bindgen::{JsCast, JsValue};
10+
use wasm_bindgen_futures::JsFuture;
11+
12+
/// The main interface to the library.
13+
///
14+
/// The workflow is the following:
15+
///
16+
/// - create a client
17+
/// - (optionally) configure it
18+
/// - use it to perform queries with the [call] method
19+
pub struct Client {
20+
endpoint: String,
21+
headers: HashMap<String, String>,
22+
}
23+
24+
/// All the ways a request can go wrong.
25+
///
26+
/// not exhaustive
27+
#[derive(Debug, Fail, PartialEq)]
28+
pub enum ClientError {
29+
/// The body couldn't be built
30+
#[fail(display = "Request body is not a valid string")]
31+
Body,
32+
/// An error caused by window.fetch
33+
#[fail(display = "Network error")]
34+
Network(String),
35+
/// Error in a dynamic JS cast that should have worked
36+
#[fail(display = "JS casting error")]
37+
Cast,
38+
/// No window object could be retrieved
39+
#[fail(
40+
display = "No Window object available - the client works only in a browser (non-worker) context"
41+
)]
42+
NoWindow,
43+
/// Response shape does not match the generated code
44+
#[fail(display = "Response shape error")]
45+
ResponseShape,
46+
/// Response could not be converted to text
47+
#[fail(display = "Response conversion to text failed (Response.text threw)")]
48+
ResponseText,
49+
/// Exception thrown when building the request
50+
#[fail(display = "Error building the request")]
51+
RequestError,
52+
/// Other JS exception
53+
#[fail(display = "Unexpected JS exception")]
54+
JsException,
55+
}
56+
57+
impl Client {
58+
/// Initialize a client. The `endpoint` parameter is the URI of the GraphQL API.
59+
pub fn new<Endpoint>(endpoint: Endpoint) -> Client
60+
where
61+
Endpoint: Into<String>,
62+
{
63+
Client {
64+
endpoint: endpoint.into(),
65+
headers: HashMap::new(),
66+
}
67+
}
68+
69+
/// Add a header to those sent with the requests. Can be used for things like authorization.
70+
pub fn add_header(&mut self, name: &str, value: &str) {
71+
self.headers.insert(name.into(), value.into());
72+
}
73+
74+
/// Perform a query.
75+
///
76+
// Lint disabled: We can pass by value because it's always an empty struct.
77+
#[allow(clippy::needless_pass_by_value)]
78+
pub fn call<Q: GraphQLQuery + 'static>(
79+
&self,
80+
_query: Q,
81+
variables: Q::Variables,
82+
) -> impl Future<Item = crate::Response<Q::ResponseData>, Error = ClientError> + 'static {
83+
// this can be removed when we convert to async/await
84+
let endpoint = self.endpoint.clone();
85+
let custom_headers = self.headers.clone();
86+
87+
web_sys::window()
88+
.ok_or_else(|| ClientError::NoWindow)
89+
.into_future()
90+
.and_then(move |window| {
91+
serde_json::to_string(&Q::build_query(variables))
92+
.map_err(|_| ClientError::Body)
93+
.map(move |body| (window, body))
94+
})
95+
.and_then(move |(window, body)| {
96+
let mut request_init = web_sys::RequestInit::new();
97+
request_init
98+
.method("POST")
99+
.body(Some(&JsValue::from_str(&body)));
100+
101+
web_sys::Request::new_with_str_and_init(&endpoint, &request_init)
102+
.map_err(|_| ClientError::JsException)
103+
.map(|request| (window, request))
104+
// "Request constructor threw");
105+
})
106+
.and_then(move |(window, request)| {
107+
let headers = request.headers();
108+
headers
109+
.set("Content-Type", "application/json")
110+
.map_err(|_| ClientError::RequestError)?;
111+
headers
112+
.set("Accept", "application/json")
113+
.map_err(|_| ClientError::RequestError)?;
114+
115+
for (header_name, header_value) in custom_headers.iter() {
116+
headers
117+
.set(header_name, header_value)
118+
.map_err(|_| ClientError::RequestError)?;
119+
}
120+
121+
Ok((window, request))
122+
})
123+
.and_then(move |(window, request)| {
124+
JsFuture::from(window.fetch_with_request(&request))
125+
.map_err(|err| ClientError::Network(js_sys::Error::from(err).message().into()))
126+
})
127+
.and_then(move |res| {
128+
debug!("response: {:?}", res);
129+
res.dyn_into::<web_sys::Response>()
130+
.map_err(|_| ClientError::Cast)
131+
})
132+
.and_then(move |cast_response| {
133+
cast_response.text().map_err(|_| ClientError::ResponseText)
134+
})
135+
.and_then(move |text_promise| {
136+
JsFuture::from(text_promise).map_err(|_| ClientError::ResponseText)
137+
})
138+
.and_then(|text| {
139+
let response_text = text.as_string().unwrap_or_default();
140+
debug!("response text as string: {:?}", response_text);
141+
serde_json::from_str(&response_text).map_err(|_| ClientError::ResponseShape)
142+
})
143+
}
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use super::*;
149+
150+
#[test]
151+
fn client_new() {
152+
Client::new("https://example.com/graphql");
153+
Client::new("/graphql");
154+
}
155+
}

graphql_client_web/tests/web.rs renamed to graphql_client/tests/web.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
#![cfg(target_arch = "wasm32")]
2+
13
use futures::Future;
2-
use graphql_client_web::Client;
3-
use graphql_client_web::*;
4+
use graphql_client::{web::Client, GraphQLQuery};
45
use wasm_bindgen::JsValue;
56
use wasm_bindgen_test::wasm_bindgen_test_configure;
67
use wasm_bindgen_test::*;
@@ -9,6 +10,7 @@ wasm_bindgen_test_configure!(run_in_browser);
910

1011
#[wasm_bindgen_test]
1112
fn build_client() {
13+
// just to test it doesn't crash
1214
Client::new("https://example.com/graphql");
1315
Client::new("/graphql");
1416
}
@@ -89,7 +91,7 @@ fn test_bad_url() -> impl Future<Item = (), Error = JsValue> {
8991
.map_err(|err| {
9092
assert_eq!(
9193
err,
92-
graphql_client_web::ClientError::Network(
94+
graphql_client::web::ClientError::Network(
9395
"NetworkError when attempting to fetch resource.".into()
9496
)
9597
);

0 commit comments

Comments
 (0)