major refactoring of the api

This commit is contained in:
didier amyot committed 2015-03-28 20:53:28 -07:00
1 parent d4f7dc57fc
commit f0628e4682
38 files changed
+821 -367

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
package api
import (
"fmt"
"net/http"
)
func GetAllDoormen(w http.ResponseWriter, request *http.Request) {
fmt.Fprint(w, `{"doormen" :[`)
doormenIds := conn.GetAllDoormen()
buff := <-doormenIds
if buff == nil {
fmt.Fprint(w, "]}")
return
}
for doormanId := range doormenIds {
fmt.Fprint(w, getDoormenIdsAsJson(buff)+`,`)
buff = doormanId
}
fmt.Fprint(w, getDoormenIdsAsJson(buff), "]}")
}
+57
View File
@@ -0,0 +1,57 @@
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/didiercrunch/doormanserver/doormen"
"github.com/didiercrunch/doormanserver/inmemoryconnector"
)
func init() {
conn = inmemoryconnector.NewInMemoryDBConnector()
}
func TestGetAllDoormen(t *testing.T) {
conn = inmemoryconnector.NewInMemoryDBConnector()
conn.Save(doormen.QuickNewDoormanDefinition("foo", 0.25, 0.75))
conn.Save(doormen.QuickNewDoormanDefinition("bar", 0.5, 0.5))
req, err := http.NewRequest("GET", "http://bigtits.com/api/doormen", nil)
if err != nil {
t.Error(err)
return
}
w := httptest.NewRecorder()
GetAllDoormen(w, req)
d := json.NewDecoder(w.Body)
m := make(map[string][]*doormen.DoormanDefinition)
if err := d.Decode(&m); err != nil {
t.Error(err)
return
}
if len(m["doormen"]) != 2 {
t.Error()
}
}
func TestGetAllDoormenEmpty(t *testing.T) {
conn = inmemoryconnector.NewInMemoryDBConnector()
req, err := http.NewRequest("GET", "http://bigtits.com/api/doormen", nil)
if err != nil {
t.Error(err)
return
}
w := httptest.NewRecorder()
GetAllDoormen(w, req)
if s := w.Body.String(); s != `{"doormen" :[]}` {
t.Error(s)
}
}
+54
View File
@@ -0,0 +1,54 @@
package api
import (
"github.com/didiercrunch/doormanserver/simpleapi"
"net/http"
)
func Create() http.Handler {
serverSpecification := &simpleapi.Endpoint{
"/server",
"Get the server specification.",
"GET",
GetServerSpecification,
}
createDoorman := &simpleapi.Endpoint{
"/doormen",
"Create a new doorman.",
"POST",
CreateDoorman,
}
allDoorman := &simpleapi.Endpoint{
"/doormen",
"Get the list of all the public doormen.",
"GET",
GetAllDoormen,
}
doorman := &simpleapi.Endpoint{
"/doormen/{id}",
"Get the doorman.",
"GET",
GetDoorman,
}
updateDoorman := &simpleapi.Endpoint{
"/doormen/{id}",
"Update the doorman with new probabilities.",
"PUT",
UpdateDoorman,
}
api := simpleapi.New(
"/api",
serverSpecification,
createDoorman,
allDoorman,
doorman,
updateDoorman,
)
return api
}
+34
View File
@@ -0,0 +1,34 @@
package api
import (
"encoding/json"
"fmt"
"github.com/didiercrunch/doormanserver/doormen"
"gopkg.in/mgo.v2/bson"
"net/http"
)
func CreateDoorman(w http.ResponseWriter, request *http.Request) {
wdef := new(doormen.DoormanDefinition)
defer request.Body.Close()
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(wdef); err != nil {
w.WriteHeader(400)
fmt.Fprintln(w, "cannot create doorman definition \n", err)
return
}
wdef.Id = bson.NewObjectId()
if err := wdef.Validate(); err != nil {
w.WriteHeader(400)
fmt.Fprintln(w, "New doorman is invalid \n", err)
return
}
if err := CreateDoormanInDatabase(wdef); err != nil {
w.WriteHeader(500)
fmt.Fprintln(w, "Server error \n", err)
return
}
go publisher.Emit(wdef.Id, wdef)
w.Header().Set("location", "/api/doormen/"+wdef.Id.Hex())
w.WriteHeader(201)
}
+57
View File
@@ -0,0 +1,57 @@
package api
import (
"bytes"
"github.com/didiercrunch/doormanserver/inmemoryconnector"
"gopkg.in/mgo.v2/bson"
"net/http"
"net/http/httptest"
"path"
"testing"
)
func GetIdFromLocation(p string) bson.ObjectId {
return bson.ObjectIdHex(path.Base(p))
}
func TestCreateDoorman(t *testing.T) {
conn = inmemoryconnector.NewInMemoryDBConnector()
payload := `{"name":"name","values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.8}]}`
req, err := http.NewRequest(
"POST",
"http://bigtits.com/api/doormen",
bytes.NewBufferString(payload),
)
if err != nil {
t.Error(err)
return
}
w := httptest.NewRecorder()
CreateDoorman(w, req)
if w.Code != 201 {
t.Error("bad status code", w.Code)
} else if location := w.Header().Get("location"); location == "" {
t.Error("missing location header", location)
} else if dor, _ := conn.GetDoorman(GetIdFromLocation(location)); dor == nil {
t.Error("dorman not created")
}
}
func TestCreateDoormanWithBadInput(t *testing.T) {
conn = inmemoryconnector.NewInMemoryDBConnector()
payload := `{"name":"name","values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.2}]}`
req, err := http.NewRequest(
"POST",
"http://bigtits.com/api/doormen",
bytes.NewBufferString(payload),
)
if err != nil {
t.Error(err)
return
}
w := httptest.NewRecorder()
CreateDoorman(w, req)
if w.Code != 400 {
t.Error("bad status code", w.Code)
}
}
+52
View File
@@ -0,0 +1,52 @@
package api
import (
"errors"
"log"
"github.com/didiercrunch/doormanserver/doormen"
"github.com/didiercrunch/doormanserver/inmemoryconnector"
"github.com/didiercrunch/doormanserver/mongoconnector"
"github.com/didiercrunch/doormanserver/shared"
"gopkg.in/mgo.v2/bson"
)
type DBConnector interface {
Save(wab *doormen.DoormanDefinition) error
Update(wab *doormen.DoormanDefinition) error
Exists(wab *doormen.DoormanDefinition) bool
ExistsId(id bson.ObjectId) bool
GetAllDoormen() <-chan *doormen.DoormanId
GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error)
DeleteDoorman(id bson.ObjectId) error
}
var conn DBConnector
func initMongoDbConnector() {
var err error
if conn, err = mongoconnector.NewMongoDBConnector(); err != nil {
panic(err)
}
}
func init() {
if params := shared.GetParams(); params.UseMongoDb() {
initMongoDbConnector()
log.Println("using mongodb as database")
} else {
conn = inmemoryconnector.NewInMemoryDBConnector()
log.Println("using inmemory as database")
}
}
func CreateDoormanInDatabase(wab *doormen.DoormanDefinition) error {
if conn.Exists(wab) {
return errors.New("doormen already exists")
}
return conn.Save(wab)
}
func UpdateDoormanInDatabase(wab *doormen.DoormanDefinition) error {
return conn.Update(wab)
}
+21
View File
@@ -0,0 +1,21 @@
package api
import (
"testing"
"github.com/didiercrunch/doormanserver/doormen"
"github.com/didiercrunch/doormanserver/inmemoryconnector"
)
func TestCreateDoormanInDatabase(t *testing.T) {
wab := doormen.NewDoormanDefinition("some_name")
conn = inmemoryconnector.NewInMemoryDBConnector()
if err := CreateDoormanInDatabase(wab); err != nil {
t.Error(err)
}
if err := CreateDoormanInDatabase(wab); err == nil {
t.Error("should have an error here")
}
}
+23
View File
@@ -0,0 +1,23 @@
package api
import (
"encoding/json"
"net/http"
)
func GetDoorman(w http.ResponseWriter, r *http.Request) {
id := getDoormanIdFromRequest(w, r)
if id == "" {
Write404Error(w)
return
}
doorman, err := conn.GetDoorman(id)
if err != nil {
Write500Error(w, err)
} else if doorman == nil {
Write404Error(w)
} else {
encoder := json.NewEncoder(w)
encoder.Encode(doorman)
}
}
+29
View File
@@ -0,0 +1,29 @@
package api
import (
"mime"
"net/http"
)
func GetDormanState(w http.ResponseWriter, request *http.Request) {
id := getDoormanIdFromRequest(w, request)
if id == "" {
Write404Error(w)
return
}
doorman, err := conn.GetDoorman(id)
if err != nil {
Write500Error(w, err)
return
} else if doorman == nil {
Write404Error(w)
return
}
if payload, err := doorman.AsDoormanUpdatePayload(); err != nil {
Write500Error(w, err)
} else {
w.Header().Add("Content-Type", mime.TypeByExtension(".json"))
w.Write(payload)
}
}
+51
View File
@@ -0,0 +1,51 @@
package api
import (
"log"
"github.com/didiercrunch/doormanserver/logpublisher"
"github.com/didiercrunch/doormanserver/nanomsgpublisher"
"github.com/didiercrunch/doormanserver/nsqpublisher"
"github.com/didiercrunch/doormanserver/shared"
"gopkg.in/mgo.v2/bson"
)
type Publisher interface {
Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error
Init() error
}
var publisher Publisher
func initNSQPublisher() {
publisher = new(nsqpublisher.NsqPublisher)
if err := publisher.Init(); err != nil {
log.Panic(err)
return
}
}
func initNanoMsgPublisher() {
publisher = new(nanomsgpublisher.NanoMsgPublisher)
if err := publisher.Init(); err != nil {
log.Panic(err)
return
}
}
func initPublisher(params *shared.Params) {
if params.UseNSQ() {
initNSQPublisher()
log.Println("using nsq as message queue")
} else if params.UseNanomsg() {
initNanoMsgPublisher()
log.Println("using nanomsg as message queue")
} else {
publisher = &logpublisher.LogPublisher{log.Println}
log.Println("using stdout as message queue")
}
}
func init() {
initPublisher(shared.GetParams())
}
+7
View File
@@ -0,0 +1,7 @@
package api
import "testing"
func TestInitPublisher(t *testing.T) {
}
+19
View File
@@ -0,0 +1,19 @@
package api
import (
"encoding/json"
"github.com/didiercrunch/doormanserver/serverstat"
"github.com/didiercrunch/doormanserver/shared"
"log"
"mime"
"net/http"
)
func GetServerSpecification(w http.ResponseWriter, request *http.Request) {
s := serverstat.Get(shared.GetParams())
w.Header().Add("Content-Type", mime.TypeByExtension(".json"))
encoder := json.NewEncoder(w)
if err := encoder.Encode(s); err != nil {
log.Println(err)
}
}
+47
View File
@@ -0,0 +1,47 @@
package api
import (
"encoding/json"
"github.com/didiercrunch/doormanserver/doormen"
"net/http"
)
func UpdateDoorman(w http.ResponseWriter, request *http.Request) {
id := getDoormanIdFromRequest(w, request)
if id == "" || !conn.ExistsId(id) {
Write404Error(w)
return
}
wdef := new(doormen.DoormanDefinition)
defer request.Body.Close()
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(wdef); err != nil {
Write400Error(w, err)
return
}
wdef.Id = id
if err := wdef.Validate(); err != nil {
Write400Error(w, err)
return
}
originalDoorman, err := conn.GetDoorman(id)
if err != nil {
Write500Error(w, err)
return
}
if err := originalDoorman.CanBeUpdatedBy(wdef); err != nil {
Write400Error(w, err)
return
}
if err := UpdateDoormanInDatabase(wdef); err != nil {
Write500Error(w, err)
return
}
go publisher.Emit(wdef.Id, wdef)
w.WriteHeader(200)
}
+39
View File
@@ -0,0 +1,39 @@
package api
import (
"fmt"
"github.com/didiercrunch/doormanserver/doormen"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2/bson"
"log"
"net/http"
)
func getDoormenIdsAsJson(id *doormen.DoormanId) string {
return fmt.Sprintf(`{"name": "%v", "id": "%v", "url": "/api/doormen/%v"}`, id.Name, id.Id.Hex(), id.Id.Hex())
}
func getDoormanIdFromRequest(w http.ResponseWriter, request *http.Request) bson.ObjectId {
id := mux.Vars(request)["id"]
if !bson.IsObjectIdHex(id) {
return ""
} else {
return bson.ObjectIdHex(id)
}
}
func Write500Error(w http.ResponseWriter, err error) {
w.WriteHeader(500)
fmt.Fprint(w, "server error")
log.Println(err)
}
func Write404Error(w http.ResponseWriter) {
w.WriteHeader(404)
fmt.Fprint(w, "url not found")
}
func Write400Error(w http.ResponseWriter, err error) {
w.WriteHeader(400)
fmt.Fprint(w, err.Error())
}
+37
View File
@@ -0,0 +1,37 @@
package api
import (
"errors"
"net/http/httptest"
"testing"
)
func TestWrite404Error(t *testing.T) {
rw := httptest.NewRecorder()
Write404Error(rw)
if rw.Code != 404 {
t.Error()
}
}
func TestWrite500Error(t *testing.T) {
rw := httptest.NewRecorder()
Write500Error(rw, errors.New("blah"))
if rw.Code != 500 {
t.Error()
}
if string(rw.Body.Bytes()) != "server error" {
t.Error()
}
}
func TestWrite400Error(t *testing.T) {
rw := httptest.NewRecorder()
Write400Error(rw, errors.New("blah"))
if rw.Code != 400 {
t.Error()
}
if string(rw.Body.Bytes()) != "blah" {
t.Error()
}
}