Created
August 31, 2023 09:54
-
-
Save evanxg852000/4d0441736bd81f1d1bc5d4a817864ed2 to your computer and use it in GitHub Desktop.
Recurce Center pair programming task [Database server]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"log" | |
"net/http" | |
"sync" | |
"golang.org/x/exp/maps" | |
) | |
// error definitions | |
var ( | |
ioError = errors.New("IoError") | |
keyNotFound = errors.New("KeyNotFound") | |
) | |
// Store interface along with implementations | |
type Store interface { | |
Put(key, value string) error | |
Get(key string) (string, error) | |
} | |
type InMemoryStore struct { | |
data map[string]string | |
mu sync.RWMutex | |
} | |
func (memStore InMemoryStore) Put(key, value string) error { | |
memStore.mu.Lock() | |
defer memStore.mu.Unlock() | |
memStore.data[key] = value | |
return nil | |
} | |
func (memStore InMemoryStore) Get(key string) (string, error) { | |
memStore.mu.RLock() | |
defer memStore.mu.RUnlock() | |
value, ok := memStore.data[key] | |
if !ok { | |
return "", keyNotFound | |
} | |
return value, nil | |
} | |
func NewInMemoryStore() Store { | |
return InMemoryStore{ | |
data: make(map[string]string), | |
mu: sync.RWMutex{}, | |
} | |
} | |
// TODO: implement a file backed store | |
// Database server | |
type DbServer struct { | |
store Store | |
} | |
func NewDbServer(store Store) *DbServer { | |
return &DbServer{ | |
store: store, | |
} | |
} | |
func (db *DbServer) Set(writer http.ResponseWriter, req *http.Request) { | |
params := req.URL.Query() | |
if len(params) != 1 { | |
writeResponse(writer, http.StatusBadRequest, "error", "Expecting key/value parameters.") | |
return | |
} | |
key := maps.Keys(params)[0] | |
value := params[key][0] // we could forbid empty values | |
err := db.store.Put(key, value) | |
if errors.Is(err, ioError) { | |
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred") | |
return | |
} | |
writeResponse(writer, http.StatusOK, "message", "The key was successfully set.") | |
} | |
func (db *DbServer) Get(writer http.ResponseWriter, req *http.Request) { | |
keySet, ok := req.URL.Query()["key"] | |
if !ok || len(keySet) != 1 { | |
writeResponse(writer, http.StatusNotFound, "error", "Expecting one key parameter.") | |
return | |
} | |
key := keySet[0] | |
value, err := db.store.Get(key) | |
if errors.Is(err, ioError) { | |
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred.") | |
return | |
} | |
if errors.Is(err, keyNotFound) { | |
writeResponse(writer, http.StatusNotFound, "error", "The key was not found.") | |
return | |
} | |
writeResponse(writer, http.StatusOK, key, value) | |
} | |
func main() { | |
dbServer := NewDbServer(NewInMemoryStore()) | |
http.HandleFunc("/set", dbServer.Set) | |
http.HandleFunc("/get", dbServer.Get) | |
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"log" | |
"net/http" | |
"sync" | |
"golang.org/x/exp/maps" | |
) | |
// error definitions | |
var ( | |
ioError = errors.New("IoError") | |
keyNotFound = errors.New("KeyNotFound") | |
) | |
// Store interface along with implementations | |
type Store interface { | |
Put(key, value string) error | |
Get(key string) (string, error) | |
} | |
type InMemoryStore struct { | |
data map[string]string | |
mu sync.RWMutex | |
} | |
func (memStore InMemoryStore) Put(key, value string) error { | |
memStore.mu.Lock() | |
defer memStore.mu.Unlock() | |
memStore.data[key] = value | |
return nil | |
} | |
func (memStore InMemoryStore) Get(key string) (string, error) { | |
memStore.mu.RLock() | |
defer memStore.mu.RUnlock() | |
value, ok := memStore.data[key] | |
if !ok { | |
return "", keyNotFound | |
} | |
return value, nil | |
} | |
func NewInMemoryStore() Store { | |
return InMemoryStore{ | |
data: make(map[string]string), | |
mu: sync.RWMutex{}, | |
} | |
} | |
// TODO: implement a file backed store | |
// Database server | |
type DbServer struct { | |
store Store | |
} | |
func NewDbServer(store Store) *DbServer { | |
return &DbServer{ | |
store: store, | |
} | |
} | |
func (db *DbServer) Set(writer http.ResponseWriter, req *http.Request) { | |
params := req.URL.Query() | |
if len(params) != 1 { | |
writeResponse(writer, http.StatusBadRequest, "error", "Expecting key/value parameters.") | |
return | |
} | |
key := maps.Keys(params)[0] | |
value := params[key][0] // we could forbid empty values | |
err := db.store.Put(key, value) | |
if errors.Is(err, ioError) { | |
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred") | |
return | |
} | |
writeResponse(writer, http.StatusOK, "message", "The key was successfully set.") | |
} | |
func (db *DbServer) Get(writer http.ResponseWriter, req *http.Request) { | |
keySet, ok := req.URL.Query()["key"] | |
if !ok || len(keySet) != 1 { | |
writeResponse(writer, http.StatusNotFound, "error", "Expecting one key parameter.") | |
return | |
} | |
key := keySet[0] | |
value, err := db.store.Get(key) | |
if errors.Is(err, ioError) { | |
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred.") | |
return | |
} | |
if errors.Is(err, keyNotFound) { | |
writeResponse(writer, http.StatusNotFound, "error", "The key was not found.") | |
return | |
} | |
writeResponse(writer, http.StatusOK, key, value) | |
} | |
func main() { | |
dbServer := NewDbServer(NewInMemoryStore()) | |
http.HandleFunc("/set", dbServer.Set) | |
http.HandleFunc("/get", dbServer.Get) | |
fmt.Println("Server running at http://localhost:4000") | |
http.ListenAndServe(":4000", nil) | |
} | |
// helpers | |
func writeResponse(writer http.ResponseWriter, statusCode int, key, value string) { | |
writer.WriteHeader(statusCode) | |
writer.Header().Set("Content-Type", "application/json") | |
resp := map[string]string{key: value} | |
data, err := json.Marshal(resp) | |
if err != nil { | |
log.Fatalf("Marshaling error. Err: %s", err) | |
} | |
writer.Write(data) | |
} | |
fmt.Println("Server running at http://localhost:4000") | |
http.ListenAndServe(":4000", nil) | |
} | |
// helpers | |
func writeResponse(writer http.ResponseWriter, statusCode int, key, value string) { | |
writer.WriteHeader(statusCode) | |
writer.Header().Set("Content-Type", "application/json") | |
resp := map[string]string{key: value} | |
data, err := json.Marshal(resp) | |
if err != nil { | |
log.Fatalf("Marshaling error. Err: %s", err) | |
} | |
writer.Write(data) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment