Skip to content

Commit

Permalink
refactor: generalize tree structure
Browse files Browse the repository at this point in the history
This will allow reusing the tree to provide debug info on how a check decision was reached.

Co-authored-by: Henning Perl <hperl@users.noreply.github.com>
  • Loading branch information
zepatrik and hperl committed Aug 19, 2022
1 parent 25e97f5 commit 6a0b2fe
Show file tree
Hide file tree
Showing 28 changed files with 598 additions and 346 deletions.
4 changes: 2 additions & 2 deletions cmd/expand/root.go
Expand Up @@ -45,9 +45,9 @@ func NewExpandCmd() *cobra.Command {
return cmdx.FailSilently(cmd)
}

var tree *ketoapi.ExpandTree
var tree *ketoapi.Tree[*ketoapi.RelationTuple]
if resp.Tree != nil {
tree = (&ketoapi.ExpandTree{}).FromProto(resp.Tree)
tree = ketoapi.TreeFromProto[*ketoapi.RelationTuple](resp.Tree)
}

cmdx.PrintJSONAble(cmd, tree)
Expand Down
@@ -1,46 +1,76 @@
{
"type": "union",
"children": [
{
"type": "union",
"children": [
{
"type": "leaf",
"subject_set": {
"namespace": "directories",
"object": "/photos",
"relation": "owner"
}
},
{
"type": "leaf",
"subject_id": "laura"
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_id": "maureen"
},
"type": "leaf"
}
],
"subject_set": {
"namespace": "directories",
"object": "/photos",
"relation": "access"
}
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_set": {
"namespace": "files",
"object": "/photos/beach.jpg",
"relation": "owner"
}
},
"type": "union"
},
{
"type": "union",
"children": [
{
"type": "leaf",
"subject_id": "maureen"
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_id": "laura"
},
"type": "leaf"
},
{
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_set": {
"namespace": "directories",
"object": "/photos",
"relation": "owner"
}
},
"type": "leaf"
}
],
"subject_set": {
"namespace": "files",
"object": "/photos/beach.jpg",
"relation": "owner"
}
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_set": {
"namespace": "directories",
"object": "/photos",
"relation": "access"
}
},
"type": "union"
}
],
"subject_set": {
"namespace": "files",
"object": "/photos/beach.jpg",
"relation": "access"
}
"tuple": {
"namespace": "",
"object": "",
"relation": "",
"subject_set": {
"namespace": "files",
"object": "/photos/beach.jpg",
"relation": "access"
}
},
"type": "union"
}
Expand Up @@ -35,16 +35,23 @@ const subjectJSON = (subject) => {

// helper to get a nice result
const prettyTree = (tree) => {
const [nodeType, subject, children] = [
const [nodeType, tuple, children] = [
tree.getNodeType(),
subjectJSON(tree.getSubject()),
{
tuple: {
namespace: "",
object: "",
relation: "",
...subjectJSON(tree.getSubject()),
},
},
tree.getChildrenList(),
]
switch (nodeType) {
case expand.NodeType.NODE_TYPE_LEAF:
return { type: "leaf", ...subject }
return { type: "leaf", ...tuple }
case expand.NodeType.NODE_TYPE_UNION:
return { type: "union", children: children.map(prettyTree), ...subject }
return { type: "union", children: children.map(prettyTree), ...tuple }
}
}

Expand Down
Expand Up @@ -28,7 +28,7 @@ func main() {
panic(err)
}

tree := (&ketoapi.ExpandTree{}).FromProto(res.Tree)
tree := ketoapi.TreeFromProto[*ketoapi.RelationTuple](res.Tree)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
Expand Down
25 changes: 16 additions & 9 deletions contrib/docs-code-samples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 18 additions & 14 deletions internal/e2e/cases_test.go
Expand Up @@ -57,36 +57,40 @@ func runCases(c client, m *namespaceTestManager) func(*testing.T) {
rel := "expand"

subjects := []string{"s1", "s2"}
expectedTree := &ketoapi.ExpandTree{
Type: ketoapi.ExpandNodeUnion,
SubjectSet: &ketoapi.SubjectSet{
Namespace: n.Name,
Object: obj,
Relation: rel,
expectedTree := &ketoapi.Tree[*ketoapi.RelationTuple]{
Type: ketoapi.TreeNodeUnion,
Tuple: &ketoapi.RelationTuple{
SubjectSet: &ketoapi.SubjectSet{
Namespace: n.Name,
Object: obj,
Relation: rel,
},
},
Children: make([]*ketoapi.ExpandTree, len(subjects)),
Children: make([]*ketoapi.Tree[*ketoapi.RelationTuple], len(subjects)),
}

for i, subjectID := range subjects {
subjectID := subjectID
c.createTuple(t, &ketoapi.RelationTuple{
Namespace: n.Name,
Object: obj,
Relation: rel,
SubjectID: &subjectID,
})
expectedTree.Children[i] = &ketoapi.ExpandTree{
Type: ketoapi.ExpandNodeLeaf,
SubjectID: &subjectID,
expectedTree.Children[i] = &ketoapi.Tree[*ketoapi.RelationTuple]{
Type: ketoapi.TreeNodeLeaf,
Tuple: &ketoapi.RelationTuple{
SubjectID: &subjectID,
},
}
}

actualTree := c.expand(t, expectedTree.SubjectSet, 100)
actualTree := c.expand(t, expectedTree.Tuple.SubjectSet, 100)

assert.Equal(t, expectedTree.Type, actualTree.Type)
assert.Equal(t, expectedTree.SubjectSet, actualTree.SubjectSet)
assert.Equal(t, expectedTree.SubjectID, actualTree.SubjectID)
assert.Equalf(t, expectedTree.Tuple, actualTree.Tuple,
"want:\t%s\ngot:\t%s", expectedTree.Tuple, actualTree.Tuple)
assert.Equal(t, len(expectedTree.Children), len(actualTree.Children), "expected: %+v; actual: %+v", expectedTree.Children, actualTree.Children)

expand.AssertExternalTreesAreEqual(t, expectedTree, actualTree)
})

Expand Down
4 changes: 2 additions & 2 deletions internal/e2e/cli_client_test.go
Expand Up @@ -99,9 +99,9 @@ func (g *cliClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool {
return res.Allowed
}

func (g *cliClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree {
func (g *cliClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] {
out := g.c.ExecNoErr(t, "expand", r.Relation, r.Namespace, r.Object, "--"+cliexpand.FlagMaxDepth, fmt.Sprintf("%d", depth), "--"+cmdx.FlagFormat, string(cmdx.FormatJSON))
res := ketoapi.ExpandTree{}
res := ketoapi.Tree[*ketoapi.RelationTuple]{}
require.NoError(t, json.Unmarshal([]byte(out), &res))
return &res
}
Expand Down
2 changes: 1 addition & 1 deletion internal/e2e/full_suit_test.go
Expand Up @@ -35,7 +35,7 @@ type (
queryTuple(t require.TestingT, q *ketoapi.RelationQuery, opts ...x.PaginationOptionSetter) *ketoapi.GetResponse
queryTupleErr(t require.TestingT, expected herodot.DefaultError, q *ketoapi.RelationQuery, opts ...x.PaginationOptionSetter)
check(t require.TestingT, r *ketoapi.RelationTuple) bool
expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree
expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple]
waitUntilLive(t require.TestingT)
}
)
Expand Down
4 changes: 2 additions & 2 deletions internal/e2e/grpc_client_test.go
Expand Up @@ -129,7 +129,7 @@ func (g *grpcClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool {
return resp.Allowed
}

func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree {
func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] {
c := rts.NewExpandServiceClient(g.readConn(t))

resp, err := c.Expand(g.ctx, &rts.ExpandRequest{
Expand All @@ -138,7 +138,7 @@ func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int
})
require.NoError(t, err)

return (&ketoapi.ExpandTree{}).FromProto(resp.Tree)
return ketoapi.TreeFromProto[*ketoapi.RelationTuple](resp.Tree)
}

func (g *grpcClient) waitUntilLive(t require.TestingT) {
Expand Down
4 changes: 2 additions & 2 deletions internal/e2e/rest_client_test.go
Expand Up @@ -140,14 +140,14 @@ func (rc *restClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool {
return false
}

func (rc *restClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree {
func (rc *restClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] {
query := r.ToURLQuery()
query.Set("max-depth", fmt.Sprintf("%d", depth))

body, code := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", expand.RouteBase, query.Encode()), "", false)
require.Equal(t, http.StatusOK, code, body)

tree := &ketoapi.ExpandTree{}
tree := &ketoapi.Tree[*ketoapi.RelationTuple]{}
require.NoError(t, json.Unmarshal([]byte(body), tree))

return tree
Expand Down
18 changes: 9 additions & 9 deletions internal/expand/engine.go
Expand Up @@ -38,14 +38,14 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r
restDepth = globalMaxDepth
}

if us, isUserSet := subject.(*relationtuple.SubjectSet); isUserSet {
ctx, wasAlreadyVisited := graph.CheckAndAddVisited(ctx, subject.UniqueID())
if subSet, isSubjectSet := subject.(*relationtuple.SubjectSet); isSubjectSet {
ctx, wasAlreadyVisited := graph.CheckAndAddVisited(ctx, subject)
if wasAlreadyVisited {
return nil, nil
}

subTree := &relationtuple.Tree{
Type: ketoapi.ExpandNodeUnion,
Type: ketoapi.TreeNodeUnion,
Subject: subject,
}

Expand All @@ -59,9 +59,9 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r
rels, nextPage, err = e.d.RelationTupleManager().GetRelationTuples(
ctx,
&relationtuple.RelationQuery{
Relation: &us.Relation,
Object: &us.Object,
Namespace: &us.Namespace,
Relation: &subSet.Relation,
Object: &subSet.Object,
Namespace: &subSet.Namespace,
},
x.WithToken(nextPage),
)
Expand All @@ -72,7 +72,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r
}

if restDepth <= 1 {
subTree.Type = ketoapi.ExpandNodeLeaf
subTree.Type = ketoapi.TreeNodeLeaf
return subTree, nil
}

Expand All @@ -84,7 +84,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r
}
if child == nil {
child = &relationtuple.Tree{
Type: ketoapi.ExpandNodeLeaf,
Type: ketoapi.TreeNodeLeaf,
Subject: r.Subject,
}
}
Expand All @@ -98,7 +98,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r

// is SubjectID
return &relationtuple.Tree{
Type: ketoapi.ExpandNodeLeaf,
Type: ketoapi.TreeNodeLeaf,
Subject: subject,
}, nil
}

0 comments on commit 6a0b2fe

Please sign in to comment.