blob: 21f0f05cebe2b503c70ef1107f56e20f3d749fbf [file] [log] [blame]
Philipp Schraderd9096a32022-02-24 17:53:09 -08001package debug
2
3import (
4 "bytes"
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07005 "encoding/base64"
Philipp Schraderd9096a32022-02-24 17:53:09 -08006 "errors"
7 "fmt"
8 "io"
9 "log"
10 "net/http"
11
12 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schraderd3fac192022-03-02 20:35:46 -080013 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080014 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070017 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Philipp Schraderd9096a32022-02-24 17:53:09 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070019 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Philipp Schrader86bf2b92022-03-30 20:57:30 -070020 flatbuffers "github.com/google/flatbuffers/go"
Philipp Schraderd9096a32022-02-24 17:53:09 -080021)
22
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070023// The username to submit the various requests as.
24const DefaultUsername = "debug_cli"
25
Philipp Schraderd9096a32022-02-24 17:53:09 -080026// A struct that can be used as an `error`. It contains information about the
27// why the server was unhappy and what the corresponding request was.
28type ResponseError struct {
29 Url string
30 StatusCode int
31 ErrorResponse *error_response.ErrorResponse
32}
33
34// Required to implement the `error` interface.
35func (err *ResponseError) Error() string {
36 return fmt.Sprintf(
37 "%s returned %d %s: %s", err.Url, err.StatusCode,
38 http.StatusText(err.StatusCode), err.ErrorResponse.ErrorMessage())
39}
40
41// Parse an `ErrorResponse` message that the server sent back. This happens
42// whenever the status code is something other than 200. If the message is
43// successfully parsed, it's turned into a `ResponseError` which implements the
44// `error` interface.
45func parseErrorResponse(url string, statusCode int, responseBytes []byte) error {
46 getRootErrMessage := ""
47 defer func() {
48 if r := recover(); r != nil {
49 getRootErrMessage = fmt.Sprintf("%v", r)
50 }
51 }()
52 errorMessage := error_response.GetRootAsErrorResponse(responseBytes, 0)
53 if getRootErrMessage != "" {
54 return errors.New(fmt.Sprintf(
55 "Failed to parse response from %s with status %d %s (bytes %v) as ErrorResponse: %s",
56 url, statusCode, http.StatusText(statusCode), responseBytes, getRootErrMessage))
57 }
58
59 return &ResponseError{
60 Url: url,
61 StatusCode: statusCode,
62 ErrorResponse: errorMessage,
63 }
64}
65
66// Performs a POST request with the specified payload. The bytes that the
67// server responds with are returned.
68func performPost(url string, requestBytes []byte) ([]byte, error) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070069 req, err := http.NewRequest("POST", url, bytes.NewReader(requestBytes))
70 if err != nil {
71 log.Printf("Failed to create a new POST request to %s: %v", url, err)
72 return nil, err
73 }
74 req.Header.Add("Authorization", "Basic "+
75 base64.StdEncoding.EncodeToString([]byte(DefaultUsername+":")))
76
77 client := &http.Client{}
78 resp, err := client.Do(req)
Philipp Schraderd9096a32022-02-24 17:53:09 -080079 if err != nil {
80 log.Printf("Failed to send POST request to %s: %v", url, err)
81 return nil, err
82 }
83 responseBytes, err := io.ReadAll(resp.Body)
84 if err != nil {
85 log.Printf("Failed to parse response bytes from POST to %s: %v", url, err)
86 return nil, err
87 }
88 if resp.StatusCode != http.StatusOK {
89 return nil, parseErrorResponse(url, resp.StatusCode, responseBytes)
90 }
91 return responseBytes, nil
92}
93
Philipp Schrader86bf2b92022-03-30 20:57:30 -070094// Sends a message to the server and returns the deserialized response.
95// The first generic argument must be specified.
96func sendMessage[FbT interface{}, Fb interface{ UnPack() *FbT }](url string, requestBytes []byte, parser func([]byte, flatbuffers.UOffsetT) Fb) (*FbT, error) {
97 responseBytes, err := performPost(url, requestBytes)
98 if err != nil {
99 return nil, err
100 }
101 response := parser(responseBytes, 0)
102 return response.UnPack(), nil
103}
104
Philipp Schrader20440f82022-03-16 20:07:09 -0700105func SubmitDataScouting(server string, requestBytes []byte) (*submit_data_scouting_response.SubmitDataScoutingResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700106 return sendMessage[submit_data_scouting_response.SubmitDataScoutingResponseT](
107 server+"/requests/submit/data_scouting", requestBytes,
108 submit_data_scouting_response.GetRootAsSubmitDataScoutingResponse)
Philipp Schraderd9096a32022-02-24 17:53:09 -0800109}
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800110
Philipp Schrader20440f82022-03-16 20:07:09 -0700111func RequestAllMatches(server string, requestBytes []byte) (*request_all_matches_response.RequestAllMatchesResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700112 return sendMessage[request_all_matches_response.RequestAllMatchesResponseT](
113 server+"/requests/request/all_matches", requestBytes,
114 request_all_matches_response.GetRootAsRequestAllMatchesResponse)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800115}
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800116
Philipp Schrader20440f82022-03-16 20:07:09 -0700117func RequestMatchesForTeam(server string, requestBytes []byte) (*request_matches_for_team_response.RequestMatchesForTeamResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700118 return sendMessage[request_matches_for_team_response.RequestMatchesForTeamResponseT](
119 server+"/requests/request/matches_for_team", requestBytes,
120 request_matches_for_team_response.GetRootAsRequestMatchesForTeamResponse)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800121}
Philipp Schraderacf96232022-03-01 22:03:30 -0800122
Philipp Schrader20440f82022-03-16 20:07:09 -0700123func RequestDataScouting(server string, requestBytes []byte) (*request_data_scouting_response.RequestDataScoutingResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700124 return sendMessage[request_data_scouting_response.RequestDataScoutingResponseT](
125 server+"/requests/request/data_scouting", requestBytes,
126 request_data_scouting_response.GetRootAsRequestDataScoutingResponse)
Philipp Schraderacf96232022-03-01 22:03:30 -0800127}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800128
Philipp Schrader20440f82022-03-16 20:07:09 -0700129func RefreshMatchList(server string, requestBytes []byte) (*refresh_match_list_response.RefreshMatchListResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700130 return sendMessage[refresh_match_list_response.RefreshMatchListResponseT](
131 server+"/requests/refresh_match_list", requestBytes,
132 refresh_match_list_response.GetRootAsRefreshMatchListResponse)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800133}
Alex Perry81f96ba2022-03-13 18:26:19 -0700134
135func SubmitNotes(server string, requestBytes []byte) (*submit_notes_response.SubmitNotesResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700136 return sendMessage[submit_notes_response.SubmitNotesResponseT](
137 server+"/requests/submit/submit_notes", requestBytes,
138 submit_notes_response.GetRootAsSubmitNotesResponse)
Alex Perry81f96ba2022-03-13 18:26:19 -0700139}
140
141func RequestNotes(server string, requestBytes []byte) (*request_notes_for_team_response.RequestNotesForTeamResponseT, error) {
Philipp Schrader86bf2b92022-03-30 20:57:30 -0700142 return sendMessage[request_notes_for_team_response.RequestNotesForTeamResponseT](
143 server+"/requests/request/notes_for_team", requestBytes,
144 request_notes_for_team_response.GetRootAsRequestNotesForTeamResponse)
Alex Perry81f96ba2022-03-13 18:26:19 -0700145}