Refactor APIs to take an /{accountid}/ prefix

This commit is contained in:
Marc Di Luzio 2020-06-05 23:08:59 +01:00
parent 9ae1f50f46
commit cade908ed2
6 changed files with 92 additions and 121 deletions

View file

@ -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)

View file

@ -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"`

View file

@ -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)
}

View file

@ -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 {

View file

@ -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

View file

@ -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)