match sites with a url path suffix
Adds suffix matching to the site owner id/name lookup process. This allows consumers to query for a site by only its path (ex: "/sites/foo") and still end up with a well formed id, name tuple. One gotcha is that this requires the lookup maps to be populated. If a lookup map is not passed in with a suffix matcher, the fallback lookup will fail.
This commit is contained in:
parent
702e25bcda
commit
ca2fbda260
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/discovery/api"
|
"github.com/alcionai/corso/src/internal/connector/discovery/api"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
@ -17,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -267,12 +269,17 @@ var ErrResourceOwnerNotFound = clues.New("resource owner not found in tenant")
|
|||||||
|
|
||||||
// getOwnerIDAndNameFrom looks up the owner's canonical id and display name.
|
// getOwnerIDAndNameFrom looks up the owner's canonical id and display name.
|
||||||
// if idToName and nameToID are populated, and the owner is a key of one of
|
// if idToName and nameToID are populated, and the owner is a key of one of
|
||||||
// those maps, then those values are returned. As a fallback, the resource
|
// those maps, then those values are returned.
|
||||||
// calls the discovery api to fetch the user or site using the owner value.
|
//
|
||||||
// This fallback assumes that the owner is a well formed ID or display name
|
// As a fallback, the resource calls the discovery api to fetch the user or
|
||||||
// of appropriate design (PrincipalName for users, WebURL for sites).
|
// site using the owner value. This fallback assumes that the owner is a well
|
||||||
// If the fallback lookup is used, the maps are populated to contain the
|
// formed ID or display name of appropriate design (PrincipalName for users,
|
||||||
// id and name references.
|
// WebURL for sites). If the fallback lookup is used, the maps are populated
|
||||||
|
// to contain the id and name references.
|
||||||
|
//
|
||||||
|
// Consumers are allowed to pass in a path suffix (eg: /sites/foo) as a site
|
||||||
|
// owner, but only if they also pass in a nameToID map. A nil map will cascade
|
||||||
|
// to the fallback, which will fail for having a malformed id value.
|
||||||
func (r resourceClient) getOwnerIDAndNameFrom(
|
func (r resourceClient) getOwnerIDAndNameFrom(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
discovery api.Client,
|
discovery api.Client,
|
||||||
@ -281,8 +288,8 @@ func (r resourceClient) getOwnerIDAndNameFrom(
|
|||||||
) (string, string, error) {
|
) (string, string, error) {
|
||||||
if n, ok := idToName[owner]; ok {
|
if n, ok := idToName[owner]; ok {
|
||||||
return owner, n, nil
|
return owner, n, nil
|
||||||
} else if i, ok := nameToID[owner]; ok {
|
} else if id, ok := nameToID[owner]; ok {
|
||||||
return i, owner, nil
|
return id, owner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = clues.Add(ctx, "owner_identifier", owner)
|
ctx = clues.Add(ctx, "owner_identifier", owner)
|
||||||
@ -292,9 +299,13 @@ func (r resourceClient) getOwnerIDAndNameFrom(
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// if r.enum == Sites {
|
// check if the provided owner is a suffix of a weburl in the lookup map
|
||||||
// TODO: check all suffixes in nameToID
|
if r.enum == Sites {
|
||||||
// }
|
url, _, ok := filters.PathSuffix([]string{owner}).CompareAny(maps.Keys(nameToID)...)
|
||||||
|
if ok {
|
||||||
|
return nameToID[url], url, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id, name, err = r.getter.GetIDAndName(ctx, owner)
|
id, name, err = r.getter.GetIDAndName(ctx, owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -64,7 +64,8 @@ func (suite *GraphConnectorUnitSuite) TestPopulateOwnerIDAndNamesFrom() {
|
|||||||
enum: Users,
|
enum: Users,
|
||||||
getter: &mockNameIDGetter{id: ownerID, name: ownerName},
|
getter: &mockNameIDGetter{id: ownerID, name: ownerName},
|
||||||
}
|
}
|
||||||
noLookup = &resourceClient{enum: Users, getter: &mockNameIDGetter{}}
|
noLookup = &resourceClient{enum: Users, getter: &mockNameIDGetter{}}
|
||||||
|
siteLookup = &resourceClient{enum: Sites, getter: &mockNameIDGetter{}}
|
||||||
)
|
)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -167,6 +168,16 @@ func (suite *GraphConnectorUnitSuite) TestPopulateOwnerIDAndNamesFrom() {
|
|||||||
expectName: "",
|
expectName: "",
|
||||||
expectErr: assert.Error,
|
expectErr: assert.Error,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "site suffix lookup",
|
||||||
|
owner: "/url/path",
|
||||||
|
rc: siteLookup,
|
||||||
|
idToName: nil,
|
||||||
|
nameToID: map[string]string{"http://some/site/url/path": ownerID},
|
||||||
|
expectID: ownerID,
|
||||||
|
expectName: "http://some/site/url/path",
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
|
|||||||
@ -359,21 +359,23 @@ func newSliceFilter(c comparator, targets, normTargets []string, negate bool) Fi
|
|||||||
// ----------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// CompareAny checks whether any one of all the provided
|
// CompareAny checks whether any one of all the provided
|
||||||
// inputs passes the filter.
|
// inputs passes the filter. If one passes, that value is
|
||||||
|
// returned, as well as its index in the input range.
|
||||||
|
// If nothing matches, returns ("", -1, false)
|
||||||
//
|
//
|
||||||
// Note that, as a gotcha, CompareAny can resolve truthily
|
// Note that, as a gotcha, CompareAny can resolve truthily
|
||||||
// for both the standard and negated versions of a filter.
|
// for both the standard and negated versions of a filter.
|
||||||
// Ex: consider the input CompareAny(true, false), which
|
// Ex: consider the input CompareAny(true, false), which
|
||||||
// will return true for both Equals(true) and NotEquals(true),
|
// will return true for both Equals(true) and NotEquals(true),
|
||||||
// because at least one element matches for both filters.
|
// because at least one element matches for both filters.
|
||||||
func (f Filter) CompareAny(inputs ...string) bool {
|
func (f Filter) CompareAny(inputs ...string) (string, int, bool) {
|
||||||
for _, in := range inputs {
|
for i, in := range inputs {
|
||||||
if f.Compare(in) {
|
if f.Compare(in) {
|
||||||
return true
|
return in, i, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return "", -1, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare checks whether the input passes the filter.
|
// Compare checks whether the input passes the filter.
|
||||||
@ -442,6 +444,48 @@ func (f Filter) Compare(input string) bool {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches extends Compare by not only checking if
|
||||||
|
// the input passes the filter, but if it passes, the
|
||||||
|
// target which matched and its index are returned as well.
|
||||||
|
// If more than one value matches the input, only the
|
||||||
|
// first is returned.
|
||||||
|
// returns ("", -1, false) if no match is found.
|
||||||
|
// TODO: only partially implemented.
|
||||||
|
// func (f Filter) Matches(input string) (string, int, bool) {
|
||||||
|
// var (
|
||||||
|
// cmp func(string, string) bool
|
||||||
|
// res bool
|
||||||
|
// targets = f.NormalizedTargets
|
||||||
|
// )
|
||||||
|
|
||||||
|
// switch f.Comparator {
|
||||||
|
// case TargetPathPrefix:
|
||||||
|
// cmp = pathPrefix
|
||||||
|
// case TargetPathContains:
|
||||||
|
// cmp = pathContains
|
||||||
|
// case TargetPathSuffix:
|
||||||
|
// cmp = pathSuffix
|
||||||
|
// case TargetPathEquals:
|
||||||
|
// cmp = pathEquals
|
||||||
|
// default:
|
||||||
|
// return "", -1, false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for i, tgt := range targets {
|
||||||
|
// res = cmp(norm(tgt), norm(input))
|
||||||
|
|
||||||
|
// if !f.Negate && res {
|
||||||
|
// return f.Targets[i], i, true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if f.Negate && !res {
|
||||||
|
// return f.Targets[i], i, true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return "", -1, false
|
||||||
|
// }
|
||||||
|
|
||||||
// true if t == i
|
// true if t == i
|
||||||
func equals(target, input string) bool {
|
func equals(target, input string) bool {
|
||||||
return target == input
|
return target == input
|
||||||
|
|||||||
@ -45,20 +45,49 @@ func (suite *FiltersSuite) TestEquals_any() {
|
|||||||
nf := filters.NotEqual("foo")
|
nf := filters.NotEqual("foo")
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
input []string
|
input []string
|
||||||
expectF assert.BoolAssertionFunc
|
expectF assert.BoolAssertionFunc
|
||||||
expectNF assert.BoolAssertionFunc
|
expectFVal string
|
||||||
|
expectFIdx int
|
||||||
|
expectNF assert.BoolAssertionFunc
|
||||||
|
expectNFVal string
|
||||||
|
expectNFIdx int
|
||||||
}{
|
}{
|
||||||
{"includes target", []string{"foo", "bar"}, assert.True, assert.True},
|
{
|
||||||
{"not includes target", []string{"baz", "qux"}, assert.False, assert.True},
|
name: "includes target",
|
||||||
|
input: []string{"foo", "bar"},
|
||||||
|
expectF: assert.True,
|
||||||
|
expectFVal: "foo",
|
||||||
|
expectFIdx: 0,
|
||||||
|
expectNF: assert.True,
|
||||||
|
expectNFVal: "bar",
|
||||||
|
expectNFIdx: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not includes target",
|
||||||
|
input: []string{"baz", "qux"},
|
||||||
|
expectF: assert.False,
|
||||||
|
expectFVal: "",
|
||||||
|
expectFIdx: -1,
|
||||||
|
expectNF: assert.True,
|
||||||
|
expectNFVal: "baz",
|
||||||
|
expectNFIdx: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
test.expectF(t, f.CompareAny(test.input...), "filter")
|
v, i, b := f.CompareAny(test.input...)
|
||||||
test.expectNF(t, nf.CompareAny(test.input...), "negated filter")
|
test.expectF(t, b, "filter")
|
||||||
|
assert.Equal(t, test.expectFIdx, i, "index")
|
||||||
|
assert.Equal(t, test.expectFVal, v, "value")
|
||||||
|
|
||||||
|
v, i, b = nf.CompareAny(test.input...)
|
||||||
|
test.expectNF(t, b, "neg-filter")
|
||||||
|
assert.Equal(t, test.expectNFIdx, i, "neg-index")
|
||||||
|
assert.Equal(t, test.expectNFVal, v, "neg-value")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -223,7 +223,9 @@ func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return s[cat.String()].CompareAny(inpts...)
|
_, _, pass := s[cat.String()].CompareAny(inpts...)
|
||||||
|
|
||||||
|
return pass
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCategory returns the scope's category value.
|
// getCategory returns the scope's category value.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user