Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from peteraba/feature-enable-various-http-methods
Browse files Browse the repository at this point in the history
Enable various HTTP methods
  • Loading branch information
gernest authored Apr 8, 2018
2 parents ae79a85 + bea17f6 commit 0705d31
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 26 deletions.
80 changes: 61 additions & 19 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package apidemic

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"

"github.com/gorilla/mux"
"github.com/pmylund/go-cache"
)

//Version is the version of apidemic. Apidemic uses semver.
const Version = "0.2"
// Version is the version of apidemic. Apidemic uses semver.
const Version = "0.3"

var maxItemTime = cache.DefaultExpiration

Expand All @@ -19,10 +21,13 @@ var store = func() *cache.Cache {
return c
}()

//API is the struct for the json object that is passed to apidemic for registration.
var allowedHttpMethods = []string{"OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD"}

// API is the struct for the json object that is passed to apidemic for registration.
type API struct {
Endpoint string `json:"endpoint"`
Payload map[string]interface{} `json:"payload"`
Endpoint string `json:"endpoint"`
HTTPMethod string `json:"http_method"`
Payload map[string]interface{} `json:"payload"`
}

// Home renders hopme page. It renders a json response with information about the service.
Expand All @@ -35,27 +40,36 @@ func Home(w http.ResponseWriter, r *http.Request) {
return
}

// RenderJSON helder for rndering JSON response, it marshals value into json and writes
// RenderJSON helper for rendering JSON response, it marshals value into json and writes
// it into w.
func RenderJSON(w http.ResponseWriter, code int, value interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
err := json.NewEncoder(w).Encode(value)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

//RegisterEndpoint receives API objects and registers them. The payload from the request is
// RegisterEndpoint receives API objects and registers them. The payload from the request is
// transformed into a self aware Value that is capable of faking its own attribute.
func RegisterEndpoint(w http.ResponseWriter, r *http.Request) {
var httpMethod string
a := API{}
err := json.NewDecoder(r.Body).Decode(&a)
if err != nil {
RenderJSON(w, http.StatusInternalServerError, NewResponse(err.Error()))
RenderJSON(w, http.StatusBadRequest, NewResponse(err.Error()))
return
}

if httpMethod, err = getAllowedMethod(a.HTTPMethod); err != nil {
RenderJSON(w, http.StatusBadRequest, NewResponse(err.Error()))
return
}
if _, ok := store.Get(a.Endpoint); ok {

eKey := getCacheKeys(a.Endpoint, httpMethod)
if _, ok := store.Get(eKey); ok {
RenderJSON(w, http.StatusOK, NewResponse("endpoint already taken"))
return
}
Expand All @@ -65,22 +79,50 @@ func RegisterEndpoint(w http.ResponseWriter, r *http.Request) {
RenderJSON(w, http.StatusInternalServerError, NewResponse(err.Error()))
return
}
store.Set(a.Endpoint, obj, maxItemTime)
store.Set(eKey, obj, maxItemTime)
RenderJSON(w, http.StatusOK, NewResponse("cool"))
}

//GetEndpoint renders registered endpoints.
func GetEndpoint(w http.ResponseWriter, r *http.Request) {
func getCacheKeys(endpoint, httpMethod string) string {
eKey := fmt.Sprintf("%s-%v-e", endpoint, httpMethod)

return eKey
}

func getAllowedMethod(method string) (string, error) {
if method == "" {
return "GET", nil
}

for _, m := range allowedHttpMethods {
if method == m {
return m, nil
}
}

return "", errors.New("HTTP method is not allowed")
}

// DynamicEndpoint renders registered endpoints.
func DynamicEndpoint(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
endpoint := vars["endpoint"]
if eVal, ok := store.Get(endpoint); ok {
RenderJSON(w, http.StatusOK, eVal)
code := http.StatusOK

eKey := getCacheKeys(vars["endpoint"], r.Method)
if eVal, ok := store.Get(eKey); ok {
if r.Method == "POST" {
code = http.StatusCreated
}

RenderJSON(w, code, eVal)
return
}
RenderJSON(w, http.StatusNotFound, NewResponse("apidemic: "+endpoint+" is not found"))

responseText := fmt.Sprintf("apidemic: %s has no %s endpoint", vars["endpoint"], r.Method)
RenderJSON(w, http.StatusNotFound, NewResponse(responseText))
}

//NewResponse helper for response JSON message
// NewResponse helper for response JSON message
func NewResponse(message string) interface{} {
return struct {
Text string `json:"text"`
Expand All @@ -89,11 +131,11 @@ func NewResponse(message string) interface{} {
}
}

//NewServer returns a new apidemic server
// NewServer returns a new apidemic server
func NewServer() *mux.Router {
m := mux.NewRouter()
m.HandleFunc("/", Home)
m.HandleFunc("/register", RegisterEndpoint).Methods("POST")
m.HandleFunc("/api/{endpoint}", GetEndpoint).Methods("GET")
m.HandleFunc("/api/{endpoint}", DynamicEndpoint).Methods("OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD")
return m
}
50 changes: 47 additions & 3 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,30 @@ import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAPI(t *testing.T) {
func TestDynamicEndpointFailsWithoutRegistration(t *testing.T) {
s := NewServer()
sample, err := ioutil.ReadFile("fixtures/sample_post_request.json")
if err != nil {
t.Fatal(err)
}
var out map[string]interface{}
err = json.NewDecoder(bytes.NewReader(sample)).Decode(&out)
if err != nil {
t.Fatal(err)
}

w := httptest.NewRecorder()
req := jsonRequest("POST", "/api/test", out)
s.ServeHTTP(w, req)

assert.Equal(t, w.Code, http.StatusNotFound)
}

func TestDynamicEndpointWithGetRequest(t *testing.T) {
s := NewServer()
sample, err := ioutil.ReadFile("fixtures/sample_request.json")
if err != nil {
Expand All @@ -24,13 +45,36 @@ func TestAPI(t *testing.T) {

w := httptest.NewRecorder()
req := jsonRequest("POST", "/register", out)

s.ServeHTTP(w, req)

w = httptest.NewRecorder()
req = jsonRequest("GET", "/api/test", nil)
s.ServeHTTP(w, req)
assert.Equal(t, w.Code, http.StatusOK)
}

func TestDynamicEndpointWithPostRequest(t *testing.T) {
s := NewServer()
sample, err := ioutil.ReadFile("fixtures/sample_post_request.json")
if err != nil {
t.Fatal(err)
}
var out map[string]interface{}
err = json.NewDecoder(bytes.NewReader(sample)).Decode(&out)
if err != nil {
t.Fatal(err)
}

w := httptest.NewRecorder()
req := jsonRequest("POST", "/register", out)

s.ServeHTTP(w, req)

w = httptest.NewRecorder()
req = jsonRequest("POST", "/api/test", nil)

s.ServeHTTP(w, req)
assert.Equal(t, w.Code, http.StatusCreated)
}

func jsonRequest(method string, path string, body interface{}) *http.Request {
Expand All @@ -46,6 +90,6 @@ func jsonRequest(method string, path string, body interface{}) *http.Request {
if err != nil {
panic(err)
}
req.Header.Set("Contet-Type", "application/json")
req.Header.Set("Content-Type", "application/json")
return req
}
2 changes: 1 addition & 1 deletion cmd/apidemic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func main() {
Flags: []cli.Flag{
cli.IntFlag{
Name: "port",
Usage: "http port to run",
Usage: "HTTP port to run",
Value: 3000,
EnvVar: "PORT",
},
Expand Down
2 changes: 1 addition & 1 deletion fixtures/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
"city:city": "Stockholm"
},
"country": {
"name:ountry": "Sweden"
"name:country": "Sweden"
}
}
32 changes: 32 additions & 0 deletions fixtures/sample_post_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"endpoint": "test",
"http_method": "POST",
"payload": {
"name: first_name": "anton",
"age: digits_n,max=2": 29,
"nothing:": null,
"true": true,
"false": false,
"list:word,max=3": [
"first",
"second"
],
"list2": [
{
"street:street": "Street 42",
"city:city": "Stockholm"
},
{
"street": "Street 42",
"city": "Stockholm"
}
],
"address": {
"street:street": "Street 42",
"city:city": "Stockholm"
},
"country": {
"name:country": "Sweden"
}
}
}
3 changes: 1 addition & 2 deletions fixtures/sample_request.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
"city:city": "Stockholm"
},
"country": {
"name:ountry": "Sweden"
"name:country": "Sweden"
}
}

}

0 comments on commit 0705d31

Please sign in to comment.