Skip to content

Commit d54de27

Browse files
authored
kinda tidy golf api? (#82)
* kinda tidy golf api? * more tidy
1 parent bf30f31 commit d54de27

File tree

5 files changed

+327
-119
lines changed

5 files changed

+327
-119
lines changed

cpp/golf_service/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,27 @@ cc_test(
1818
],
1919
)
2020

21+
cc_library(
22+
name = "api",
23+
srcs = ["api.cc"],
24+
hdrs = ["api.h"],
25+
deps = [
26+
"//cpp/cards/golf",
27+
"@com_google_absl//absl/status:statusor",
28+
"@mongoose_cc//:mongoose",
29+
],
30+
)
31+
2132
cc_binary(
2233
name = "golf_service",
2334
srcs = [
2435
"Main.cc",
2536
],
2637
deps = [
38+
":api",
2739
":game_state_mapper",
2840
"//cpp/cards/golf",
41+
"@com_google_absl//absl/status:statusor",
2942
"@com_google_absl//absl/strings",
3043
"@mongoose_cc//:mongoose",
3144
],

cpp/golf_service/Main.cc

Lines changed: 139 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -3,220 +3,243 @@
33
#include <stdexcept>
44
#include <string>
55
#include <unordered_map>
6+
#include <utility>
7+
#include <variant>
68

79
#include "absl/status/statusor.h"
810
#include "absl/strings/str_split.h"
911
#include "cpp/cards/golf/golf.h"
12+
#include "cpp/golf_service/api.h"
1013
#include "cpp/golf_service/game_state_mapper.h"
1114
#include "mongoose.h"
1215

13-
using std::string;
14-
15-
typedef struct Args {
16-
string username;
17-
string gameId;
18-
int players;
19-
golf::Position position;
20-
struct mg_connection *c;
21-
} Args;
22-
23-
typedef struct COMMAND {
24-
const char *name;
25-
string (*command)(Args);
26-
} API;
27-
2816
using golf::GameStateMapper;
17+
using golf_service::DiscardDrawRequest;
18+
using golf_service::GolfServiceRequest;
19+
using golf_service::JoinGameRequest;
20+
using golf_service::KnockRequest;
21+
using golf_service::NewGameRequest;
22+
using golf_service::PeekRequest;
23+
using golf_service::readDiscardDrawRequest;
24+
using golf_service::readJoinGameRequest;
25+
using golf_service::readKnockRequest;
26+
using golf_service::readNewGameRequest;
27+
using golf_service::readPeekRequest;
28+
using golf_service::readRegisterUserRequest;
29+
using golf_service::readSwapForDiscardRequest;
30+
using golf_service::readSwapForDrawRequest;
31+
using golf_service::RegisterUserRequest;
32+
using golf_service::SwapForDiscardRequest;
33+
using golf_service::SwapForDrawRequest;
34+
using std::string;
2935

3036
std::mutex m;
3137
std::unordered_map<std::string, mg_connection *> connectionsByUser;
3238
golf::GameManager gm;
3339
GameStateMapper gsm;
3440

35-
static void registerUser(const Args &args) {
41+
template <typename T>
42+
static bool validRequestType(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
43+
if (std::holds_alternative<T>(serviceRequest)) {
44+
return true;
45+
}
46+
47+
string output("error|invalid request");
48+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
49+
return false;
50+
}
51+
52+
static void registerUser(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
53+
if (!validRequestType<RegisterUserRequest>(serviceRequest, c)) {
54+
return;
55+
}
56+
57+
const RegisterUserRequest registerUserRequest = std::get<RegisterUserRequest>(serviceRequest);
3658
// don't allow re-registration yet
3759
for (auto i = connectionsByUser.begin(); i != connectionsByUser.end(); i++) {
38-
if (connectionsByUser.at(i->first) == args.c) {
60+
if (connectionsByUser.at(i->first) == c) {
3961
string output("error|already registered");
40-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
62+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
4163
return;
4264
}
4365
}
4466

45-
auto res = gm.registerUser(args.username);
67+
auto res = gm.registerUser(registerUserRequest.username);
4668
if (!res.ok()) {
4769
string output("error|");
4870
output.append(res.status().message());
49-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
71+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
5072
return;
5173
}
5274

5375
string user = *res;
54-
connectionsByUser.insert({user, args.c});
76+
connectionsByUser.insert({user, c});
5577
string output(R"({"inGame":false,"username":")" + user + "\"}");
56-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
78+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
5779
}
5880

59-
static bool usernameMismatch(const Args &args) {
60-
if (connectionsByUser.find(args.username) == connectionsByUser.end() ||
61-
connectionsByUser.at(args.username) != args.c) {
81+
static bool usernameMismatch(const string &username, struct mg_connection *c) {
82+
if (connectionsByUser.find(username) == connectionsByUser.end() ||
83+
connectionsByUser.at(username) != c) {
6284
string output("error|username mismatch");
63-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
85+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
6486
return true;
6587
}
6688
return false;
6789
}
6890

6991
static void handleGameManagerResult(const absl::StatusOr<golf::GameStatePtr> &res,
70-
const Args &args) {
92+
struct mg_connection *c) {
7193
if (!res.ok()) {
7294
string output("error|");
7395
output.append(res.status().message());
74-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
96+
mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
7597
return;
7698
}
7799

78100
const auto &gameStatePtr = *res;
79-
for (auto &user : gm.getUsersByGameId(args.gameId)) {
101+
for (auto &user : gm.getUsersByGameId(gameStatePtr->getGameId())) {
80102
auto stateForUser = GameStateMapper::gameStateJson(gameStatePtr, user);
81-
auto c = connectionsByUser.at(user);
82-
mg_ws_send(c, stateForUser.c_str(), stateForUser.size(), WEBSOCKET_OP_TEXT);
103+
auto userConnection = connectionsByUser.at(user);
104+
mg_ws_send(userConnection, stateForUser.c_str(), stateForUser.size(), WEBSOCKET_OP_TEXT);
83105
}
84106
}
85107

86-
static void newGame(const Args &args) {
87-
if (usernameMismatch(args)) {
108+
static void newGame(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
109+
if (!validRequestType<NewGameRequest>(serviceRequest, c)) {
88110
return;
89111
}
90112

91-
auto res = gm.newGame(args.username, args.players);
92-
string output;
93-
if (!res.ok()) {
94-
output.append("error|");
95-
output.append(res.status().message());
96-
} else {
97-
output.append(GameStateMapper::gameStateJson(*res, args.username));
113+
auto newGameRequest = std::get<NewGameRequest>(serviceRequest);
114+
if (usernameMismatch(newGameRequest.username, c)) {
115+
return;
98116
}
99-
mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT);
117+
118+
auto res = gm.newGame(newGameRequest.username, newGameRequest.players);
119+
handleGameManagerResult(res, c);
100120
}
101121

102-
static void joinGame(const Args &args) {
103-
if (usernameMismatch(args)) {
122+
static void joinGame(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
123+
if (!validRequestType<JoinGameRequest>(serviceRequest, c)) {
104124
return;
105125
}
106-
auto res = gm.joinGame(args.gameId, args.username);
107-
handleGameManagerResult(res, args);
108-
}
109126

110-
static void peekAtDrawPile(const Args &args) {
111-
if (usernameMismatch(args)) {
127+
auto joinGameRequest = std::get<JoinGameRequest>(serviceRequest);
128+
if (usernameMismatch(joinGameRequest.username, c)) {
112129
return;
113130
}
114-
auto res = gm.peekAtDrawPile(args.username);
115-
handleGameManagerResult(res, args);
131+
auto res = gm.joinGame(joinGameRequest.gameId, joinGameRequest.username);
132+
handleGameManagerResult(res, c);
116133
}
117134

118-
static void discardFromDrawPile(const Args &args) {
119-
if (usernameMismatch(args)) {
135+
static void peekAtDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
136+
if (!validRequestType<PeekRequest>(serviceRequest, c)) {
120137
return;
121138
}
122-
auto res = gm.swapDrawForDiscardPile(args.username);
123-
handleGameManagerResult(res, args);
139+
140+
auto peekRequest = std::get<PeekRequest>(serviceRequest);
141+
if (usernameMismatch(peekRequest.username, c)) {
142+
return;
143+
}
144+
auto res = gm.peekAtDrawPile(peekRequest.username);
145+
handleGameManagerResult(res, c);
124146
}
125147

126-
static void swapForDrawPile(const Args &args) {
127-
if (usernameMismatch(args)) {
148+
static void discardFromDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
149+
if (!validRequestType<DiscardDrawRequest>(serviceRequest, c)) {
150+
return;
151+
}
152+
153+
auto discardDrawRequest = std::get<DiscardDrawRequest>(serviceRequest);
154+
if (usernameMismatch(discardDrawRequest.username, c)) {
128155
return;
129156
}
130-
auto res = gm.swapForDrawPile(args.username, args.position);
131-
handleGameManagerResult(res, args);
157+
auto res = gm.swapDrawForDiscardPile(discardDrawRequest.username);
158+
handleGameManagerResult(res, c);
132159
}
133160

134-
static void swapForDiscardPile(const Args &args) {
135-
if (usernameMismatch(args)) {
161+
static void swapForDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
162+
if (!validRequestType<SwapForDrawRequest>(serviceRequest, c)) {
136163
return;
137164
}
138-
auto res = gm.swapForDiscardPile(args.username, args.position);
139-
handleGameManagerResult(res, args);
165+
166+
auto swapForDrawRequest = std::get<SwapForDrawRequest>(serviceRequest);
167+
if (usernameMismatch(swapForDrawRequest.username, c)) {
168+
return;
169+
}
170+
auto res = gm.swapForDrawPile(swapForDrawRequest.username, swapForDrawRequest.position);
171+
handleGameManagerResult(res, c);
140172
}
141173

142-
static void knock(const Args &args) {
143-
if (usernameMismatch(args)) {
174+
static void swapForDiscardPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
175+
if (!validRequestType<SwapForDiscardRequest>(serviceRequest, c)) {
176+
return;
177+
}
178+
179+
auto swapForDiscardRequest = std::get<SwapForDiscardRequest>(serviceRequest);
180+
if (usernameMismatch(swapForDiscardRequest.username, c)) {
144181
return;
145182
}
146-
auto res = gm.knock(args.username);
147-
handleGameManagerResult(res, args);
183+
auto res = gm.swapForDiscardPile(swapForDiscardRequest.username, swapForDiscardRequest.position);
184+
handleGameManagerResult(res, c);
148185
}
149186

150-
const std::unordered_map<string, void (*)(const Args &)> handlers{
151-
{"register", registerUser},
152-
{"new", newGame},
153-
{"join", joinGame},
154-
{"peek", peekAtDrawPile},
155-
{"discardDraw", discardFromDrawPile},
156-
{"swapDraw", swapForDrawPile},
157-
{"swapDiscard", swapForDiscardPile},
158-
{"knock", knock},
159-
};
187+
static void knock(const GolfServiceRequest &serviceRequest, struct mg_connection *c) {
188+
if (!validRequestType<KnockRequest>(serviceRequest, c)) {
189+
return;
190+
}
160191

161-
static absl::StatusOr<Args> parseArgs(std::vector<string> &parts, struct mg_connection *c) {
162-
if (parts.size() != 5) {
163-
return absl::InvalidArgumentError("args -> <user>|<game>|<numPlayers>|<pos>");
164-
}
165-
string username = parts[1];
166-
string gameId = parts[2];
167-
int numberOfPlayers;
168-
try {
169-
numberOfPlayers = std::stoi(parts[3]);
170-
} catch (std::invalid_argument const &ex) {
171-
return absl::InvalidArgumentError(ex.what());
172-
} catch (std::out_of_range const &ex) {
173-
return absl::InvalidArgumentError(ex.what());
174-
}
175-
golf::Position position;
176-
if (parts[4] == "tl") {
177-
position = golf::Position::TopLeft;
178-
} else if (parts[4] == "tr") {
179-
position = golf::Position::TopRight;
180-
} else if (parts[4] == "bl") {
181-
position = golf::Position::BottomLeft;
182-
} else if (parts[4] == "br") {
183-
position = golf::Position::BottomRight;
184-
} else {
185-
return absl::InvalidArgumentError("invalid position. must be in (tl, tr, bl, br)");
186-
}
187-
188-
return Args{username, gameId, numberOfPlayers, position, c};
192+
auto knockRequest = std::get<KnockRequest>(serviceRequest);
193+
if (usernameMismatch(knockRequest.username, c)) {
194+
return;
195+
}
196+
auto res = gm.knock(knockRequest.username);
197+
handleGameManagerResult(res, c);
189198
}
190199

200+
typedef std::function<void(const GolfServiceRequest &, struct mg_connection *)> handler;
201+
typedef std::function<absl::StatusOr<GolfServiceRequest>(std::vector<string>)> argReader;
202+
203+
// make this map<string, pair<handler, parser>>
204+
const std::unordered_map<string, std::pair<handler, argReader>> handlers{
205+
{"register", {registerUser, readRegisterUserRequest}},
206+
{"new", {newGame, readNewGameRequest}},
207+
{"join", {joinGame, readJoinGameRequest}},
208+
{"peek", {peekAtDrawPile, readPeekRequest}},
209+
{"discardDraw", {discardFromDrawPile, readDiscardDrawRequest}},
210+
{"swapDraw", {swapForDrawPile, readSwapForDrawRequest}},
211+
{"swapDiscard", {swapForDiscardPile, readSwapForDiscardRequest}},
212+
{"knock", {knock, readKnockRequest}},
213+
};
214+
191215
static void handleMessage(struct mg_ws_message *wm, struct mg_connection *c) {
192216
std::scoped_lock lock(m);
193217

194218
std::vector<string> commandParts =
195219
absl::StrSplit(string(wm->data.ptr), '|', absl::SkipWhitespace());
196-
197-
if (commandParts.size() < 2) {
220+
if (commandParts.empty()) {
198221
string response = "error|arg count";
199222
mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
200223
}
201224

202-
auto res = parseArgs(commandParts, c);
203-
if (!res.ok()) {
204-
std::string response = "error|";
205-
response.append(res.status().message());
225+
auto command = handlers.find(commandParts[0]);
226+
if (command == handlers.end()) {
227+
std::string response = "error|bad_command";
206228
mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
207229
return;
208230
}
209231

210-
Args args = *res;
211-
212-
auto cmdIter = handlers.find(commandParts[0]);
213-
if (cmdIter == handlers.end()) {
214-
std::string response = "error|bad_command";
232+
auto res = command->second.second(commandParts);
233+
if (!res.ok()) {
234+
std::string response = "error|";
235+
response.append(res.status().message());
215236
mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
216237
return;
217238
}
218239

219-
cmdIter->second(args);
240+
GolfServiceRequest req = *res;
241+
242+
command->second.first(req, c);
220243
}
221244

222245
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {

0 commit comments

Comments
 (0)