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
117
118
119
120
121
122
//! Feature `bevy_net_backend`: typed HTTP calls through that client. Any [`HttpCall`] (every
//! route of every server module, or a game's own) becomes a `bevy_net_backend` request with its
//! method, path, JSON body or query string and the protocol header, exactly as
//! `net_backend_client` sends it; the answer arrives as that client's `JsonResponse<C::Response>`,
//! and [`api_error`] reads the protocol's error out of a refused one.
//!
//! - [`request`]: the `OutgoingRequest` of a call, to adjust (a timeout, a header) and send with
//! `HttpClient::send_json::<C::Response>`.
//! - [`HttpClientCalls::call`]: build and send in one step.
//! - [`api_error`]: the [`ApiError`] of a non-2xx answer (`BackendError::Status` whose body is the
//! protocol's `{"error":{…}}`).
//!
//! A route that needs a Bearer token gets the game's `BackendCredentials` (an [`AccessToken`]
//! is one, see the crate's `Credentials` impl); a route that does not (login, registration,
//! refresh, …) is sent without them, as `net_backend_client` does. Logout takes either: it is
//! sent with the game's credentials (when the game has any), so `LogoutRequest::everywhere()`
//! works with the access token alone, as `net_backend_client`'s `logout` sends it. A path parameter that is
//! missing or would need escaping, or a query payload that is not a flat object, is answered
//! `InvalidRequest` and never sent.
//!
//! ```
//! use bevy_net_backend::{BackendError, HttpClient, JsonResponse};
//! use net_backend_protocol::auth::{Account, GetAccount};
//! use net_backend_protocol::bevy::{api_error, request, HttpClientCalls};
//! use net_backend_protocol::{codes, HttpCall};
//!
//! // Register the answer type once: `app.add_json_response::<Account>()`.
//! fn load_account(client: &HttpClient) {
//! client.call(&GetAccount::new());
//! }
//!
//! fn read_answer(answer: &JsonResponse<Account>) {
//! match &answer.result {
//! Ok(account) => println!("signed in as account {}", account.id.get()),
//! Err(error) => match api_error(error) {
//! Some(api) if api.code == codes::UNAUTHORIZED => println!("log in again"),
//! Some(api) => println!("the server refused: {}", api.message),
//! None => println!("no answer from the server: {error}"),
//! },
//! }
//! }
//!
//! let built = request(&GetAccount::new());
//! assert_eq!((built.method().as_str(), built.path()), ("GET", GetAccount::ROUTE.path));
//! # let _ = (load_account, read_answer);
//! ```
//!
//! [`AccessToken`]: crate::AccessToken
use Method;
use ;
use crate;
use crate;
use crate;
/// The `bevy_net_backend` request for `call`: `C::ROUTE.method` on [`call.path()`](HttpCall::path),
/// the payload as the JSON body (`content-type: application/json`) or the query string (absent
/// fields left out), `accept: application/json` and [`PROTOCOL_HEADER`]; without the game's
/// credentials when the route needs no token (except logout, which takes a Bearer token or the
/// refresh token in the body: it keeps them). Send it with
/// `HttpClient::send_json::<C::Response>` (after `app.add_json_response::<C::Response>()`).
///
/// A path parameter that is missing or would need escaping, or a query payload that is not a
/// flat object: the request is marked invalid (answered `InvalidRequest`, never sent).
/// The protocol's error in a refused answer: `Some` for a [`BackendError::Status`] whose body is
/// an [`ErrorBody`] (`{"error":{"code":…,"message":…}}`, every error of every route), `None` for
/// any other error (no answer, a proxy's error page, a timeout, …). The status is the
/// `BackendError`'s own (`BackendError::Status` carries it), and so is `retry_after()`.
/// Typed calls on `bevy_net_backend`'s [`HttpClient`].