blob: 08cf67c86bba0312d1e7964b39468de33efba82e [file] [log] [blame]
package debug
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"net/http"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
flatbuffers "github.com/google/flatbuffers/go"
)
// The username to submit the various requests as.
const DefaultUsername = "debug_cli"
// A struct that can be used as an `error`. It contains information about the
// why the server was unhappy and what the corresponding request was.
type ResponseError struct {
Url string
StatusCode int
ErrorResponse *error_response.ErrorResponse
}
// Required to implement the `error` interface.
func (err *ResponseError) Error() string {
return fmt.Sprintf(
"%s returned %d %s: %s", err.Url, err.StatusCode,
http.StatusText(err.StatusCode), err.ErrorResponse.ErrorMessage())
}
// Parse an `ErrorResponse` message that the server sent back. This happens
// whenever the status code is something other than 200. If the message is
// successfully parsed, it's turned into a `ResponseError` which implements the
// `error` interface.
func parseErrorResponse(url string, statusCode int, responseBytes []byte) error {
getRootErrMessage := ""
defer func() {
if r := recover(); r != nil {
getRootErrMessage = fmt.Sprintf("%v", r)
}
}()
errorMessage := error_response.GetRootAsErrorResponse(responseBytes, 0)
if getRootErrMessage != "" {
return errors.New(fmt.Sprintf(
"Failed to parse response from %s with status %d %s (bytes %v) as ErrorResponse: %s",
url, statusCode, http.StatusText(statusCode), responseBytes, getRootErrMessage))
}
return &ResponseError{
Url: url,
StatusCode: statusCode,
ErrorResponse: errorMessage,
}
}
// Performs a POST request with the specified payload. The bytes that the
// server responds with are returned.
func performPost(url string, requestBytes []byte) ([]byte, error) {
req, err := http.NewRequest("POST", url, bytes.NewReader(requestBytes))
if err != nil {
log.Printf("Failed to create a new POST request to %s: %v", url, err)
return nil, err
}
req.Header.Add("Authorization", "Basic "+
base64.StdEncoding.EncodeToString([]byte(DefaultUsername+":")))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send POST request to %s: %v", url, err)
return nil, err
}
responseBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to parse response bytes from POST to %s: %v", url, err)
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, parseErrorResponse(url, resp.StatusCode, responseBytes)
}
return responseBytes, nil
}
// Sends a message to the server and returns the deserialized response.
// The first generic argument must be specified.
func sendMessage[FbT interface{}, Fb interface{ UnPack() *FbT }](url string, requestBytes []byte, parser func([]byte, flatbuffers.UOffsetT) Fb) (*FbT, error) {
responseBytes, err := performPost(url, requestBytes)
if err != nil {
return nil, err
}
response := parser(responseBytes, 0)
return response.UnPack(), nil
}
func SubmitDataScouting(server string, requestBytes []byte) (*submit_data_scouting_response.SubmitDataScoutingResponseT, error) {
return sendMessage[submit_data_scouting_response.SubmitDataScoutingResponseT](
server+"/requests/submit/data_scouting", requestBytes,
submit_data_scouting_response.GetRootAsSubmitDataScoutingResponse)
}
func RequestAllMatches(server string, requestBytes []byte) (*request_all_matches_response.RequestAllMatchesResponseT, error) {
return sendMessage[request_all_matches_response.RequestAllMatchesResponseT](
server+"/requests/request/all_matches", requestBytes,
request_all_matches_response.GetRootAsRequestAllMatchesResponse)
}
func RequestAllDriverRankings(server string, requestBytes []byte) (*request_all_driver_rankings_response.RequestAllDriverRankingsResponseT, error) {
return sendMessage[request_all_driver_rankings_response.RequestAllDriverRankingsResponseT](
server+"/requests/request/all_driver_rankings", requestBytes,
request_all_driver_rankings_response.GetRootAsRequestAllDriverRankingsResponse)
}
func RequestMatchesForTeam(server string, requestBytes []byte) (*request_matches_for_team_response.RequestMatchesForTeamResponseT, error) {
return sendMessage[request_matches_for_team_response.RequestMatchesForTeamResponseT](
server+"/requests/request/matches_for_team", requestBytes,
request_matches_for_team_response.GetRootAsRequestMatchesForTeamResponse)
}
func RequestDataScouting(server string, requestBytes []byte) (*request_data_scouting_response.RequestDataScoutingResponseT, error) {
return sendMessage[request_data_scouting_response.RequestDataScoutingResponseT](
server+"/requests/request/data_scouting", requestBytes,
request_data_scouting_response.GetRootAsRequestDataScoutingResponse)
}
func RefreshMatchList(server string, requestBytes []byte) (*refresh_match_list_response.RefreshMatchListResponseT, error) {
return sendMessage[refresh_match_list_response.RefreshMatchListResponseT](
server+"/requests/refresh_match_list", requestBytes,
refresh_match_list_response.GetRootAsRefreshMatchListResponse)
}
func SubmitNotes(server string, requestBytes []byte) (*submit_notes_response.SubmitNotesResponseT, error) {
return sendMessage[submit_notes_response.SubmitNotesResponseT](
server+"/requests/submit/submit_notes", requestBytes,
submit_notes_response.GetRootAsSubmitNotesResponse)
}
func RequestNotes(server string, requestBytes []byte) (*request_notes_for_team_response.RequestNotesForTeamResponseT, error) {
return sendMessage[request_notes_for_team_response.RequestNotesForTeamResponseT](
server+"/requests/request/notes_for_team", requestBytes,
request_notes_for_team_response.GetRootAsRequestNotesForTeamResponse)
}
func RequestAllNotes(server string, requestBytes []byte) (*request_all_notes_response.RequestAllNotesResponseT, error) {
return sendMessage[request_all_notes_response.RequestAllNotesResponseT](
server+"/requests/request/all_notes", requestBytes,
request_all_notes_response.GetRootAsRequestAllNotesResponse)
}
func RequestShiftSchedule(server string, requestBytes []byte) (*request_shift_schedule_response.RequestShiftScheduleResponseT, error) {
return sendMessage[request_shift_schedule_response.RequestShiftScheduleResponseT](
server+"/requests/request/shift_schedule", requestBytes,
request_shift_schedule_response.GetRootAsRequestShiftScheduleResponse)
}
func SubmitShiftSchedule(server string, requestBytes []byte) (*submit_shift_schedule_response.SubmitShiftScheduleResponseT, error) {
return sendMessage[submit_shift_schedule_response.SubmitShiftScheduleResponseT](
server+"/requests/submit/shift_schedule", requestBytes,
submit_shift_schedule_response.GetRootAsSubmitShiftScheduleResponse)
}
func SubmitDriverRanking(server string, requestBytes []byte) (*submit_driver_ranking_response.SubmitDriverRankingResponseT, error) {
return sendMessage[submit_driver_ranking_response.SubmitDriverRankingResponseT](
server+"/requests/submit/submit_driver_ranking", requestBytes,
submit_driver_ranking_response.GetRootAsSubmitDriverRankingResponse)
}