Skip to content

Commit

Permalink
Merge 61746b5 into 937e59c
Browse files Browse the repository at this point in the history
  • Loading branch information
jirenius committed Dec 16, 2019
2 parents 937e59c + 61746b5 commit 2ccf771
Show file tree
Hide file tree
Showing 22 changed files with 990 additions and 635 deletions.
16 changes: 8 additions & 8 deletions errors.go
Expand Up @@ -27,21 +27,21 @@ func InternalError(err error) *Error {

// Predefined error codes
const (
CodeAccessDenied = "system.accessDenied"
CodeInternalError = "system.internalError"
CodeInvalidParams = "system.invalidParams"
CodeMethodNotFound = "system.methodNotFound"
CodeNotFound = "system.notFound"
CodeTimeout = "system.timeout"
CodeBadRequest = "system.badRequest"
CodeMethodNotAllowed = "system.methodNotAllowed"
CodeAccessDenied = "system.accessDenied"
CodeInternalError = "system.internalError"
CodeInvalidParams = "system.invalidParams"
CodeInvalidQuery = "system.invalidQuery"
CodeMethodNotFound = "system.methodNotFound"
CodeNotFound = "system.notFound"
CodeTimeout = "system.timeout"
)

// Predefined errors
var (
ErrAccessDenied = &Error{Code: CodeAccessDenied, Message: "Access denied"}
ErrInternalError = &Error{Code: CodeInternalError, Message: "Internal error"}
ErrInvalidParams = &Error{Code: CodeInvalidParams, Message: "Invalid parameters"}
ErrInvalidQuery = &Error{Code: CodeInvalidQuery, Message: "Invalid query"}
ErrMethodNotFound = &Error{Code: CodeMethodNotFound, Message: "Method not found"}
ErrNotFound = &Error{Code: CodeNotFound, Message: "Not found"}
ErrTimeout = &Error{Code: CodeTimeout, Message: "Request timeout"}
Expand Down
25 changes: 16 additions & 9 deletions getrequest.go
Expand Up @@ -16,15 +16,20 @@ type getRequest struct {
err error
}

func (r *getRequest) Value() (interface{}, error) {
panic("Value() called within get request handler")
}

func (r *getRequest) RequireValue() interface{} {
panic("RequireValue() called within get request handler")
}

func (r *getRequest) Model(model interface{}) {
r.reply()
r.value = model
}

func (r *getRequest) QueryModel(model interface{}, query string) {
if query != "" && r.query == "" {
panic("res: query model response on non-query get request")
}
r.reply()
r.value = model
}
Expand All @@ -35,9 +40,6 @@ func (r *getRequest) Collection(collection interface{}) {
}

func (r *getRequest) QueryCollection(collection interface{}, query string) {
if query != "" && r.query == "" {
panic("res: query model response on non-query get request")
}
r.reply()
r.value = collection
}
Expand All @@ -46,6 +48,14 @@ func (r *getRequest) NotFound() {
r.Error(ErrNotFound)
}

func (r *getRequest) InvalidQuery(message string) {
if message == "" {
r.Error(ErrInvalidQuery)
} else {
r.Error(&Error{Code: CodeInvalidQuery, Message: message})
}
}

func (r *getRequest) Error(err *Error) {
r.reply()
r.err = err
Expand All @@ -67,11 +77,8 @@ func (r *getRequest) reply() {
}

func (r *getRequest) executeHandler() {
r.inGet = true
// Recover from panics inside handlers
defer func() {
r.inGet = false

v := recover()
if v == nil {
return
Expand Down
2 changes: 0 additions & 2 deletions middleware/badgerdb.go
Expand Up @@ -429,7 +429,6 @@ func (b *badgerDB) applyDelete(r res.Resource) (interface{}, error) {
return nil
})
if err != nil {
println("From 1", err)
return nil, err
}

Expand All @@ -442,7 +441,6 @@ func (b *badgerDB) applyDelete(r res.Resource) (interface{}, error) {
// This might cause panic in any OnDelete handlers, when trying to type assert
// the value. But then the delete event is at least propagated properly.

println("From 2")
return json.RawMessage(dta), nil
}
return v.Elem().Interface(), nil
Expand Down
11 changes: 11 additions & 0 deletions queryevent.go
Expand Up @@ -16,6 +16,7 @@ const queryEventChannelSize = 10
type QueryRequest interface {
Resource
NotFound()
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
}
Expand Down Expand Up @@ -67,6 +68,16 @@ func (qr *queryRequest) NotFound() {
qr.reply(responseNotFound)
}

// InvalidQuery sends a system.invalidQuery response for the query request.
// An empty message will default to "Invalid query".
func (qr *queryRequest) InvalidQuery(message string) {
if message == "" {
qr.reply(responseInvalidQuery)
} else {
qr.error(&Error{Code: CodeInvalidQuery, Message: message})
}
}

// Error sends a custom error response for the query request.
func (qr *queryRequest) Error(err *Error) {
qr.error(err)
Expand Down
26 changes: 18 additions & 8 deletions request.go
Expand Up @@ -44,6 +44,7 @@ type AccessRequest interface {
AccessDenied()
AccessGranted()
NotFound()
InvalidQuery(message string)
Error(err *Error)
RawToken() json.RawMessage
ParseToken(interface{})
Expand All @@ -56,6 +57,7 @@ type ModelRequest interface {
Model(model interface{})
QueryModel(model interface{}, query string)
NotFound()
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
ForValue() bool
Expand All @@ -67,6 +69,7 @@ type CollectionRequest interface {
Collection(collection interface{})
QueryCollection(collection interface{}, query string)
NotFound()
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
ForValue() bool
Expand All @@ -80,6 +83,7 @@ type GetRequest interface {
Collection(collection interface{})
QueryCollection(collection interface{}, query string)
NotFound()
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
ForValue() bool
Expand All @@ -98,6 +102,7 @@ type CallRequest interface {
NotFound()
MethodNotFound()
InvalidParams(message string)
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
}
Expand All @@ -114,6 +119,7 @@ type NewRequest interface {
NotFound()
MethodNotFound()
InvalidParams(message string)
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
}
Expand All @@ -135,6 +141,7 @@ type AuthRequest interface {
NotFound()
MethodNotFound()
InvalidParams(message string)
InvalidQuery(message string)
Error(err *Error)
Timeout(d time.Duration)
TokenEvent(t interface{})
Expand All @@ -147,6 +154,7 @@ var (
responseNotFound = []byte(`{"error":{"code":"system.notFound","message":"Not found"}}`)
responseMethodNotFound = []byte(`{"error":{"code":"system.methodNotFound","message":"Method not found"}}`)
responseInvalidParams = []byte(`{"error":{"code":"system.invalidParams","message":"Invalid parameters"}}`)
responseInvalidQuery = []byte(`{"error":{"code":"system.invalidQuery","message":"Invalid query"}}`)
responseMissingResponse = []byte(`{"error":{"code":"system.internalError","message":"Internal error: missing response"}}`)
responseMissingQuery = []byte(`{"error":{"code":"system.internalError","message":"Internal error: missing query"}}`)
responseAccessGranted = []byte(`{"result":{"get":true,"call":"*"}}`)
Expand Down Expand Up @@ -258,6 +266,16 @@ func (r *Request) InvalidParams(message string) {
}
}

// InvalidQuery sends a system.invalidQuery response.
// An empty message will default to "Invalid query".
func (r *Request) InvalidQuery(message string) {
if message == "" {
r.reply(responseInvalidQuery)
} else {
r.error(&Error{Code: CodeInvalidQuery, Message: message})
}
}

// Access sends a successful response.
// The get flag tells if the client has access to get (read) the resource.
// The call string is a comma separated list of methods that the client can
Expand Down Expand Up @@ -301,9 +319,6 @@ func (r *Request) QueryModel(model interface{}, query string) {

// model sends a successful model response for the get request.
func (r *Request) model(model interface{}, query string) {
if query != "" && r.query == "" {
panic("res: query model response on non-query request")
}
// [TODO] Marshal model to a json.RawMessage to see if it is a JSON object
r.success(modelResponse{Model: model, Query: query})
}
Expand All @@ -324,9 +339,6 @@ func (r *Request) QueryCollection(collection interface{}, query string) {

// collection sends a successful collection response for the get request.
func (r *Request) collection(collection interface{}, query string) {
if query != "" && r.query == "" {
panic("res: query collection response on non-query request")
}
// [TODO] Marshal collection to a json.RawMessage to see if it is a JSON array
r.success(collectionResponse{Collection: collection, Query: query})
}
Expand Down Expand Up @@ -434,7 +446,6 @@ func (r *Request) reply(payload []byte) {
func (r *Request) executeHandler() {
// Recover from panics inside handlers
defer func() {
r.inGet = false
v := recover()
if v == nil {
return
Expand Down Expand Up @@ -481,7 +492,6 @@ func (r *Request) executeHandler() {
}
hs.Access(r)
case "get":
r.inGet = true
if hs.Get == nil {
r.reply(responseNotFound)
return
Expand Down
17 changes: 11 additions & 6 deletions resource.go
Expand Up @@ -83,6 +83,12 @@ type Resource interface {
// https://github.com/resgateio/resgate/blob/master/docs/res-service-protocol.md#reaccess-event
ReaccessEvent()

// ResetEvent sends a reset event to signal that the resource's data has changed.
// It will invalidate any previous get response sent for the resource.
// See the protocol specification for more information:
// https://github.com/resgateio/resgate/blob/master/docs/res-service-protocol.md#reaccess-event
ResetEvent()

// QueryEvent sends a query event to signal that the query resource's underlying data has been modified.
// See the protocol specification for more information:
// https://github.com/resgateio/resgate/blob/master/docs/res-service-protocol.md#query-event
Expand All @@ -102,7 +108,6 @@ type resource struct {
pathParams map[string]string
query string
group string
inGet bool
h Handler
s *Service
}
Expand Down Expand Up @@ -156,11 +161,6 @@ func (r *resource) ParseQuery() url.Values {
// defined, it returns a nil interface and a *Error type error.
// Panics if called from within a Get handler.
func (r *resource) Value() (interface{}, error) {
// Panic if the getRequest is called within Get handler.
if r.inGet {
panic("Value() called from within get handler")
}

gr := &getRequest{resource: r}
gr.executeHandler()
return gr.value, gr.err
Expand Down Expand Up @@ -274,6 +274,11 @@ func (r *resource) ReaccessEvent() {
r.s.rawEvent("event."+r.rname+".reaccess", nil)
}

// ResetEvent sends a system.reset event for the specific resource.
func (r *resource) ResetEvent() {
r.s.Reset([]string{r.ResourceName()}, nil)
}

// QueryEvent sends a query event on the resource, calling the
// provided callback on any query request.
// The last call to the callback will always be with nil, indicating
Expand Down
8 changes: 8 additions & 0 deletions service.go
Expand Up @@ -14,6 +14,9 @@ import (
nats "github.com/nats-io/nats.go"
)

// Targetted RES protocol version
const protocolVersion = "1.1.1"

// The size of the in channel receiving messages from NATS Server.
const inChannelSize = 256

Expand Down Expand Up @@ -235,6 +238,11 @@ func (s *Service) Logger() logger.Logger {
return s.logger
}

// ProtocolVersion returns the supported RES protocol version.
func (s *Service) ProtocolVersion() string {
return protocolVersion
}

// infof logs a formatted info entry.
func (s *Service) infof(format string, v ...interface{}) {
if s.logger == nil {
Expand Down
15 changes: 8 additions & 7 deletions test/00serve_test.go
Expand Up @@ -64,12 +64,13 @@ func TestServiceSetReset(t *testing.T) {

// Test that TokenEvent sends a connection token event.
func TestServiceTokenEvent(t *testing.T) {
token := `{"id":42,"user":"foo","role":"admin"}`
runTest(t, func(s *Session) {
s.Handle("model", res.GetResource(func(r res.GetRequest) { r.NotFound() }))
}, func(s *Session) {
s.TokenEvent(defaultCID, json.RawMessage(token))
s.GetMsg(t).AssertSubject(t, "conn."+defaultCID+".token").AssertPayload(t, json.RawMessage(`{"token":`+token+`}`))
s.TokenEvent(mock.CID, mock.Token)
s.GetMsg(t).
AssertSubject(t, "conn."+mock.CID+".token").
AssertPayload(t, json.RawMessage(`{"token":{"user":"foo","id":42}}`))
})
}

Expand All @@ -78,8 +79,8 @@ func TestServiceNilTokenEvent(t *testing.T) {
runTest(t, func(s *Session) {
s.Handle("model", res.GetResource(func(r res.GetRequest) { r.NotFound() }))
}, func(s *Session) {
s.TokenEvent(defaultCID, nil)
s.GetMsg(t).AssertSubject(t, "conn."+defaultCID+".token").AssertPayload(t, json.RawMessage(`{"token":null}`))
s.TokenEvent(mock.CID, nil)
s.GetMsg(t).AssertSubject(t, "conn."+mock.CID+".token").AssertPayload(t, json.RawMessage(`{"token":null}`))
})
}

Expand Down Expand Up @@ -124,15 +125,15 @@ func TestServiceReset(t *testing.T) {
}, func(s *Session) {
s.Reset(l.Resources, l.Access)
// Send token event to flush any system.reset event
s.TokenEvent(defaultCID, nil)
s.TokenEvent(mock.CID, nil)

if l.Expected != nil {
s.GetMsg(t).
AssertSubject(t, "system.reset").
AssertPayload(t, l.Expected)
}

s.GetMsg(t).AssertSubject(t, "conn."+defaultCID+".token")
s.GetMsg(t).AssertSubject(t, "conn."+mock.CID+".token")
})
}
}
Expand Down
31 changes: 31 additions & 0 deletions test/02access_request_test.go
Expand Up @@ -60,6 +60,37 @@ func TestAccessError(t *testing.T) {
})
}

// Test calling InvalidQuery with no message on an access request results in system.invalidQuery
func TestAccessInvalidQuery_EmptyMessage(t *testing.T) {
runTest(t, func(s *Session) {
s.Handle("model", res.Access(func(r res.AccessRequest) {
r.InvalidQuery("")
}))
}, func(s *Session) {
inb := s.Request("access.test.model", mock.QueryRequest())
s.GetMsg(t).
AssertSubject(t, inb).
AssertError(t, res.ErrInvalidQuery)
})
}

// Test calling InvalidQuery on an access request results in system.invalidQuery
func TestAccessInvalidQuery_CustomMessage(t *testing.T) {
runTest(t, func(s *Session) {
s.Handle("model", res.Access(func(r res.AccessRequest) {
r.InvalidQuery(mock.ErrorMessage)
}))
}, func(s *Session) {
inb := s.Request("access.test.model", mock.QueryRequest())
s.GetMsg(t).
AssertSubject(t, inb).
AssertError(t, &res.Error{
Code: res.CodeInvalidQuery,
Message: mock.ErrorMessage,
})
})
}

// Test that panicing in an access request results in system.internalError
func TestPanicOnAccess(t *testing.T) {
runTest(t, func(s *Session) {
Expand Down

0 comments on commit 2ccf771

Please sign in to comment.