blob: 0a28ca0be2b75188d10723fdaa1a6183e4dbf398 [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
4 "fmt"
5 "io"
6 "net/http"
7
Philipp Schrader8747f1b2022-02-23 23:56:22 -08008 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08009 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080010 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
11 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080012 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
13 _ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
14 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
15 flatbuffers "github.com/google/flatbuffers/go"
16)
17
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080018type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
19type RequestAllMatches = request_all_matches.RequestAllMatches
20type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
21
Philipp Schrader8747f1b2022-02-23 23:56:22 -080022// The interface we expect the database abstraction to conform to.
23// We use an interface here because it makes unit testing easier.
24type Database interface {
25 AddToMatch(db.Match) error
26 AddToStats(db.Stats) error
27 ReturnMatches() ([]db.Match, error)
28 ReturnStats() ([]db.Stats, error)
29 QueryMatches(int) ([]db.Match, error)
30 QueryStats(int) ([]db.Stats, error)
31}
32
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080033// Handles unknown requests. Just returns a 404.
34func unknown(w http.ResponseWriter, req *http.Request) {
35 w.WriteHeader(http.StatusNotFound)
36}
37
38func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
39 builder := flatbuffers.NewBuilder(1024)
40 builder.Finish((&error_response.ErrorResponseT{
41 ErrorMessage: errorMessage,
42 }).Pack(builder))
43 w.WriteHeader(statusCode)
44 w.Write(builder.FinishedBytes())
45}
46
47func respondNotImplemented(w http.ResponseWriter) {
48 respondWithError(w, http.StatusNotImplemented, "")
49}
50
51// TODO(phil): Can we turn this into a generic?
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080052func parseSubmitDataScouting(w http.ResponseWriter, buf []byte) (*SubmitDataScouting, bool) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080053 success := true
54 defer func() {
55 if r := recover(); r != nil {
56 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
57 success = false
58 }
59 }()
60 result := submit_data_scouting.GetRootAsSubmitDataScouting(buf, 0)
61 return result, success
62}
63
64// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -080065type submitDataScoutingHandler struct {
66 db Database
67}
68
69func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080070 requestBytes, err := io.ReadAll(req.Body)
71 if err != nil {
72 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
73 return
74 }
75
76 _, success := parseSubmitDataScouting(w, requestBytes)
77 if !success {
78 return
79 }
80
81 // TODO(phil): Actually handle the request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -080082 // We have access to the database via "handler.db" here. For example:
83 // stats := handler.db.ReturnStats()
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080084
85 respondNotImplemented(w)
86}
87
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080088// TODO(phil): Can we turn this into a generic?
89func parseRequestAllMatches(w http.ResponseWriter, buf []byte) (*RequestAllMatches, bool) {
90 success := true
91 defer func() {
92 if r := recover(); r != nil {
93 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
94 success = false
95 }
96 }()
97 result := request_all_matches.GetRootAsRequestAllMatches(buf, 0)
98 return result, success
99}
100
101// Handles a RequestAllMaches request.
102type requestAllMatchesHandler struct {
103 db Database
104}
105
106func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
107 requestBytes, err := io.ReadAll(req.Body)
108 if err != nil {
109 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
110 return
111 }
112
113 _, success := parseRequestAllMatches(w, requestBytes)
114 if !success {
115 return
116 }
117
118 matches, err := handler.db.ReturnMatches()
119 if err != nil {
120 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
121 }
122
123 var response RequestAllMatchesResponseT
124 for _, match := range matches {
125 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
126 MatchNumber: match.MatchNumber,
127 Round: match.Round,
128 CompLevel: match.CompLevel,
129 R1: match.R1,
130 R2: match.R2,
131 R3: match.R3,
132 B1: match.B1,
133 B2: match.B2,
134 B3: match.B3,
135 })
136 }
137
138 builder := flatbuffers.NewBuilder(50 * 1024)
139 builder.Finish((&response).Pack(builder))
140 w.Write(builder.FinishedBytes())
141}
142
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800143func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800144 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800145 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800146 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800147}