Skip to content

Commit 5bbba4f

Browse files
authored
Merge pull request #203 from graphql-rust/better-wasm-example
Improve the wasm example to use graphql_client_web and web_sys
2 parents c2128f9 + 2cd7695 commit 5bbba4f

File tree

8 files changed

+135
-147
lines changed

8 files changed

+135
-147
lines changed

README.md

+10-17
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ A typed GraphQL client library for Rust.
99

1010
## Features
1111

12-
- Precise types for query variables and responses
13-
- Supports GraphQL fragments, objects, unions, inputs, enums, custom scalars and input objects
14-
- Works in the browser (WebAssembly)
15-
- Subscriptions support (serialization-deserialization only at the moment)
16-
- Copies documentation from the GraphQL schema to the generated Rust code
17-
- Arbitrary derives on the generated responses
18-
- Arbitrary custom scalars
19-
- Supports multiple operations per query document
12+
- Precise types for query variables and responses.
13+
- Supports GraphQL fragments, objects, unions, inputs, enums, custom scalars and input objects.
14+
- Works in the browser (WebAssembly).
15+
- Subscriptions support (serialization-deserialization only at the moment).
16+
- Copies documentation from the GraphQL schema to the generated Rust code.
17+
- Arbitrary derives on the generated responses.
18+
- Arbitrary custom scalars.
19+
- Supports multiple operations per query document.
2020
- Supports setting GraphQL fields as deprecated and having the Rust compiler check
2121
their use.
22+
- [web client](./graphql_client_web) for boilerplate-free API calls from browsers.
2223

2324
## Getting started
2425

@@ -150,15 +151,7 @@ There is an [`include`](https://doc.rust-lang.org/cargo/reference/manifest.html#
150151

151152
## Examples
152153

153-
See the examples directory in this repository.
154-
155-
## Roadmap
156-
157-
A lot of desired features have been defined in issues.
158-
159-
graphql_client does not provide any networking, caching or other client functionality yet. Integration with different HTTP libraries is planned, although building one yourself is trivial (just send the constructed request payload as JSON with a POST request to a GraphQL endpoint, modulo authentication).
160-
161-
There is an embryonic CLI for downloading schemas - the plan is to make it something similar to `apollo-codegen`.
154+
See the [examples directory](./graphql_client/examples) in this repository.
162155

163156
## Contributors
164157

graphql_client/examples/call_from_js/Cargo.toml

+18-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "call_from_js"
33
version = "0.1.0"
44
authors = ["Tom Houlé <[email protected]>"]
5+
edition = "2018"
56

67
[profile.release]
78
lto = "thin"
@@ -10,12 +11,28 @@ lto = "thin"
1011
crate-type = ["cdylib"]
1112

1213
[dependencies]
13-
graphql_client = { path = "../..", version = "0.4.0" }
14+
graphql_client_web = { path = "../../../graphql_client_web" }
1415
wasm-bindgen = "0.2.12"
1516
serde = "1.0.67"
1617
serde_derive = "1.0.67"
1718
serde_json = "1.0.22"
1819
lazy_static = "1.0.1"
20+
js-sys = "0.3.6"
21+
futures = "0.1.25"
22+
wasm-bindgen-futures = "0.3.6"
23+
24+
[dependencies.web-sys]
25+
version = "0.3.6"
26+
features = [
27+
"console",
28+
"Document",
29+
"Element",
30+
"EventTarget",
31+
"Node",
32+
"HtmlBodyElement",
33+
"HtmlDocument",
34+
"HtmlElement",
35+
]
1936

2037
[workspace]
2138
members = ["."]

graphql_client/examples/call_from_js/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# call from JS example
22

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

55
## Build
66

graphql_client/examples/call_from_js/convenient_fetch.js

-14
This file was deleted.

graphql_client/examples/call_from_js/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"serve": "webpack-dev-server"
44
},
55
"devDependencies": {
6-
"webpack": "^4.0.1",
7-
"webpack-cli": "^2.0.10",
8-
"webpack-dev-server": "^3.1.0"
6+
"webpack": "^4.28.3",
7+
"webpack-cli": "^3.1.2",
8+
"webpack-dev-server": "^3.1.14"
99
}
1010
}

graphql_client/examples/call_from_js/src/lib.rs

+97-110
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,144 @@
1-
#![feature(use_extern_macros)]
2-
3-
#[macro_use]
4-
extern crate graphql_client;
5-
extern crate serde;
6-
#[macro_use]
7-
extern crate serde_derive;
8-
extern crate serde_json;
9-
#[macro_use]
10-
extern crate lazy_static;
11-
1+
use futures::Future;
2+
use lazy_static::*;
123
use std::cell::RefCell;
134
use std::sync::Mutex;
145

15-
extern crate wasm_bindgen;
166
use wasm_bindgen::prelude::*;
7+
use wasm_bindgen_futures::future_to_promise;
178

18-
use graphql_client::*;
9+
use graphql_client_web::*;
1910

2011
#[derive(GraphQLQuery)]
2112
#[graphql(
2213
schema_path = "schema.json",
23-
query_path = "src/puppy_smiles.graphql"
14+
query_path = "src/puppy_smiles.graphql",
15+
response_derives = "Debug"
2416
)]
2517
struct PuppySmiles;
2618

27-
#[wasm_bindgen]
28-
extern "C" {
29-
#[wasm_bindgen(js_namespace = console)]
30-
fn log(s: &str);
31-
32-
type HTMLDocument;
33-
static document: HTMLDocument;
34-
#[wasm_bindgen(method)]
35-
fn createElement(this: &HTMLDocument, tagName: &str) -> Element;
36-
#[wasm_bindgen(method, getter)]
37-
fn body(this: &HTMLDocument) -> Element;
38-
39-
type Element;
40-
#[wasm_bindgen(method, setter = innerHTML)]
41-
fn set_inner_html(this: &Element, html: &str);
42-
#[wasm_bindgen(method, js_name = appendChild)]
43-
fn append_child(this: &Element, other: Element);
44-
#[wasm_bindgen(method, js_name = addEventListener)]
45-
fn add_event_listener(this: &Element, event: &str, cb: &Closure<Fn()>);
46-
}
47-
48-
#[wasm_bindgen(module = "./convenient_fetch")]
49-
extern "C" {
50-
fn convenient_post(
51-
req: &str,
52-
body: String,
53-
on_complete: &Closure<Fn(String)>,
54-
on_error: &Closure<Fn()>,
55-
);
19+
fn log(s: &str) {
20+
web_sys::console::log_1(&JsValue::from_str(s))
5621
}
5722

5823
lazy_static! {
5924
static ref LAST_ENTRY: Mutex<RefCell<Option<String>>> = Mutex::new(RefCell::new(None));
6025
}
6126

62-
fn load_more() {
63-
let cb = cb();
64-
let on_error = on_error();
65-
convenient_post(
66-
"https://www.graphqlhub.com/graphql",
67-
serde_json::to_string(&PuppySmiles::build_query(puppy_smiles::Variables {
68-
after: LAST_ENTRY
69-
.lock()
70-
.ok()
71-
.and_then(|opt| opt.borrow().to_owned()),
72-
})).unwrap(),
73-
&cb,
74-
&on_error,
75-
);
27+
fn load_more() -> impl Future<Item = JsValue, Error = JsValue> {
28+
let client = graphql_client_web::Client::new("https://www.graphqlhub.com/graphql");
29+
let variables = puppy_smiles::Variables {
30+
after: LAST_ENTRY
31+
.lock()
32+
.ok()
33+
.and_then(|opt| opt.borrow().to_owned()),
34+
};
35+
let response = client.call(PuppySmiles, variables);
36+
37+
response
38+
.map(|response| {
39+
render_response(response);
40+
JsValue::NULL
41+
})
42+
.map_err(|err| {
43+
log(&format!(
44+
"Could not fetch puppies. graphql_client_web error: {:?}",
45+
err
46+
));
47+
JsValue::NULL
48+
})
49+
}
7650

77-
cb.forget();
78-
on_error.forget();
51+
fn document() -> web_sys::Document {
52+
web_sys::window()
53+
.expect("no window")
54+
.document()
55+
.expect("no document")
7956
}
8057

8158
fn add_load_more_button() {
82-
let btn = document.createElement("button");
59+
let btn = document()
60+
.create_element("button")
61+
.expect("could not create button");
8362
btn.set_inner_html("I WANT MORE PUPPIES");
84-
let on_click = Closure::new(move || load_more());
85-
btn.add_event_listener("click", &on_click);
63+
let on_click = Closure::wrap(
64+
Box::new(move || future_to_promise(load_more())) as Box<FnMut() -> js_sys::Promise>
65+
);
66+
btn.add_event_listener_with_callback(
67+
"click",
68+
js_sys::Function::try_from(&on_click.as_ref()).expect("on click is not a Function"),
69+
)
70+
.expect("could not add event listener to load more button");
8671

87-
let doc = document.body();
88-
doc.append_child(btn);
72+
let doc = document().body().expect("no body");
73+
doc.append_child(&btn).expect("could not append button");
8974

9075
on_click.forget();
9176
}
9277

93-
fn cb() -> Closure<Fn(String)> {
78+
fn render_response(response: graphql_client_web::Response<puppy_smiles::ResponseData>) {
9479
use std::fmt::Write;
9580

96-
Closure::new(move |s: String| {
97-
log(&format!("response body\n\n{}", s));
98-
99-
let parent = document.body();
100-
101-
let json: Response<puppy_smiles::ResponseData> =
102-
serde_json::from_str(&s).expect("failed to deserialize");
103-
let response = document.createElement("div");
104-
let mut inner_html = String::new();
105-
let listings = json
106-
.data
107-
.expect("response data")
108-
.reddit
109-
.expect("reddit")
110-
.subreddit
111-
.expect("puppy smiles subreddit")
112-
.new_listings;
113-
114-
let new_cursor: Option<String> = listings[listings.len() - 1]
115-
.as_ref()
116-
.map(|puppy| puppy.fullname_id.clone())
117-
.to_owned();
118-
LAST_ENTRY.lock().unwrap().replace(new_cursor);
119-
120-
for puppy in &listings {
121-
if let Some(puppy) = puppy {
122-
write!(
123-
inner_html,
124-
r#"
81+
log(&format!("response body\n\n{:?}", response));
82+
83+
let parent = document().body().expect("no body");
84+
85+
let json: graphql_client_web::Response<puppy_smiles::ResponseData> = response;
86+
let response = document()
87+
.create_element("div")
88+
.expect("could not create div");
89+
let mut inner_html = String::new();
90+
let listings = json
91+
.data
92+
.expect("response data")
93+
.reddit
94+
.expect("reddit")
95+
.subreddit
96+
.expect("puppy smiles subreddit")
97+
.new_listings;
98+
99+
let new_cursor: Option<String> = listings[listings.len() - 1]
100+
.as_ref()
101+
.map(|puppy| puppy.fullname_id.clone())
102+
.to_owned();
103+
LAST_ENTRY.lock().unwrap().replace(new_cursor);
104+
105+
for puppy in &listings {
106+
if let Some(puppy) = puppy {
107+
write!(
108+
inner_html,
109+
r#"
125110
<div class="card" style="width: 26rem;">
126111
<img class="img-thumbnail card-img-top" alt="{}" src="{}" />
127112
<div class="card-body">
128113
<h5 class="card-title">{}</h5>
129114
</div>
130115
</div>
131116
"#,
132-
puppy.title, puppy.url, puppy.title
133-
).expect("write to string");
134-
}
117+
puppy.title, puppy.url, puppy.title
118+
)
119+
.expect("write to string");
135120
}
136-
response.set_inner_html(&format!(
137-
"<h2>response:</h2><div class=\"container\"><div class=\"row\">{}</div></div>",
138-
inner_html
139-
));
140-
parent.append_child(response);
141-
})
142-
}
143-
144-
fn on_error() -> Closure<Fn()> {
145-
Closure::new(|| log("sad :("))
121+
}
122+
response.set_inner_html(&format!(
123+
"<h2>response:</h2><div class=\"container\"><div class=\"row\">{}</div></div>",
124+
inner_html
125+
));
126+
parent
127+
.append_child(&response)
128+
.expect("could not append response");
146129
}
147130

148131
#[wasm_bindgen]
149132
pub fn run() {
150133
log("Hello there");
151-
let message_area = document.createElement("div");
134+
let message_area = document()
135+
.create_element("div")
136+
.expect("could not create div");
152137
message_area.set_inner_html("<p>good morning</p>");
153-
let parent = document.body();
154-
parent.append_child(message_area);
138+
let parent = document().body().unwrap();
139+
parent
140+
.append_child(&message_area)
141+
.expect("could not append message area");
155142

156143
load_more();
157144
add_load_more_button();

graphql_client_web/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# graphql_client_web
2+
3+
Make boilerplate-free GraphQL API calls from web browsers using [graphql-client](../README.md) and [wasm-bindgen](https://github.com/alexcrichton/wasm-bindgen).
4+
5+
For usage details, see the [API docs](https://docs.rs/graphql_client/latest/graphql_client_web/), the [example](../graphql_client/examples/call_from_js) and the [tests](./tests/web.rs).

graphql_client_web/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use failure::*;
99
use futures::{Future, IntoFuture};
10-
pub use graphql_client::{self, GraphQLQuery};
10+
pub use graphql_client::{self, GraphQLQuery, Response};
1111
use log::*;
1212
use std::collections::HashMap;
1313
use wasm_bindgen::{JsCast, JsValue};

0 commit comments

Comments
 (0)