-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathreqres1.rs
116 lines (106 loc) · 4.92 KB
/
reqres1.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::collections::HashMap;
use derive_builder::Builder;
use rustify::{Client, Endpoint, Wrapper};
use rustify_derive::Endpoint;
use serde::{de::DeserializeOwned, Deserialize};
// While using a builder archetype for requests is not required, it's often the
// cleanest way for building requests. For this endpoint it doesn't bring too
// much benefit, however, for consistency it's implemented anyways.
//
// Setting the `builder` attribute to true adds a `builder()` method to the
// struct for easily getting a default instance of the request builder.
//
// Rustify supports a few more parameters in the endpoint definition than are
// shown here. It resorts to sane defaults in most cases. For our example they
// are as follows:
// * method: defaults to GET
// * request_type: defaults to JSON
// * response_type: defaults to JSON
#[derive(Builder, Endpoint)]
#[endpoint(path = "/api/users", response = "Vec<User>", builder = "true")]
struct ListUsersRequest {
// Tagging this field with #[endpoint(query)] informs rustify that this
// field should be appended as a query parameter to the request URL.
#[endpoint(query)]
pub page: usize,
}
// Some responses from the API are paginated and contain a common wrapper around
// the actual resulting data. Since this is so prevalent in APIs, rustify offers
// a `Wrapper` which can be used to define this behavior.
//
// Below we define the details of the wrapper that appears around paginated
// responses. The form of the resulting data field is specified with a generic
// and will be supplied when we call the endpoint. Endpoints have a special
// `exec_wrap()` method which will automatically wrap the response from the
// endpoint in the given wrapper.
#[derive(Debug, Deserialize)]
pub struct PaginationWrapper<T> {
pub page: usize,
pub per_page: usize,
pub total: usize,
pub total_pages: usize,
pub data: T,
pub support: HashMap<String, String>,
}
// This is almost always the form that the implementation will take.
// Unfortunately, Rust does not support associated types having a default
// type set to a generic, so we must define it when we use it.
impl<T: DeserializeOwned + Send + Sync> Wrapper for PaginationWrapper<T> {
type Value = T;
}
// Our endpoint returns a JSON array of objects which each contain information
// about a user. We represent this by creating a `User` struct and then using
// `Vec<User>` in the `response` parameter of the endpoint to inform rustify on
// how it should deserialize the response. We don't need to worry about the
// wrapper because it's handled for us!
#[derive(Debug, Deserialize)]
struct User {
pub id: usize,
pub email: String,
pub first_name: String,
pub last_name: String,
}
#[tokio::main]
async fn main() {
// In order to execute endpoints, we must first create a client configured
// with the base URL of our HTTP API server. In this case we're using the
// popular reqres.in for our example.
// Asynchronous clients can be found in rustify::clients and synchronous
// clients in rustify::blocking::clients.
let client = Client::default("https://reqres.in/");
// We use the builder archetype here for constructing an instance of the
// endpoint that we can then execute. It's safe to unwrap because we know
// that all required fields have been specified.
let endpoint = ListUsersRequest::builder().page(1).build().unwrap();
// Here is where the magic of rustify happens. We call `exec()` which
// takes an instance of a `Client` and behind the scenes rustify will
// initiate a connection to the API server and send a HTTP request as
// defined by the endpoint. In this case, it sends a GET request to
// https://reqres.in/api/users?page=1 and automatically deserializes the
// response into a PaginationWrapper<ListUsersResponse> when we call parse.
let result = endpoint.exec(&client).await;
// Executing an endpoint can fail for a number of reasons: there was a
// problem building the request, an underlying network issue, the server
// returned a non-200 response, the response could not be properly
// deserialized, etc. Rustify uses a common error enum which contains a
// number of variants for identifying the root cause.
match result {
// We inform rustify of the wrapped response by calling `wrap()` instead
// of `parse()` which takes a single type argument that instructs
// rustify how to properly parse the result (in this case our data is
// wrapped in a pagination wrapper).
Ok(r) => match r.wrap::<PaginationWrapper<_>>() {
Ok(d) => {
d.data.iter().for_each(print_user);
}
Err(e) => println!("Error: {:#?}", e),
},
Err(e) => println!("Error: {:#?}", e),
};
}
fn print_user(user: &User) {
println!(
"ID: {}\nEmail: {}\nFirst Name: {}\nLast Name: {}\n\n",
user.id, user.email, user.first_name, user.last_name
);
}