diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index b9003ef8c..6ad563d14 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -18,6 +18,7 @@ import ( "github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/fault" + "github.com/alcionai/corso/src/pkg/filters" ) // --------------------------------------------------------------------------- @@ -233,6 +234,10 @@ type getOwnerIDAndNamer interface { // 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 of appropriate design // (PrincipalName for users, WebURL for sites). +// +// 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( ctx context.Context, discovery api.Client, @@ -254,9 +259,14 @@ func (r resourceClient) getOwnerIDAndNameFrom( err error ) - // if r.enum == Sites { - // TODO: check all suffixes in nameToID - // } + // check if the provided owner is a suffix of a weburl in the lookup map + if r.enum == Sites { + url, _, ok := filters.PathSuffix([]string{owner}).CompareAny(ins.Names()...) + if ok { + id, _ := ins.IDOf(url) + return id, url, nil + } + } id, name, err = r.getter.GetIDAndName(ctx, owner) if err != nil { diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index 746cf1fbd..cf79b3bc2 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -65,7 +65,8 @@ func (suite *GraphConnectorUnitSuite) TestPopulateOwnerIDAndNamesFrom() { enum: Users, getter: &mockNameIDGetter{id: id, name: name}, } - noLookup = &resourceClient{enum: Users, getter: &mockNameIDGetter{}} + noLookup = &resourceClient{enum: Users, getter: &mockNameIDGetter{}} + siteLookup = &resourceClient{enum: Sites, getter: &mockNameIDGetter{}} ) table := []struct { @@ -249,6 +250,18 @@ func (suite *GraphConnectorUnitSuite) TestPopulateOwnerIDAndNamesFrom() { expectName: name, expectErr: require.NoError, }, + { + name: "site suffix lookup", + owner: "/url/path", + rc: siteLookup, + ins: common.IDsNames{ + IDToName: nil, + NameToID: map[string]string{"http://some/site/url/path": id}, + }, + expectID: id, + expectName: "http://some/site/url/path", + expectErr: require.NoError, + }, } for _, test := range table { suite.Run(test.name, func() { diff --git a/src/pkg/filters/filters.go b/src/pkg/filters/filters.go index e05134592..36a8fd106 100644 --- a/src/pkg/filters/filters.go +++ b/src/pkg/filters/filters.go @@ -359,21 +359,23 @@ func newSliceFilter(c comparator, targets, normTargets []string, negate bool) Fi // ---------------------------------------------------------------------------------------------------- // 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 // for both the standard and negated versions of a filter. // Ex: consider the input CompareAny(true, false), which // will return true for both Equals(true) and NotEquals(true), // because at least one element matches for both filters. -func (f Filter) CompareAny(inputs ...string) bool { - for _, in := range inputs { +func (f Filter) CompareAny(inputs ...string) (string, int, bool) { + for i, in := range inputs { if f.Compare(in) { - return true + return in, i, true } } - return false + return "", -1, false } // Compare checks whether the input passes the filter. @@ -442,6 +444,48 @@ func (f Filter) Compare(input string) bool { 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 func equals(target, input string) bool { return strings.EqualFold(target, input) diff --git a/src/pkg/filters/filters_test.go b/src/pkg/filters/filters_test.go index 7ee32e3e0..fdb691016 100644 --- a/src/pkg/filters/filters_test.go +++ b/src/pkg/filters/filters_test.go @@ -45,20 +45,49 @@ func (suite *FiltersSuite) TestEquals_any() { nf := filters.NotEqual("foo") table := []struct { - name string - input []string - expectF assert.BoolAssertionFunc - expectNF assert.BoolAssertionFunc + name string + input []string + expectF 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 { suite.Run(test.name, func() { t := suite.T() - test.expectF(t, f.CompareAny(test.input...), "filter") - test.expectNF(t, nf.CompareAny(test.input...), "negated filter") + v, i, b := f.CompareAny(test.input...) + 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") }) } } diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index dca906705..3446c4463 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -223,7 +223,9 @@ func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool { return false } - return s[cat.String()].CompareAny(inpts...) + _, _, pass := s[cat.String()].CompareAny(inpts...) + + return pass } // getCategory returns the scope's category value.