From cade908ed27f12f476258c20b038f17e8cdcd66f Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Fri, 5 Jun 2020 23:08:59 +0100 Subject: [PATCH] Refactor APIs to take an /{accountid}/ prefix --- cmd/rove/main.go | 14 ++++----- pkg/rove/api.go | 39 +++++++++-------------- pkg/rove/integration_test.go | 37 +++++++--------------- pkg/server/routes.go | 59 ++++++++++++++++------------------- pkg/server/routes_test.go | 60 ++++++++++++++++++------------------ pkg/server/server.go | 4 ++- 6 files changed, 92 insertions(+), 121 deletions(-) diff --git a/cmd/rove/main.go b/cmd/rove/main.go index 086a2c9..2edb086 100644 --- a/cmd/rove/main.go +++ b/cmd/rove/main.go @@ -123,8 +123,8 @@ func main() { } case "spawn": verifyId(config) - d := rove.SpawnData{Id: config.Account} - if response, err := server.Spawn(d); err != nil { + d := rove.SpawnData{} + if response, err := server.Spawn(config.Account, d); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -138,11 +138,11 @@ func main() { case "command": verifyId(config) - d := rove.CommandData{Id: config.Account} + d := rove.CommandData{} // TODO: Send real commands in - if response, err := server.Command(d); err != nil { + if response, err := server.Command(config.Account, d); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -157,8 +157,7 @@ func main() { case "radar": verifyId(config) - d := rove.RadarData{Id: config.Account} - if response, err := server.Radar(d); err != nil { + if response, err := server.Radar(config.Account); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -173,8 +172,7 @@ func main() { case "rover": verifyId(config) - d := rove.RoverData{Id: config.Account} - if response, err := server.Rover(d); err != nil { + if response, err := server.Rover(config.Account); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/pkg/rove/api.go b/pkg/rove/api.go index 4d3512e..7ad5062 100644 --- a/pkg/rove/api.go +++ b/pkg/rove/api.go @@ -1,6 +1,8 @@ package rove import ( + "path" + "github.com/mdiluz/rove/pkg/game" ) @@ -43,18 +45,18 @@ type RegisterResponse struct { } // ============================== -// API: /spawn method: POST +// API: /{account}/spawn method: POST // Spawn spawns the rover for an account // Responds with the position of said rover -func (s Server) Spawn(d SpawnData) (r SpawnResponse, err error) { - err = s.POST("spawn", d, &r) +func (s Server) Spawn(account string, d SpawnData) (r SpawnResponse, err error) { + err = s.POST(path.Join(account, "spawn"), d, &r) return } // SpawnData is the data to be sent for the spawn command type SpawnData struct { - Id string `json:"id"` + // Empty for now, reserved for data } // SpawnResponse is the data to respond with on a spawn command @@ -67,17 +69,16 @@ type SpawnResponse struct { } // ============================== -// API: /command method: POST +// API: /{account}/command method: POST // Command issues a set of commands from the user -func (s Server) Command(d CommandData) (r CommandResponse, err error) { - err = s.POST("command", d, &r) +func (s Server) Command(account string, d CommandData) (r CommandResponse, err error) { + err = s.POST(path.Join(account, "command"), d, &r) return } // CommandData is a set of commands to execute in order type CommandData struct { - Id string `json:"id"` Commands []Command `json:"commands"` } @@ -104,19 +105,14 @@ type Command struct { } // ================ -// API: /radar POST +// API: /{account}/radar method: GET // Radar queries the current radar for the user -func (s Server) Radar(d RadarData) (r RadarResponse, err error) { - err = s.POST("radar", d, &r) +func (s Server) Radar(account string) (r RadarResponse, err error) { + err = s.GET(path.Join(account, "radar"), &r) return } -// RadarData describes the input data to request an accounts current radar -type RadarData struct { - Id string `json:"id"` -} - // RadarResponse describes the response to a /radar call type RadarResponse struct { Success bool `json:"success"` @@ -127,19 +123,14 @@ type RadarResponse struct { } // ================ -// API: /rover POST +// API: /{account}/rover method: GET // Rover queries the current state of the rover -func (s Server) Rover(d RoverData) (r RoverResponse, err error) { - err = s.POST("rover", d, &r) +func (s Server) Rover(account string) (r RoverResponse, err error) { + err = s.GET(path.Join(account, "rover"), &r) return } -// RoverData describes the input data to request rover status -type RoverData struct { - Id string `json:"id"` -} - // RoverResponse includes information about the rover in question type RoverResponse struct { Success bool `json:"success"` diff --git a/pkg/rove/integration_test.go b/pkg/rove/integration_test.go index 465f3f0..efb11ec 100644 --- a/pkg/rove/integration_test.go +++ b/pkg/rove/integration_test.go @@ -47,10 +47,8 @@ func TestServer_Spawn(t *testing.T) { assert.True(t, r1.Success) assert.NotZero(t, len(r1.Id)) - s := SpawnData{ - Id: r1.Id, - } - r2, err := server.Spawn(s) + s := SpawnData{} + r2, err := server.Spawn(r1.Id, s) assert.NoError(t, err) assert.True(t, r2.Success) } @@ -64,15 +62,12 @@ func TestServer_Command(t *testing.T) { assert.True(t, r1.Success) assert.NotZero(t, len(r1.Id)) - s := SpawnData{ - Id: r1.Id, - } - r2, err := server.Spawn(s) + s := SpawnData{} + r2, err := server.Spawn(r1.Id, s) assert.NoError(t, err) assert.True(t, r2.Success) c := CommandData{ - Id: r1.Id, Commands: []Command{ { Command: CommandMove, @@ -81,7 +76,7 @@ func TestServer_Command(t *testing.T) { }, }, } - r3, err := server.Command(c) + r3, err := server.Command(r1.Id, c) assert.NoError(t, err) assert.True(t, r3.Success) } @@ -95,17 +90,12 @@ func TestServer_Radar(t *testing.T) { assert.True(t, r1.Success) assert.NotZero(t, len(r1.Id)) - s := SpawnData{ - Id: r1.Id, - } - r2, err := server.Spawn(s) + s := SpawnData{} + r2, err := server.Spawn(r1.Id, s) assert.NoError(t, err) assert.True(t, r2.Success) - r := RadarData{ - Id: r1.Id, - } - r3, err := server.Radar(r) + r3, err := server.Radar(r1.Id) assert.NoError(t, err) assert.True(t, r3.Success) } @@ -119,17 +109,12 @@ func TestServer_Rover(t *testing.T) { assert.True(t, r1.Success) assert.NotZero(t, len(r1.Id)) - s := SpawnData{ - Id: r1.Id, - } - r2, err := server.Spawn(s) + s := SpawnData{} + r2, err := server.Spawn(r1.Id, s) assert.NoError(t, err) assert.True(t, r2.Success) - r := RoverData{ - Id: r1.Id, - } - r3, err := server.Rover(r) + r3, err := server.Rover(r1.Id) assert.NoError(t, err) assert.True(t, r3.Success) } diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 41486d0..9869f95 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -12,7 +12,7 @@ import ( ) // Handler describes a function that handles any incoming request and can respond -type Handler func(*Server, io.ReadCloser, io.Writer) (interface{}, error) +type Handler func(*Server, map[string]string, io.ReadCloser, io.Writer) (interface{}, error) // Route defines the information for a single path->function route type Route struct { @@ -34,29 +34,29 @@ var Routes = []Route{ handler: HandleRegister, }, { - path: "/spawn", + path: "/{account}/spawn", method: http.MethodPost, handler: HandleSpawn, }, { - path: "/command", + path: "/{account}/command", method: http.MethodPost, handler: HandleCommand, }, { - path: "/radar", - method: http.MethodPost, + path: "/{account}/radar", + method: http.MethodGet, handler: HandleRadar, }, { - path: "/rover", - method: http.MethodPost, + path: "/{account}/rover", + method: http.MethodGet, handler: HandleRover, }, } // HandleStatus handles the /status request -func HandleStatus(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleStatus(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { // Simply return the current server status return rove.StatusResponse{ @@ -66,7 +66,7 @@ func HandleStatus(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) } // HandleRegister handles /register endpoint -func HandleRegister(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { var response = rove.RegisterResponse{ Success: false, } @@ -93,21 +93,23 @@ func HandleRegister(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error } // HandleSpawn will spawn the player entity for the associated account -func HandleSpawn(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleSpawn(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { var response = rove.SpawnResponse{ Success: false, } + id := vars["account"] + // Decode the spawn info, verify it and spawn the rover for this account var data rove.SpawnData if err := json.NewDecoder(b).Decode(&data); err != nil { fmt.Printf("Failed to decode json: %s\n", err) response.Error = err.Error() - } else if len(data.Id) == 0 { + } else if len(id) == 0 { response.Error = "No account ID provided" - } else if id, err := uuid.Parse(data.Id); err != nil { + } else if id, err := uuid.Parse(id); err != nil { response.Error = "Provided account ID was invalid" } else if pos, _, err := s.SpawnRoverForAccount(id); err != nil { @@ -122,21 +124,23 @@ func HandleSpawn(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { } // HandleSpawn will spawn the player entity for the associated account -func HandleCommand(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleCommand(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { var response = rove.CommandResponse{ Success: false, } + id := vars["account"] + // Decode the commands, verify them and the account, and execute the commands var data rove.CommandData if err := json.NewDecoder(b).Decode(&data); err != nil { fmt.Printf("Failed to decode json: %s\n", err) response.Error = err.Error() - } else if len(data.Id) == 0 { + } else if len(id) == 0 { response.Error = "No account ID provided" - } else if id, err := uuid.Parse(data.Id); err != nil { + } else if id, err := uuid.Parse(id); err != nil { response.Error = fmt.Sprintf("Provided account ID was invalid: %s", err) } else if inst, err := s.accountant.GetRover(id); err != nil { @@ -156,21 +160,16 @@ func HandleCommand(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) } // HandleRadar handles the radar request -func HandleRadar(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { var response = rove.RadarResponse{ Success: false, } - // Decode the radar message, verify it, and respond with the radar info - var data rove.CommandData - if err := json.NewDecoder(b).Decode(&data); err != nil { - fmt.Printf("Failed to decode json: %s\n", err) - response.Error = err.Error() - - } else if len(data.Id) == 0 { + id := vars["account"] + if len(id) == 0 { response.Error = "No account ID provided" - } else if id, err := uuid.Parse(data.Id); err != nil { + } else if id, err := uuid.Parse(id); err != nil { response.Error = fmt.Sprintf("Provided account ID was invalid: %s", err) } else if inst, err := s.accountant.GetRover(id); err != nil { @@ -188,20 +187,16 @@ func HandleRadar(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { } // HandleRover handles the rover request -func HandleRover(s *Server, b io.ReadCloser, w io.Writer) (interface{}, error) { +func HandleRover(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer) (interface{}, error) { var response = rove.RoverResponse{ Success: false, } - var data rove.RoverData - if err := json.NewDecoder(b).Decode(&data); err != nil { - fmt.Printf("Failed to decode json: %s\n", err) - response.Error = err.Error() - - } else if len(data.Id) == 0 { + id := vars["account"] + if len(id) == 0 { response.Error = "No account ID provided" - } else if id, err := uuid.Parse(data.Id); err != nil { + } else if id, err := uuid.Parse(id); err != nil { response.Error = fmt.Sprintf("Provided account ID was invalid: %s", err) } else if inst, err := s.accountant.GetRover(id); err != nil { diff --git a/pkg/server/routes_test.go b/pkg/server/routes_test.go index 7b9a789..cade48f 100644 --- a/pkg/server/routes_test.go +++ b/pkg/server/routes_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "path" "testing" "github.com/mdiluz/rove/pkg/game" @@ -13,11 +14,14 @@ import ( ) func TestHandleStatus(t *testing.T) { + request, _ := http.NewRequest(http.MethodGet, "/status", nil) response := httptest.NewRecorder() s := NewServer() - s.wrapHandler(http.MethodGet, HandleStatus)(response, request) + s.Initialise() + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.StatusResponse json.NewDecoder(response.Body).Decode(&status) @@ -42,7 +46,9 @@ func TestHandleRegister(t *testing.T) { response := httptest.NewRecorder() s := NewServer() - s.wrapHandler(http.MethodPost, HandleRegister)(response, request) + s.Initialise() + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.RegisterResponse json.NewDecoder(response.Body).Decode(&status) @@ -54,28 +60,32 @@ func TestHandleRegister(t *testing.T) { func TestHandleSpawn(t *testing.T) { s := NewServer() + s.Initialise() a, err := s.accountant.RegisterAccount("test") assert.NoError(t, err, "Error registering account") - data := rove.SpawnData{Id: a.Id.String()} + data := rove.SpawnData{} b, err := json.Marshal(data) assert.NoError(t, err, "Error marshalling data") - request, _ := http.NewRequest(http.MethodPost, "/spawn", bytes.NewReader(b)) + request, _ := http.NewRequest(http.MethodPost, path.Join("/", a.Id.String(), "/spawn"), bytes.NewReader(b)) response := httptest.NewRecorder() - s.wrapHandler(http.MethodPost, HandleSpawn)(response, request) + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.SpawnResponse json.NewDecoder(response.Body).Decode(&status) + assert.Equal(t, http.StatusOK, response.Code) if status.Success != true { - t.Errorf("got false for /spawn") + t.Errorf("got false for /spawn: %s", status.Error) } } func TestHandleCommand(t *testing.T) { s := NewServer() + s.Initialise() a, err := s.accountant.RegisterAccount("test") assert.NoError(t, err, "Error registering account") @@ -86,7 +96,6 @@ func TestHandleCommand(t *testing.T) { assert.NoError(t, err, "Couldn't get rover position") data := rove.CommandData{ - Id: a.Id.String(), Commands: []rove.Command{ { Command: rove.CommandMove, @@ -99,16 +108,17 @@ func TestHandleCommand(t *testing.T) { b, err := json.Marshal(data) assert.NoError(t, err, "Error marshalling data") - request, _ := http.NewRequest(http.MethodPost, "/command", bytes.NewReader(b)) + request, _ := http.NewRequest(http.MethodPost, path.Join("/", a.Id.String(), "/command"), bytes.NewReader(b)) response := httptest.NewRecorder() - s.wrapHandler(http.MethodPost, HandleCommand)(response, request) + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.CommandResponse json.NewDecoder(response.Body).Decode(&status) if status.Success != true { - t.Errorf("got false for /command") + t.Errorf("got false for /command: %s", status.Error) } attrib, err := s.world.RoverAttributes(inst) @@ -122,29 +132,24 @@ func TestHandleCommand(t *testing.T) { func TestHandleRadar(t *testing.T) { s := NewServer() + s.Initialise() a, err := s.accountant.RegisterAccount("test") assert.NoError(t, err, "Error registering account") // Spawn the rover rover for the account _, _, err = s.SpawnRoverForAccount(a.Id) - data := rove.RadarData{ - Id: a.Id.String(), - } - - b, err := json.Marshal(data) - assert.NoError(t, err, "Error marshalling data") - - request, _ := http.NewRequest(http.MethodPost, "/radar", bytes.NewReader(b)) + request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/radar"), nil) response := httptest.NewRecorder() - s.wrapHandler(http.MethodPost, HandleRadar)(response, request) + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.RadarResponse json.NewDecoder(response.Body).Decode(&status) if status.Success != true { - t.Errorf("got false for /radar") + t.Errorf("got false for /radar: %s", status.Error) } // TODO: Verify the radar information @@ -152,29 +157,24 @@ func TestHandleRadar(t *testing.T) { func TestHandleRover(t *testing.T) { s := NewServer() + s.Initialise() a, err := s.accountant.RegisterAccount("test") assert.NoError(t, err, "Error registering account") // Spawn the rover rover for the account _, _, err = s.SpawnRoverForAccount(a.Id) - data := rove.RoverData{ - Id: a.Id.String(), - } - - b, err := json.Marshal(data) - assert.NoError(t, err, "Error marshalling data") - - request, _ := http.NewRequest(http.MethodPost, "/rover", bytes.NewReader(b)) + request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/rover"), nil) response := httptest.NewRecorder() - s.wrapHandler(http.MethodPost, HandleRover)(response, request) + s.router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) var status rove.RoverResponse json.NewDecoder(response.Body).Decode(&status) if status.Success != true { - t.Errorf("got false for /rover") + t.Errorf("got false for /rover: %s", status.Error) } // TODO: Verify the radar information diff --git a/pkg/server/server.go b/pkg/server/server.go index 00ec75a..3a84b2a 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -143,11 +143,13 @@ func (s *Server) wrapHandler(method string, handler Handler) func(w http.Respons // Log the request fmt.Printf("%s\t%s\n", r.Method, r.RequestURI) + vars := mux.Vars(r) + // Verify the method, call the handler, and encode the return if r.Method != method { w.WriteHeader(http.StatusMethodNotAllowed) - } else if val, err := handler(s, r.Body, w); err != nil { + } else if val, err := handler(s, vars, r.Body, w); err != nil { fmt.Printf("Failed to handle http request: %s", err) w.WriteHeader(http.StatusInternalServerError)