Skip to content

Commit 2e08e12

Browse files
committed
lobby: split api.hpp to separate cpp and hpp file
1 parent 9337379 commit 2e08e12

File tree

3 files changed

+365
-313
lines changed

3 files changed

+365
-313
lines changed

src/lobby/api.cpp

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
2+
3+
#include "api.hpp"
4+
5+
6+
namespace {
7+
8+
9+
inline helper::expected<std::string, std::string> is_json_response(const httplib::Result& result) {
10+
if (not result->has_header("Content-Type")) {
11+
return helper::unexpected<std::string>{ "Content-Type not set!" };
12+
}
13+
14+
if (const auto value = result->get_header_value("Content-Type"); value != constants::json_content_type) {
15+
return helper::unexpected<std::string>{ fmt::format("Content-Type is not json but {}", value) };
16+
}
17+
18+
return result->body;
19+
}
20+
21+
22+
inline helper::expected<lobby::ErrorResponse, std::string> is_error_message_response(const httplib::Result& result
23+
) {
24+
25+
const auto body = is_json_response(result);
26+
if (not body.has_value()) {
27+
return helper::unexpected<std::string>{ body.error() };
28+
}
29+
30+
const auto parsed = json::try_parse_json<lobby::ErrorResponse>(body.value());
31+
32+
if (parsed.has_value()) {
33+
return parsed.value();
34+
}
35+
36+
return helper::unexpected<std::string>{ fmt::format("Couldn't parse json with error: {}", parsed.error()) };
37+
}
38+
39+
inline helper::expected<void, std::string> is_request_ok(const httplib::Result& result, int ok_code = 200) {
40+
41+
if (not result) {
42+
return helper::unexpected<std::string>{
43+
fmt::format("Request failed with: {}", httplib::to_string(result.error()))
44+
};
45+
}
46+
47+
if (result->status == 401) {
48+
49+
const auto error_type = is_error_message_response(result);
50+
51+
if (error_type.has_value()) {
52+
return helper::unexpected<std::string>{ fmt::format("Unauthorized: {}", error_type.value().message) };
53+
}
54+
55+
return helper::unexpected<std::string>{ "Unauthorized" };
56+
}
57+
58+
59+
if (result->status != ok_code) {
60+
61+
const auto error_type = is_error_message_response(result);
62+
63+
if (error_type.has_value()) {
64+
return helper::unexpected<std::string>{ fmt::format(
65+
"Got error response with status code {}: '{}' and message: {}", result->status,
66+
httplib::status_message(result->status), error_type.value().message
67+
) };
68+
}
69+
70+
71+
return helper::unexpected<std::string>{ fmt::format(
72+
"Got error response with status code {}: '{}' but expected {}", result->status,
73+
httplib::status_message(result->status), ok_code
74+
) };
75+
}
76+
77+
return {};
78+
};
79+
80+
81+
template<typename T>
82+
helper::expected<T, std::string> get_json_from_request(const httplib::Result& result, int ok_code = 200) {
83+
84+
const auto temp = is_request_ok(result, ok_code);
85+
if (not temp.has_value()) {
86+
return helper::unexpected<std::string>{ temp.error() };
87+
}
88+
89+
const auto body = is_json_response(result);
90+
if (not body.has_value()) {
91+
return helper::unexpected<std::string>{ body.error() };
92+
}
93+
94+
const auto parsed = json::try_parse_json<T>(body.value());
95+
96+
if (parsed.has_value()) {
97+
return parsed.value();
98+
}
99+
100+
return helper::unexpected<std::string>{ fmt::format("Couldn't parse json with error: {}", parsed.error()) };
101+
}
102+
103+
104+
} // namespace
105+
106+
107+
helper::expected<void, std::string> lobby::Client::check_compatibility() {
108+
const auto server_version = get_version();
109+
110+
if (not server_version.has_value()) {
111+
return helper::unexpected<std::string>{ fmt::format(
112+
"Connecting to unsupported server, he can't report his version\nGot error: {}", server_version.error()
113+
) };
114+
}
115+
116+
const auto& version = server_version.value();
117+
118+
//TODO(Totto): if version is semver, support semver comparison
119+
if (Client::supported_version.string() != version.version) {
120+
return helper::unexpected<std::string>{ fmt::format(
121+
"Connecting to unsupported server, version is {}, but we support only {}",
122+
Client::supported_version.string(), version.version
123+
) };
124+
}
125+
126+
return {};
127+
}
128+
129+
helper::expected<void, std::string> lobby::Client::check_reachability() {
130+
131+
auto result = m_client.Get("/");
132+
133+
if (not result) {
134+
return helper::unexpected<std::string>{
135+
fmt::format("Server not reachable: {}", httplib::to_string(result.error()))
136+
};
137+
}
138+
139+
return {};
140+
}
141+
142+
lobby::Client::Client(const std::string& api_url) : m_client{ api_url } {
143+
144+
// clang-format off
145+
m_client.set_default_headers({
146+
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
147+
{ "Accept-Encoding",
148+
149+
#if defined(CPPHTTPLIB_ZLIB_SUPPORT)
150+
"gzip, deflate"
151+
#endif
152+
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) && defined(CPPHTTPLIB_BROTLI_SUPPORT)
153+
", "
154+
#endif
155+
#if defined(CPPHTTPLIB_BROTLI_SUPPORT)
156+
"br"
157+
#endif
158+
},
159+
#endif
160+
// clang-format on
161+
{ "Accept", constants::json_content_type } });
162+
163+
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
164+
m_client.set_compress(true);
165+
m_client.set_decompress(true);
166+
#endif
167+
}
168+
169+
helper::expected<lobby::VersionResult, std::string> lobby::Client::get_version() {
170+
auto res = m_client.Get("/version");
171+
172+
return get_json_from_request<VersionResult>(res);
173+
}
174+
175+
176+
lobby::Client::Client(Client&& other) noexcept
177+
: m_client{ std::move(other.m_client) },
178+
m_authentication_token{ std::move(other.m_authentication_token) } { }
179+
180+
lobby::Client::~Client() = default;
181+
182+
helper::expected<lobby::Client, std::string> lobby::Client::get_client(const std::string& url) {
183+
184+
Client client{ url };
185+
186+
const auto reachable = client.check_reachability();
187+
188+
if (not reachable.has_value()) {
189+
return helper::unexpected<std::string>{ reachable.error() };
190+
}
191+
192+
//TODO(Totto): once version is standard, check here if the version is supported
193+
194+
return client;
195+
}
196+
197+
198+
helper::expected<lobby::LoginResponse, std::string> lobby::Client::login(const Credentials& credentials) {
199+
const auto json_result = json::try_json_to_string(credentials);
200+
if (not json_result.has_value()) {
201+
return helper::unexpected<std::string>{ json_result.error() };
202+
}
203+
204+
auto res = m_client.Post("/login", json_result.value(), constants::json_content_type);
205+
206+
return get_json_from_request<LoginResponse>(res);
207+
}
208+
209+
210+
bool lobby::Client::is_authenticated() {
211+
return m_authentication_token.has_value();
212+
}
213+
214+
bool lobby::Client::authenticate(const Credentials& credentials) {
215+
216+
const auto result = login(credentials);
217+
218+
if (not result.has_value()) {
219+
spdlog::error("Failed authenticating user {}: {}", credentials.username, result.error());
220+
m_authentication_token = std::nullopt;
221+
return false;
222+
}
223+
224+
m_authentication_token = result.value().jwt;
225+
226+
m_client.set_bearer_token_auth(m_authentication_token.value());
227+
228+
return true;
229+
}
230+
231+
helper::expected<std::vector<lobby::LobbyInfo>, std::string> lobby::Client::get_lobbies() {
232+
auto res = m_client.Get("/lobbies");
233+
234+
return get_json_from_request<std::vector<LobbyInfo>>(res);
235+
}
236+
237+
238+
helper::expected<void, std::string> lobby::Client::join_lobby(int lobby_id) {
239+
if (not is_authenticated()) {
240+
return helper::unexpected<std::string>{
241+
"Authentication needed for this "
242+
"endpoint, but not authenticated!"
243+
};
244+
}
245+
246+
auto res = m_client.Post(fmt::format("/lobbies/{}", lobby_id));
247+
248+
return is_request_ok(res, 204);
249+
}
250+
251+
helper::expected<lobby::LobbyDetail, std::string> lobby::Client::get_lobby_detail(int lobby_id) {
252+
if (not is_authenticated()) {
253+
return helper::unexpected<std::string>{
254+
"Authentication needed for this "
255+
"endpoint, but not authenticated!"
256+
};
257+
}
258+
259+
auto res = m_client.Get(fmt::format("/lobbies/{}", lobby_id));
260+
261+
return get_json_from_request<LobbyDetail>(res);
262+
}
263+
264+
helper::expected<void, std::string> lobby::Client::delete_lobby(int lobby_id) {
265+
if (not is_authenticated()) {
266+
return helper::unexpected<std::string>{
267+
"Authentication needed for this "
268+
"endpoint, but not authenticated!"
269+
};
270+
}
271+
272+
auto res = m_client.Delete(fmt::format("/lobbies/{}", lobby_id));
273+
274+
return is_request_ok(res, 204);
275+
}
276+
277+
helper::expected<void, std::string> lobby::Client::leave_lobby(int lobby_id) {
278+
if (not is_authenticated()) {
279+
return helper::unexpected<std::string>{
280+
"Authentication needed for this "
281+
"endpoint, but not authenticated!"
282+
};
283+
}
284+
285+
auto res = m_client.Put(fmt::format("/lobbies/{}/leave", lobby_id));
286+
287+
return is_request_ok(res, 204);
288+
}
289+
290+
helper::expected<void, std::string> lobby::Client::start_lobby(int lobby_id) {
291+
if (not is_authenticated()) {
292+
return helper::unexpected<std::string>{
293+
"Authentication needed for this "
294+
"endpoint, but not authenticated!"
295+
};
296+
}
297+
298+
auto res = m_client.Post(fmt::format("/lobbies/{}/start", lobby_id));
299+
300+
return is_request_ok(res, 204);
301+
}
302+
303+
helper::expected<lobby::LobbyCreateResponse, std::string> lobby::Client::create_lobby(
304+
const CreateLobbyRequest& arguments
305+
) {
306+
if (not is_authenticated()) {
307+
return helper::unexpected<std::string>{
308+
"Authentication needed for this "
309+
"endpoint, but not authenticated!"
310+
};
311+
}
312+
313+
const auto json_result = json::try_json_to_string(arguments);
314+
if (not json_result.has_value()) {
315+
return helper::unexpected<std::string>{ json_result.error() };
316+
}
317+
318+
auto res = m_client.Post("/lobbies", json_result.value(), constants::json_content_type);
319+
320+
return get_json_from_request<LobbyCreateResponse>(res, 201);
321+
}
322+
323+
helper::expected<std::vector<lobby::PlayerInfo>, std::string> lobby::Client::get_users() {
324+
325+
auto res = m_client.Get("/users");
326+
327+
return get_json_from_request<std::vector<PlayerInfo>>(res);
328+
}
329+
330+
helper::expected<void, std::string> lobby::Client::register_user(const RegisterRequest& register_request) {
331+
const auto json_result = json::try_json_to_string(register_request);
332+
if (not json_result.has_value()) {
333+
return helper::unexpected<std::string>{ json_result.error() };
334+
}
335+
336+
auto res = m_client.Post("/register", json_result.value(), constants::json_content_type);
337+
338+
return is_request_ok(res, 204);
339+
}

0 commit comments

Comments
 (0)