Skip to content

Commit

Permalink
Merge pull request #1 from slackhq/go-pre-interview-mac
Browse files Browse the repository at this point in the history
Golang pre interview code v1
  • Loading branch information
alelizalde authored Apr 25, 2022
2 parents 36289d9 + 38499d0 commit 889a198
Show file tree
Hide file tree
Showing 21 changed files with 990 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:

- name: Run 🚀
working-directory: ./go
run: ./run_test.sh
run: ./setup.sh
3 changes: 3 additions & 0 deletions go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
main
datastore.db
server.log
62 changes: 62 additions & 0 deletions go/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package api

import (
"context"
"fmt"
"log"

"go-pre-interview/model"
"go-pre-interview/server"
)

// API implements the server dispatch interface
type API struct {
datastore *model.Datastore
}

// Init bootstraps the API
func Init(datastore *model.Datastore) *API {
return &API{datastore}
}

type handlerFunc = func() error

// Dispatch executes a command in the given session
func (api *API) Dispatch(ctx context.Context, requestType string, requestID int, payload []byte) {

session := ctx.Value(server.SessionKey).(Session)
if session == nil {
panic("no session in context")
}
err := api.dispatch(session, requestType, requestID, payload)
if err != nil {
session.Send(NewErrorResponse(requestID, err.Error()))
}
}

func (api *API) dispatch(session Session, requestType string, requestID int, payload []byte) error {

log.Printf("got api request %s %s", requestType, payload)

dispatchTable := map[string]handlerFunc{
"test.create": func() error { return api.CreateHandler(session, requestID, payload) },
"test.info": func() error { return api.infoHandler(session, requestID, payload) },
"test.broadcast": func() error { return api.broadcastHandler(session, requestID, payload) },
}

handler, found := dispatchTable[requestType]
if !found {
return fmt.Errorf("unimplemented API method %s", requestType)
}

switch requestType {
case "test.create":
break
default:
if session.ID() == 0 {
return errCreatingTest
}
}

return handler()
}
31 changes: 31 additions & 0 deletions go/api/error_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package api

import "encoding/json"

// Response is sent for all API success responses without additional
// data and for all errors
type Response struct {
OK bool `json:"ok"`
ReplyTo int `json:"reply_to"`
Error string `json:"error,omitempty"`
}

// NewOKResponse creates a JSON encoded ok response
func NewOKResponse(requestID int) []byte {
okResponse := Response{OK: true, ReplyTo: requestID}
data, err := json.Marshal(okResponse)
if err != nil {
panic("response unable to marshal to json")
}
return data
}

// NewErrorResponse creates a JSON encoded error response wrapping the given message
func NewErrorResponse(requestID int, error string) []byte {
errResponse := Response{OK: false, ReplyTo: requestID, Error: error}
data, err := json.Marshal(errResponse)
if err != nil {
panic("response unable to marshal to json")
}
return data
}
10 changes: 10 additions & 0 deletions go/api/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package api

import "errors"

var (
errInvalidRequest = errors.New("invalid_request")
errCreatingTest = errors.New("error_creating_test")
errMissingID = errors.New("missing_id")
errTestNotFound = errors.New("test_not_found")
)
19 changes: 19 additions & 0 deletions go/api/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package api

// Session wraps a session
type Session interface {
// ID returns the id for the current session
ID() int

// SetID sets the id in the current session
SetID(id int)

// Send a reply on the current connection
Send(message []byte)

// Subscribe the given ID
Subscribe(sessionid, id int)

// Broadcast a message.
Broadcast(id int, message []byte) []int
}
118 changes: 118 additions & 0 deletions go/api/test_table_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package api

import (
"encoding/json"

"go-pre-interview/model"
)

type apiResponse struct {
OK bool `json:"ok"`
ReplyTO int `json:"reply_to"`
TestTable *model.TestTable `json:"test"`
}

type CreateRequest struct {
Name string `json:"name"`
RandomString string `json:"random_string"`
}

type broadcastRequest struct {
ID int `json:"test_id"`
}

type Message struct {
Type string `json:"type"`
RequestId int `json:"requestId"`
Test int `json:"test"`
}

func (api *API) CreateHandler(session Session, requestID int, payload []byte) error {
var request CreateRequest
err := json.Unmarshal(payload, &request)
if err != nil {
return err
}

test := api.datastore.TestTableStore.Create(request.Name, request.RandomString)

session.SetID(test.ID)

response := apiResponse{true, requestID, test}
data, err := json.Marshal(response)
if err != nil {
return err
}
session.Send(data)

return nil
}

type infoRequest struct {
TestId int `json:"test_id"`
}

func (api *API) infoHandler(session Session, requestID int, payload []byte) error {
var request infoRequest
err := json.Unmarshal(payload, &request)
if err != nil {
return errInvalidRequest
}

if request.TestId == 0 {
return errMissingID
}

test := api.datastore.TestTableStore.GetByID(request.TestId)
if test == nil {
return errTestNotFound
}

response := apiResponse{true, requestID, test}
data, err := json.Marshal(response)
if err != nil {
return err
}
session.Send(data)

return nil
}

func newMessage(sessionId, testId int) []byte {
msg := Message{"message", sessionId, testId}
data, err := json.Marshal(msg)
if err != nil {
panic("unable to marshal json")
}
return data
}

func (api *API) broadcastHandler(session Session, requestID int, payload []byte) error {
var request broadcastRequest
err := json.Unmarshal(payload, &request)
if err != nil {
return errInvalidRequest
}

if requestID == 0 {
return errMissingID
}

test := api.datastore.TestTableStore.GetByID(request.ID)
if test == nil {
return errTestNotFound
}

session.Subscribe(request.ID, session.ID())
session.Broadcast(request.ID, newMessage(session.ID(), request.ID))

response := apiResponse{OK: true, ReplyTO: requestID, TestTable: test}
data, err := json.Marshal(response)
if err != nil {
return err
}

session.Send(data)

return nil
}
Loading

0 comments on commit 889a198

Please sign in to comment.