Skip to content

Commit

Permalink
Merge pull request #52 from imulab/releases/2.1.0
Browse files Browse the repository at this point in the history
Releases/2.1.0
  • Loading branch information
imulab authored Feb 25, 2020
2 parents 51e18ff + a170355 commit 0df0357
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 61 deletions.
40 changes: 40 additions & 0 deletions pkg/v2/db/noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package db

import (
"context"
"github.com/imulab/go-scim/pkg/v2/crud"
"github.com/imulab/go-scim/pkg/v2/prop"
)

// NoOp return an no op implementation of DB. This implementation does nothing and always returns nil error. For Count
// method, it returns 0 as count; for Get method, it returns nil resource; For Query method, it returns empty slice as
// results. This implementation might be useful when implementing use cases where resource does not require persistence.
func NoOp() DB {
return noOpDB{}
}

type noOpDB struct{}

func (_ noOpDB) Insert(_ context.Context, _ *prop.Resource) error {
return nil
}

func (_ noOpDB) Count(_ context.Context, _ string) (int, error) {
return 0, nil
}

func (_ noOpDB) Get(_ context.Context, _ string, _ *crud.Projection) (*prop.Resource, error) {
return nil, nil
}

func (_ noOpDB) Replace(_ context.Context, _ *prop.Resource, _ *prop.Resource) error {
return nil
}

func (_ noOpDB) Delete(_ context.Context, _ *prop.Resource) error {
return nil
}

func (_ noOpDB) Query(_ context.Context, _ string, _ *crud.Sort, _ *crud.Pagination, _ *crud.Projection) ([]*prop.Resource, error) {
return []*prop.Resource{}, nil
}
7 changes: 5 additions & 2 deletions pkg/v2/json/deserialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ kvs:
break kvs
case scanEnd:
break kvs
case scanSkipSpace, scanObjectValue:
case scanSkipSpace, scanObjectValue, scanEndArray:
d.scanNext()
default:
break fastForward
Expand Down Expand Up @@ -233,7 +233,10 @@ func (d *deserializeState) parseMultiValuedProperty() error {
}

// Skip any spaces between '[' and the potential first element
d.scanWhile(scanSkipSpace)
d.scanNext()
if d.opCode == scanSkipSpace {
d.scanWhile(scanSkipSpace)
}

elements:
for d.opCode != scanEndArray {
Expand Down
117 changes: 66 additions & 51 deletions pkg/v2/json/deserialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,57 +32,57 @@ func (s *JsonDeserializeTestSuite) TestDeserializeResource() {
name: "default",
json: `
{
"schemas":[
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"id":"3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"meta":{
"resourceType":"User",
"created":"2019-11-20T13:09:00",
"lastModified":"2019-11-20T13:09:00",
"location":"https://identity.imulab.io/Users/3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"version":"W/\"1\""
},
"userName":"imulab",
"name":{
"formatted":"Mr. Weinan Qiu",
"familyName":"Qiu",
"givenName":"Weinan",
"honorificPrefix":"Mr."
},
"displayName":"Weinan",
"profileUrl":"https://identity.imulab.io/profiles/3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"userType":"Employee",
"preferredLanguage":"zh_CN",
"locale":"zh_CN",
"timezone":"Asia/Shanghai",
"active":true,
"emails":[
{
"value":"imulab@foo.com",
"type":"work",
"primary":true,
"display":"imulab@foo.com"
},
{
"value":"imulab@bar.com",
"type":"home",
"display":"imulab@bar.com"
}
],
"phoneNumbers":[
{
"value":"123-45678",
"type":"work",
"primary":true,
"display":"123-45678"
},
{
"value":"123-45679",
"type":"work",
"display":"123-45679"
}
]
"schemas":[
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"id":"3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"meta":{
"resourceType":"User",
"created":"2019-11-20T13:09:00",
"lastModified":"2019-11-20T13:09:00",
"location":"https://identity.imulab.io/Users/3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"version":"W/\"1\""
},
"userName":"imulab",
"name":{
"formatted":"Mr. Weinan Qiu",
"familyName":"Qiu",
"givenName":"Weinan",
"honorificPrefix":"Mr."
},
"displayName":"Weinan",
"profileUrl":"https://identity.imulab.io/profiles/3cc032f5-2361-417f-9e2f-bc80adddf4a3",
"userType":"Employee",
"preferredLanguage":"zh_CN",
"locale":"zh_CN",
"timezone":"Asia/Shanghai",
"active":true,
"emails":[
{
"value":"imulab@foo.com",
"type":"work",
"primary":true,
"display":"imulab@foo.com"
},
{
"value":"imulab@bar.com",
"type":"home",
"display":"imulab@bar.com"
}
],
"phoneNumbers":[
{
"value":"123-45678",
"type":"work",
"primary":true,
"display":"123-45678"
},
{
"value":"123-45679",
"type":"work",
"display":"123-45679"
}
]
}
`,
expect: func(t *testing.T, resource *prop.Resource, err error) {
Expand Down Expand Up @@ -196,6 +196,21 @@ func (s *JsonDeserializeTestSuite) TestDeserializeResource() {
}
},
},
{
name: "empty array",
json: `
{
"id": "foobar",
"emails":[],
"timezone":"Asia/Shanghai"
}`,
expect: func(t *testing.T, resource *prop.Resource, err error) {
assert.Nil(t, err)
assert.Equal(t, "foobar", resource.Navigator().Dot("id").Current().Raw())
assert.True(t, resource.Navigator().Dot("emails").Current().IsUnassigned())
assert.Equal(t, "Asia/Shanghai", resource.Navigator().Dot("timezone").Current().Raw())
},
},
}

for _, test := range tests {
Expand Down
11 changes: 8 additions & 3 deletions pkg/v2/service/filter/navigate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import (
// Marker property that indicates flexNavigator is out of sync
var outOfSync = outOfSyncProperty{}

// IsOutOfSync returns true when the given property is the marker out-of-sync property.
func IsOutOfSync(property prop.Property) bool {
return property == outOfSync
}

// flexNavigator is a Navigator implementation that can be used in two ways.
//
// First, as a follow-along navigator that synchronizes with another Navigator. When the other navigator focuses on
Expand Down Expand Up @@ -62,7 +67,7 @@ func (n *flexNavigator) Retract() prop.Navigator {
}

func (n *flexNavigator) Dot(name string) prop.Navigator {
if n.Current() == outOfSync {
if IsOutOfSync(n.Current()) {
n.Push(outOfSync)
return n
}
Expand All @@ -82,7 +87,7 @@ func (n *flexNavigator) Dot(name string) prop.Navigator {
}

func (n *flexNavigator) At(index int) prop.Navigator {
if n.Current() == outOfSync {
if IsOutOfSync(n.Current()) {
n.Push(outOfSync)
return n
}
Expand All @@ -102,7 +107,7 @@ func (n *flexNavigator) At(index int) prop.Navigator {
}

func (n *flexNavigator) Where(criteria func(child prop.Property) bool) prop.Navigator {
if n.Current() == outOfSync {
if IsOutOfSync(n.Current()) {
n.Push(outOfSync)
return n
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/v2/service/filter/ro.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (f readOnlyPropertyFilter) tryCopy(nav prop.Navigator, refNav prop.Navigato
return nil
}

if refNav == nil || refNav.Current() == outOfSync {
if refNav == nil || IsOutOfSync(refNav.Current()) {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/v2/service/filter/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (f *validationPropertyFilter) validateCanonical(property prop.Property) err
}

func (f *validationPropertyFilter) validateMutability(property prop.Property, ref prop.Property) error {
if ref == nil || ref == outOfSync {
if ref == nil || IsOutOfSync(ref) {
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/v2/service/filter/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Visit(ctx context.Context, resource *prop.Resource, filters ...ByProperty)
visitFunc: func(resourceNav prop.Navigator, referenceNav prop.Navigator) error {
for _, filter := range filters {
if !filter.Supports(resourceNav.Current().Attribute()) {
return nil
continue
}
if err := filter.Filter(ctx, resource.ResourceType(), resourceNav); err != nil {
return err
Expand Down Expand Up @@ -43,7 +43,7 @@ func VisitWithRef(ctx context.Context, resource *prop.Resource, ref *prop.Resour
visitFunc: func(resourceNav prop.Navigator, referenceNav prop.Navigator) error {
for _, filter := range filters {
if !filter.Supports(resourceNav.Current().Attribute()) {
return nil
continue
}
if err := filter.FilterRef(ctx, resource.ResourceType(), resourceNav, referenceNav); err != nil {
return err
Expand Down
111 changes: 110 additions & 1 deletion pkg/v2/service/filter/visit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,101 @@ type VisitorTestSuite struct {
resourceType *spec.ResourceType
}

func (s *VisitorTestSuite) TestUnsupportedFilterDoesNotPreventRemainingFiltersFromRunning() {
tests := []struct {
name string
getResource func(t *testing.T) *prop.Resource
expect func(t *testing.T, propVisited []prop.Property)
}{
{
name: "traverse resource",
getResource: func(t *testing.T) *prop.Resource {
r := prop.NewResource(s.resourceType)
assert.False(t, r.Navigator().Replace(map[string]interface{}{
"schemas": []interface{}{"A", "B"},
"id": "foobar",
"meta": map[string]interface{}{
"version": "v1",
},
"emails": []interface{}{
map[string]interface{}{
"value": "foo@bar.com",
},
map[string]interface{}{
"value": "bar@foo.com",
},
},
}).HasError())
return r
},
expect: func(t *testing.T, propVisited []prop.Property) {
for i, p := range []string{
"schemas",
"schemas$elem",
"schemas$elem",
"id",
"externalId",
"meta",
"meta.resourceType",
"meta.created",
"meta.lastModified",
"meta.location",
"meta.version",
"urn:ietf:params:scim:schemas:core:2.0:User:userName",
"urn:ietf:params:scim:schemas:core:2.0:User:name",
"urn:ietf:params:scim:schemas:core:2.0:User:name.formatted",
"urn:ietf:params:scim:schemas:core:2.0:User:name.familyName",
"urn:ietf:params:scim:schemas:core:2.0:User:name.givenName",
"urn:ietf:params:scim:schemas:core:2.0:User:name.middleName",
"urn:ietf:params:scim:schemas:core:2.0:User:name.honorificPrefix",
"urn:ietf:params:scim:schemas:core:2.0:User:name.honorificSuffix",
"urn:ietf:params:scim:schemas:core:2.0:User:displayName",
"urn:ietf:params:scim:schemas:core:2.0:User:nickName",
"urn:ietf:params:scim:schemas:core:2.0:User:profileUrl",
"urn:ietf:params:scim:schemas:core:2.0:User:title",
"urn:ietf:params:scim:schemas:core:2.0:User:userType",
"urn:ietf:params:scim:schemas:core:2.0:User:preferredLanguage",
"urn:ietf:params:scim:schemas:core:2.0:User:locale",
"urn:ietf:params:scim:schemas:core:2.0:User:timezone",
"urn:ietf:params:scim:schemas:core:2.0:User:active",
"urn:ietf:params:scim:schemas:core:2.0:User:password",
"urn:ietf:params:scim:schemas:core:2.0:User:emails",
"urn:ietf:params:scim:schemas:core:2.0:User:emails$elem",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.value",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.type",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.primary",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.display",
"urn:ietf:params:scim:schemas:core:2.0:User:emails$elem",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.value",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.type",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.primary",
"urn:ietf:params:scim:schemas:core:2.0:User:emails.display",
"urn:ietf:params:scim:schemas:core:2.0:User:phoneNumbers",
"urn:ietf:params:scim:schemas:core:2.0:User:ims",
"urn:ietf:params:scim:schemas:core:2.0:User:photos",
"urn:ietf:params:scim:schemas:core:2.0:User:addresses",
"urn:ietf:params:scim:schemas:core:2.0:User:groups",
"urn:ietf:params:scim:schemas:core:2.0:User:entitlements",
"urn:ietf:params:scim:schemas:core:2.0:User:roles",
"urn:ietf:params:scim:schemas:core:2.0:User:x509Certificates",
} {
assert.Equal(t, p, propVisited[i].Attribute().ID())
}
},
},
}

for _, test := range tests {
s.T().Run(test.name, func(t *testing.T) {
resource := test.getResource(t)
recordingFilter := recordingPropertyFilter{propHistory: []prop.Property{}}
err := Visit(context.Background(), resource, alwaysUnsupportedFilter{}, &recordingFilter)
assert.Nil(t, err)
test.expect(t, recordingFilter.propHistory)
})
}
}

func (s *VisitorTestSuite) TestVisit() {
tests := []struct {
name string
Expand Down Expand Up @@ -227,7 +322,7 @@ func (s *VisitorTestSuite) TestVisitWithRef() {
visited := make([]compare, 0)
for i, p := range propVisited {
c := compare{prop: p.Attribute().ID()}
if refVisited[i] == outOfSync {
if IsOutOfSync(refVisited[i]) {
c.ref = "outOfSync"
} else {
c.ref = refVisited[i].Attribute().ID()
Expand Down Expand Up @@ -366,3 +461,17 @@ func (f *recordingPropertyFilter) FilterRef(_ context.Context, _ *spec.ResourceT
f.refHistory = append(f.refHistory, refNav.Current())
return nil
}

type alwaysUnsupportedFilter struct{}

func (f alwaysUnsupportedFilter) Supports(_ *spec.Attribute) bool {
return false
}

func (f alwaysUnsupportedFilter) Filter(_ context.Context, _ *spec.ResourceType, _ prop.Navigator) error {
return nil
}

func (f alwaysUnsupportedFilter) FilterRef(_ context.Context, _ *spec.ResourceType, _ prop.Navigator, _ prop.Navigator) error {
return nil
}

0 comments on commit 0df0357

Please sign in to comment.