Skip to content

Commit

Permalink
make api simple (#2)
Browse files Browse the repository at this point in the history
* simple api

* update readme

* update travis
  • Loading branch information
acoshift authored Apr 19, 2019
1 parent d5065c5 commit c019e52
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
.idea/
.vscode/
*.tmp
*.log
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
language: go

go:
- 1.9.x
- 1.10.x
- 1.12.x

before_install:
- go get github.com/mattn/goveralls
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ Convert RPC style function into http.Handler
### Create new hrpc Manager

```go
m := hrpc.New(hrpc.Config{
RequestDecoder: func(r *http.Request, dst interface{}) error {
m := hrpc.Manager{
Decoder: func(r *http.Request, dst interface{}) error {
return json.NewDecoder(r.Body).Decode(dst)
},
ResponseEncoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
Encoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
},
Expand All @@ -45,7 +45,7 @@ m := hrpc.New(hrpc.Config{
json.NewEncoder(w).Encode(res)
},
Validate: true,
})
}
```

### RPC style function
Expand All @@ -55,6 +55,14 @@ type UserRequest struct {
ID int `json:"id"`
}

func (req *UserRequest) Valid() error {
// Valid will be called when decode, if set validate to true
if req.ID <= 0 {
return fmt.Errorf("invalid id")
}
return nil
}

type UserResponse struct {
ID int `json:"id"`
Username string `json:"username"`
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module github.com/acoshift/hrpc

go 1.12
74 changes: 44 additions & 30 deletions hrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,47 @@ import (
"reflect"
)

// Manager type
// Decoder is the request decoder
type Decoder func(*http.Request, interface{}) error

// Encoder is the response encoder
type Encoder func(http.ResponseWriter, *http.Request, interface{})

// ErrorEncoder is the error response encoder
type ErrorEncoder func(http.ResponseWriter, *http.Request, error)

// Manager is the hrpc manager
type Manager struct {
c Config
Decoder Decoder
Encoder Encoder
ErrorEncoder ErrorEncoder
Validate bool // set to true to validate request after decode using Validatable interface
}

// Config is the hrpc config
type Config struct {
RequestDecoder func(*http.Request, interface{}) error
ResponseEncoder func(http.ResponseWriter, *http.Request, interface{})
ErrorEncoder func(http.ResponseWriter, *http.Request, error)
Validate bool // set to true to validate request after decode using Validatable interface
func (m *Manager) decoder() Decoder {
if m.Decoder == nil {
return func(*http.Request, interface{}) error { return nil }
}
return m.Decoder
}

// Validatable interface
type Validatable interface {
Validate() error
func (m *Manager) encoder() Encoder {
if m.Encoder == nil {
return func(http.ResponseWriter, *http.Request, interface{}) {}
}
return m.Encoder
}

// New creates new manager
func New(config Config) *Manager {
m := &Manager{config}
if config.RequestDecoder == nil {
m.c.RequestDecoder = func(*http.Request, interface{}) error { return nil }
func (m *Manager) errorEncoder() ErrorEncoder {
if m.ErrorEncoder == nil {
return func(http.ResponseWriter, *http.Request, error) {}
}
if config.ResponseEncoder == nil {
m.c.ResponseEncoder = func(http.ResponseWriter, *http.Request, interface{}) {}
}
if config.ErrorEncoder == nil {
m.c.ErrorEncoder = func(http.ResponseWriter, *http.Request, error) {}
}
return m
return m.ErrorEncoder
}

// Validatable interface
type Validatable interface {
Valid() error
}

type mapIndex int
Expand Down Expand Up @@ -120,6 +130,10 @@ func (m *Manager) Handler(f interface{}) http.Handler {
}
}

encoder := m.encoder()
decoder := m.decoder()
errorEncoder := m.errorEncoder()

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vIn := make([]reflect.Value, numIn)
// inject context
Expand All @@ -130,17 +144,17 @@ func (m *Manager) Handler(f interface{}) http.Handler {
if i, ok := mapIn[miInterface]; ok {
rfReq := reflect.New(typ)
req := rfReq.Interface()
err := m.c.RequestDecoder(r, req)
err := decoder(r, req)
if err != nil {
m.c.ErrorEncoder(w, r, err)
errorEncoder(w, r, err)
return
}

if m.c.Validate {
if m.Validate {
if req, ok := req.(Validatable); ok {
err = req.Validate()
err = req.Valid()
if err != nil {
m.c.ErrorEncoder(w, r, err)
errorEncoder(w, r, err)
return
}
}
Expand All @@ -161,14 +175,14 @@ func (m *Manager) Handler(f interface{}) http.Handler {
if i, ok := mapOut[miError]; ok {
if vErr := vOut[i]; !vErr.IsNil() {
if err, ok := vErr.Interface().(error); ok && err != nil {
m.c.ErrorEncoder(w, r, err)
errorEncoder(w, r, err)
return
}
}
}
// check response
if i, ok := mapOut[miInterface]; ok {
m.c.ResponseEncoder(w, r, vOut[i].Interface())
encoder(w, r, vOut[i].Interface())
}

// if f is not return response, it may already call from native response writer
Expand Down
24 changes: 12 additions & 12 deletions hrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import (
"testing"
)

func jsonRequestDecoder(r *http.Request, dst interface{}) error {
func jsonDecoder(r *http.Request, dst interface{}) error {
return json.NewDecoder(r.Body).Decode(dst)
}

type requestType struct {
Data int
}

func (req *requestType) Validate() error {
func (req *requestType) Valid() error {
if req.Data < 0 {
return errors.New("invalid data")
}
Expand Down Expand Up @@ -48,16 +48,16 @@ func TestHandler(t *testing.T) {
w = httptest.NewRecorder()
}

m := New(Config{
RequestDecoder: jsonRequestDecoder,
ResponseEncoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
m := Manager{
Decoder: jsonDecoder,
Encoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
callSuccess = true
},
ErrorEncoder: func(w http.ResponseWriter, r *http.Request, err error) {
callError = true
},
Validate: true,
})
}

h := m.Handler(func(ctx context.Context, req *requestType) (interface{}, error) {
if req.Data != 1 {
Expand Down Expand Up @@ -146,7 +146,7 @@ func TestHandler(t *testing.T) {
}

func TestDefault(t *testing.T) {
m := New(Config{})
m := Manager{}
i := 0
h := m.Handler(func(ctx context.Context, req *requestType) (interface{}, error) {
if i == 0 {
Expand All @@ -171,7 +171,7 @@ func TestInvalidF(t *testing.T) {
t.Fatal("should panic")
}
}
m := New(Config{})
m := Manager{}
func() {
defer p()
m.Handler(1)
Expand All @@ -191,11 +191,11 @@ func TestInvalidF(t *testing.T) {
}

func ExampleManager() {
m := New(Config{
RequestDecoder: func(r *http.Request, dst interface{}) error {
m := Manager{
Decoder: func(r *http.Request, dst interface{}) error {
return json.NewDecoder(r.Body).Decode(dst)
},
ResponseEncoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
Encoder: func(w http.ResponseWriter, r *http.Request, res interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
},
Expand All @@ -208,7 +208,7 @@ func ExampleManager() {
json.NewEncoder(w).Encode(res)
},
Validate: true,
})
}

http.Handle("/user.get", m.Handler(func(ctx context.Context, req *struct {
ID string `json:"id"`
Expand Down

0 comments on commit c019e52

Please sign in to comment.