Skip to content

Commit

Permalink
simple resources with auto marshalling of response value
Browse files Browse the repository at this point in the history
  • Loading branch information
smokku committed Dec 4, 2014
1 parent 1625868 commit 69f1f32
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 65 deletions.
123 changes: 89 additions & 34 deletions core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,74 @@ package rip

import (
"encoding/json"
// "errors"
// "fmt"
"net/http"
"net/url"
"strconv"
"strings"
)

const (
GET = "GET"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
HEAD = "HEAD"
GET = "GET"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
)

// GetSupported is the interface that provides the Get
// method a resource must support to receive HTTP GETs.
type GetSupported interface {
Get(url.Values, http.Header) (int, interface{}, http.Header)
Get(string, *http.Request) (int, interface{}, http.Header)
}

// PostSupported is the interface that provides the Post
// method a resource must support to receive HTTP POSTs.
type PostSupported interface {
Post(url.Values, http.Header) (int, interface{}, http.Header)
Post(string, *http.Request) (int, interface{}, http.Header)
}

// PatchSupported is the interface that provides the Patch
// method a resource must support to receive HTTP PATCHs.
type PatchSupported interface {
Patch(url.Values, http.Header) (int, interface{}, http.Header)
Patch(string, *http.Request) (int, interface{}, http.Header)
}

// PutSupported is the interface that provides the Put
// method a resource must support to receive HTTP PUTs.
type PutSupported interface {
Put(url.Values, http.Header) (int, interface{}, http.Header)
Put(string, *http.Request) (int, interface{}, http.Header)
}

// DeleteSupported is the interface that provides the Delete
// method a resource must support to receive HTTP DELETEs.
type DeleteSupported interface {
Delete(url.Values, http.Header) (int, interface{}, http.Header)
Delete(string, *http.Request) (int, interface{}, http.Header)
}

// HeadSupported is the interface that provides the Head
// method a resource must support to receive HTTP HEADs.
type HeadSupported interface {
Head(url.Values, http.Header) (int, interface{}, http.Header)
Head(string, *http.Request) (int, interface{}, http.Header)
}

// HeadSupported is the interface that provides the Head
// method a resource must support to receive HTTP HEADs.
type OptionsSupported interface {
Options(string, *http.Request) (int, interface{}, http.Header)
}

func requestHandler(resource interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, request *http.Request) {
return func(resp http.ResponseWriter, req *http.Request) {

if request.ParseForm() != nil {
rw.WriteHeader(http.StatusBadRequest)
if req.ParseForm() != nil {
resp.WriteHeader(http.StatusBadRequest)
return
}

var handler func(url.Values, http.Header) (int, interface{}, http.Header)
var handler func(string, *http.Request) (int, interface{}, http.Header)

switch request.Method {
switch req.Method {
case GET:
if resource, ok := resource.(GetSupported); ok {
handler = resource.Get
Expand All @@ -76,6 +82,10 @@ func requestHandler(resource interface{}) http.HandlerFunc {
if resource, ok := resource.(PutSupported); ok {
handler = resource.Put
}
case PATCH:
if resource, ok := resource.(PatchSupported); ok {
handler = resource.Patch
}
case DELETE:
if resource, ok := resource.(DeleteSupported); ok {
handler = resource.Delete
Expand All @@ -84,30 +94,75 @@ func requestHandler(resource interface{}) http.HandlerFunc {
if resource, ok := resource.(HeadSupported); ok {
handler = resource.Head
}
case PATCH:
if resource, ok := resource.(PatchSupported); ok {
handler = resource.Patch
case OPTIONS:
if resource, ok := resource.(OptionsSupported); ok {
handler = resource.Options
}
}

var code = http.StatusOK
var content []byte
var header http.Header

if handler == nil {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
code = http.StatusMethodNotAllowed
} else if req.URL.Path[0] == '/' {
pathTokens := strings.Split(req.URL.Path, "/")
var id string
if len(pathTokens) > 2 {
id = pathTokens[2]
}

code, data, header := handler(request.Form, request.Header)
var data interface{}
code, data, header = handler(id, req)

content, err := json.MarshalIndent(data, "", " ")
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
if header == nil {
header = make(http.Header)
}

switch v := data.(type) {
case string:
content = []byte(v)
case int, int8, int16, int32, int64:
content = []byte(strconv.Itoa(data.(int)))
case uint, uint8, uint16, uint32, uint64:
content = []byte(strconv.FormatUint(data.(uint64), 10))
case float32, float64:
content = []byte(strconv.FormatFloat(data.(float64), 'f', -1, 64))
case bool:
if data.(bool) {
content = []byte("true")
} else {
content = []byte("false")
}
default:
var err error
content, err = json.MarshalIndent(data, "", " ")
if err != nil {
code = http.StatusInternalServerError
} else {
header.Set("Content-Type", "application/json")
}
}

if len(content) > 0 && len(header.Get("Content-Type")) == 0 {
header.Set("Content-Type", "text/plain")
}
} else {
code = http.StatusBadRequest
}

for name, values := range header {
for _, value := range values {
rw.Header().Add(name, value)
resp.Header().Add(name, value)
}
}
rw.WriteHeader(code)
rw.Write(content)

resp.WriteHeader(code)

if len(content) == 0 && code != http.StatusOK {
content = []byte(http.StatusText(code))
}
resp.Write(content)
}
}
44 changes: 23 additions & 21 deletions rip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,45 @@ package rip

import (
"net/http"
"strings"
//"strings"
)

type Resource interface {
AddResource(string, Resource) Resource
GetResource(string) Resource
/*
type Resource struct {
Add(string, interface{}) Resource
Get(string) Resource
ServeHTTP(http.ResponseWriter, *http.Request)
}
*/

type ResourceBase struct {
resources map[string]Resource
type Handler struct {
*http.ServeMux
resources map[string]interface{}
}

func New() Resource {
return new(ResourceBase)
func New() *Handler {
return &Handler{http.NewServeMux(), make(map[string]interface{})}
}

func (r *ResourceBase) AddResource(path string, res Resource) Resource {
if r.resources == nil {
r.resources = make(map[string]Resource)
}
r.resources[path] = res
return r
func (h *Handler) Add(name string, resource interface{}) *Handler {
h.HandleFunc("/"+name+"/", requestHandler(resource))
return h
}

func (r *ResourceBase) GetResource(path string) Resource {
return r.resources[path]
/*
func (r *Handler) Get(name string) *Handler {
return r.resources[name]
}
func (r *ResourceBase) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
path := req.URL.Path[1:]
func (r *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
name := req.URL.Path[1:]
for needle, res := range r.resources {
if needle == path || strings.HasPrefix(path, needle+"/") {
if len(path) > len(needle) {
req.URL.Path = path[len(needle):]
if needle == name || strings.HasPrefix(name, needle+"/") {
if len(name) > len(needle) {
req.URL.Path = name[len(needle):]
res.ServeHTTP(resp, req)
}
}
}
}
*/
17 changes: 7 additions & 10 deletions rip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@ import (
)

type fooResource struct {
ResourceBase
}
type fooSubResource struct {
ResourceBase
}
type bazResource struct {
ResourceBase
}

func TestMain(t *testing.T) {
root := New().
AddResource("foos", new(fooResource)).
AddResource("bazes", new(bazResource))
root.GetResource("foos").
AddResource("bars", new(fooSubResource))
Add("foos", fooResource{}).
Add("bazes", bazResource{})
//root.Get("foos").
// Add("bars", fooSubResource{})

type MainRequestTest struct {
method string
Expand All @@ -47,8 +44,8 @@ func TestMain(t *testing.T) {
t.Fatal(err)
}

w := httptest.NewRecorder()
root.ServeHTTP(w, req)
t.Logf("%d - %s", w.Code, w.Body.String())
rsp := httptest.NewRecorder()
root.ServeHTTP(rsp, req)
t.Logf("%d - %s", rsp.Code, rsp.Body.String())
}
}

0 comments on commit 69f1f32

Please sign in to comment.