Skip to content

Commit

Permalink
Merge 1264a78 into 42640c4
Browse files Browse the repository at this point in the history
  • Loading branch information
jirenius committed Jun 3, 2020
2 parents 42640c4 + 1264a78 commit 454e896
Show file tree
Hide file tree
Showing 21 changed files with 1,200 additions and 544 deletions.
2 changes: 1 addition & 1 deletion docs/res-client-protocol.md
Expand Up @@ -50,7 +50,7 @@ The resource that is subscribed to with a [subscribe request](#subscribe-request
It is possible to make multiple direct subscriptions on a resource. It will be considered directly subscribed until an equal number of [unsubscribe requests](#unsubscribe-request) has been made.

## Indirect subscription
A resource that is referred to with a [resource reference](res-protocol.md#values) by a [directly subscribed](#direct-subscription) resource, or by an indirectly subscribed resource, will be considered *indirectly subscribed*. Cyclic references where none of the resources are directly subscribed will not be considered subscribed.
A resource that is referred to with a non-soft [resource reference](res-protocol.md#values) by a [directly subscribed](#direct-subscription) resource, or by an indirectly subscribed resource, will be considered *indirectly subscribed*. Cyclic references where none of the resources are directly subscribed will not be considered subscribed.


## Resource set
Expand Down
7 changes: 6 additions & 1 deletion docs/res-protocol.md
Expand Up @@ -94,12 +94,17 @@ null // null

## Resource references

A resource reference is a JSON objects with the following parameter:
A resource reference is a link to a resource. A *soft reference* is a resource reference which will not automatically be followed by the gateway. The resource reference is a JSON objects with the following parameters:

**rid**
Resource ID of the referenced resource.
MUST be a valid [resource ID](#resource-ids).

**soft**
Flag telling if the reference is a soft resource reference.
May be omitted if the reference is not a soft reference.
MUST be a boolean.

## Messaging system

The messaging system handles the communication between [services](#services) and [gateways](#gateways). It MUST provide the following functionality:
Expand Down
8 changes: 5 additions & 3 deletions docs/res-service-protocol.md
Expand Up @@ -152,14 +152,16 @@ MUST be a string.
### Result

**get**
Flag if the client has access to get (read) the resource.
Flag telling if the client has access to get (read) the resource, including any
resource recursively referenced by non-soft [resource
references](res-protocol.md#resource-references).
May be omitted if client has no get access.
MUST be a boolean
MUST be a boolean.

**call**
A comma separated list of methods that the client can call. Eg. `"set,foo,bar"`.
May be omitted if client is not allowed to call any methods.
Value may be a single asterisk character (`"*"`) if client is allowed to call any method.
Value may be a single asterisk character (`"*"`) if client is allowed to call any method.

### Error

Expand Down
78 changes: 50 additions & 28 deletions server/apiEncoding.go
Expand Up @@ -214,13 +214,8 @@ func (e *encoderJSON) encodeSubscription(s *Subscription, wrap bool) error {
if i > 0 {
e.b.WriteByte(',')
}
if v.Type == codec.ValueTypeResource {
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc, true); err != nil {
return err
}
} else {
e.b.Write(v.RawMessage)
if err := e.encodeValue(s, v); err != nil {
return err
}
}
e.b.WriteByte(']')
Expand All @@ -247,13 +242,8 @@ func (e *encoderJSON) encodeSubscription(s *Subscription, wrap bool) error {
e.b.Write(dta)
e.b.WriteByte(':')

if v.Type == codec.ValueTypeResource {
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc, true); err != nil {
return err
}
} else {
e.b.Write(v.RawMessage)
if err := e.encodeValue(s, v); err != nil {
return err
}
}
e.b.WriteByte('}')
Expand All @@ -265,6 +255,27 @@ func (e *encoderJSON) encodeSubscription(s *Subscription, wrap bool) error {
return nil
}

func (e *encoderJSON) encodeValue(s *Subscription, v codec.Value) error {
switch v.Type {
case codec.ValueTypeReference:
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc, true); err != nil {
return err
}
case codec.ValueTypeSoftReference:
e.b.Write([]byte(`{"href":`))
dta, err := json.Marshal(RIDToPath(v.RID, e.apiPath))
if err != nil {
return err
}
e.b.Write(dta)
e.b.WriteByte('}')
default:
e.b.Write(v.RawMessage)
}
return nil
}

type encoderJSONFlat struct {
b bytes.Buffer
path []string
Expand Down Expand Up @@ -338,13 +349,8 @@ func (e *encoderJSONFlat) encodeSubscription(s *Subscription) error {
if i > 0 {
e.b.WriteByte(',')
}
if v.Type == codec.ValueTypeResource {
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc); err != nil {
return err
}
} else {
e.b.Write(v.RawMessage)
if err := e.encodeValue(s, v); err != nil {
return err
}
}
e.b.WriteByte(']')
Expand All @@ -368,13 +374,8 @@ func (e *encoderJSONFlat) encodeSubscription(s *Subscription) error {
e.b.Write(dta)
e.b.WriteByte(':')

if v.Type == codec.ValueTypeResource {
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc); err != nil {
return err
}
} else {
e.b.Write(v.RawMessage)
if err := e.encodeValue(s, v); err != nil {
return err
}
}
e.b.WriteByte('}')
Expand All @@ -385,6 +386,27 @@ func (e *encoderJSONFlat) encodeSubscription(s *Subscription) error {
return nil
}

func (e *encoderJSONFlat) encodeValue(s *Subscription, v codec.Value) error {
switch v.Type {
case codec.ValueTypeReference:
sc := s.Ref(v.RID)
if err := e.encodeSubscription(sc); err != nil {
return err
}
case codec.ValueTypeSoftReference:
e.b.Write([]byte(`{"href":`))
dta, err := json.Marshal(RIDToPath(v.RID, e.apiPath))
if err != nil {
return err
}
e.b.Write(dta)
e.b.WriteByte('}')
default:
e.b.Write(v.RawMessage)
}
return nil
}

func jsonEncodeError(rerr *reserr.Error) []byte {
out, err := json.Marshal(rerr)
if err != nil {
Expand Down
33 changes: 23 additions & 10 deletions server/codec/codec.go
Expand Up @@ -175,9 +175,10 @@ type ValueType byte
// Value type constants
const (
ValueTypeNone ValueType = iota
ValueTypePrimitive
ValueTypeResource
ValueTypeDelete
ValueTypePrimitive
ValueTypeReference
ValueTypeSoftReference
)

// Value represents a RES value
Expand All @@ -191,9 +192,16 @@ type Value struct {
// ValueObject represents a resource reference or an action
type ValueObject struct {
RID *string `json:"rid"`
Soft bool `json:"soft"`
Action *string `json:"action"`
}

// IsProper returns true if the value's type is either a primitive or a
// reference.
func (v Value) IsProper() bool {
return v.Type >= ValueTypePrimitive
}

// DeleteValue is a predeclared delete action value
var DeleteValue = Value{
RawMessage: json.RawMessage(`{"action":"delete"}`),
Expand Down Expand Up @@ -231,11 +239,15 @@ func (v *Value) UnmarshalJSON(data []byte) error {
if mvo.Action != nil || *mvo.RID == "" {
return errInvalidValue
}
v.Type = ValueTypeResource
v.RID = *mvo.RID
if !IsValidRID(v.RID, true) {
return errInvalidValue
}
if mvo.Soft {
v.Type = ValueTypeSoftReference
} else {
v.Type = ValueTypeReference
}
} else {
// Must be an action of type actionDelete
if mvo.Action == nil || *mvo.Action != actionDelete {
Expand All @@ -261,7 +273,9 @@ func (v Value) Equal(w Value) bool {
switch v.Type {
case ValueTypePrimitive:
return bytes.Equal(v.RawMessage, w.RawMessage)
case ValueTypeResource:
case ValueTypeReference:
fallthrough
case ValueTypeSoftReference:
return v.RID == w.RID
}

Expand Down Expand Up @@ -320,14 +334,14 @@ func DecodeGetResponse(payload []byte) (*GetResult, error) {
}
// Assert model only has proper values
for _, v := range res.Model {
if v.Type != ValueTypeResource && v.Type != ValueTypePrimitive {
if !v.IsProper() {
return nil, errInvalidResponse
}
}
} else if res.Collection != nil {
// Assert collection only has proper values
for _, v := range res.Collection {
if v.Type != ValueTypeResource && v.Type != ValueTypePrimitive {
if !v.IsProper() {
return nil, errInvalidResponse
}
}
Expand Down Expand Up @@ -397,14 +411,14 @@ func DecodeEventQueryResponse(payload []byte) (*EventQueryResult, error) {
}
// Assert model only has proper values
for _, v := range res.Model {
if v.Type != ValueTypeResource && v.Type != ValueTypePrimitive {
if !v.IsProper() {
return nil, errInvalidResponse
}
}
case res.Collection != nil:
// Assert collection only has proper values
for _, v := range res.Collection {
if v.Type != ValueTypeResource && v.Type != ValueTypePrimitive {
if !v.IsProper() {
return nil, errInvalidResponse
}
}
Expand Down Expand Up @@ -483,8 +497,7 @@ func DecodeAddEvent(data json.RawMessage) (*AddEvent, error) {
}

// Assert it is a proper value
t := d.Value.Type
if t != ValueTypeResource && t != ValueTypePrimitive {
if !d.Value.IsProper() {
return nil, errInvalidValue
}

Expand Down
88 changes: 88 additions & 0 deletions server/rescache/legacy.go
@@ -0,0 +1,88 @@
package rescache

import (
"bytes"
"encoding/json"

"github.com/resgateio/resgate/server/codec"
)

// Legacy120Model marshals a model compatible with version 1.2.0
// (versionSoftResourceReference) and below.
type Legacy120Model Model

// Legacy120Collection marshals a collection compatible with version 1.2.0
// (versionSoftResourceReference) and below.
type Legacy120Collection Collection

// Legacy120Value marshals a value compatible with version 1.2.0
// (versionSoftResourceReference) and below.
type Legacy120Value codec.Value

// Legacy120ValueMap marshals a map of values compatible with version 1.2.0
// (versionSoftResourceReference) and below.
type Legacy120ValueMap map[string]codec.Value

// MarshalJSON creates a JSON encoded representation of the model
func (m *Legacy120Model) MarshalJSON() ([]byte, error) {
for _, v := range m.Values {
if v.Type == codec.ValueTypeSoftReference {
return Legacy120ValueMap(m.Values).MarshalJSON()
}
}
return (*Model)(m).MarshalJSON()
}

// MarshalJSON creates a JSON encoded representation of the model
func (c *Legacy120Collection) MarshalJSON() ([]byte, error) {
for _, v := range c.Values {
if v.Type == codec.ValueTypeSoftReference {
goto LegacyMarshal
}
}
return (*Collection)(c).MarshalJSON()

LegacyMarshal:

vs := c.Values
lvs := make([]Legacy120Value, len(vs))
for i, v := range vs {
lvs[i] = Legacy120Value(v)
}

return json.Marshal(lvs)
}

// MarshalJSON creates a JSON encoded representation of the value
func (v Legacy120Value) MarshalJSON() ([]byte, error) {
if v.Type == codec.ValueTypeSoftReference {
return json.Marshal(v.RID)
}
return v.RawMessage, nil
}

// MarshalJSON creates a JSON encoded representation of the map
func (m Legacy120ValueMap) MarshalJSON() ([]byte, error) {
var b bytes.Buffer
notfirst := false
b.WriteByte('{')
for k, v := range m {
if notfirst {
b.WriteByte(',')
}
notfirst = true
dta, err := json.Marshal(k)
if err != nil {
return nil, err
}
b.Write(dta)
b.WriteByte(':')
dta, err = Legacy120Value(v).MarshalJSON()
if err != nil {
return nil, err
}
b.Write(dta)
}
b.WriteByte('}')
return b.Bytes(), nil
}

0 comments on commit 454e896

Please sign in to comment.