1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
package commands
import (
"errors"
"strings"
"github.com/spf13/cobra"
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/zed/internal/client"
)
type CompletionArgumentType int
const (
ResourceType CompletionArgumentType = iota
ResourceID
Permission
SubjectType
SubjectID
SubjectTypeWithOptionalRelation
)
func FileExtensionCompletions(extension ...string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return extension, cobra.ShellCompDirectiveFilterFileExt
}
}
func GetArgs(fields ...CompletionArgumentType) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Read the current schema, if any.
schema, err := readSchema(cmd)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
// Find the specified resource type, if any.
var resourceType string
loop:
for index, arg := range args {
field := fields[index]
switch field {
case ResourceType:
resourceType = arg
break loop
case ResourceID:
pieces := strings.Split(arg, ":")
if len(pieces) >= 1 {
resourceType = pieces[0]
break loop
}
}
}
// Handle : on resource and subject IDs.
if strings.HasSuffix(toComplete, ":") && (fields[len(args)] == ResourceID || fields[len(args)] == SubjectID) {
comps := []string{}
comps = cobra.AppendActiveHelp(comps, "Please enter an object ID")
return comps, cobra.ShellCompDirectiveNoFileComp
}
// Handle # on subject types. If the toComplete contains a valid subject,
// then we should return the relation names. Note that we cannot do this
// on the # character because shell autocompletion won't send it to us.
if len(args) == len(fields)-1 && toComplete != "" && fields[len(args)] == SubjectTypeWithOptionalRelation {
for _, objDef := range schema.ObjectDefinitions {
subjectType := toComplete
if objDef.Name == subjectType {
relationNames := make([]string, 0)
relationNames = append(relationNames, subjectType)
for _, relation := range objDef.Relation {
relationNames = append(relationNames, subjectType+"#"+relation.Name)
}
return relationNames, cobra.ShellCompDirectiveNoFileComp
}
}
}
if len(args) >= len(fields) {
// If we have all the arguments, return no completions.
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Return the completions.
currentFieldType := fields[len(args)]
switch currentFieldType {
case ResourceType:
fallthrough
case SubjectType:
fallthrough
case SubjectID:
fallthrough
case SubjectTypeWithOptionalRelation:
fallthrough
case ResourceID:
resourceTypeNames := make([]string, 0, len(schema.ObjectDefinitions))
for _, objDef := range schema.ObjectDefinitions {
resourceTypeNames = append(resourceTypeNames, objDef.Name)
}
flags := cobra.ShellCompDirectiveNoFileComp
if currentFieldType == ResourceID || currentFieldType == SubjectID || currentFieldType == SubjectTypeWithOptionalRelation {
flags |= cobra.ShellCompDirectiveNoSpace
}
return resourceTypeNames, flags
case Permission:
if resourceType == "" {
return nil, cobra.ShellCompDirectiveNoFileComp
}
relationNames := make([]string, 0)
for _, objDef := range schema.ObjectDefinitions {
if objDef.Name == resourceType {
for _, relation := range objDef.Relation {
relationNames = append(relationNames, relation.Name)
}
}
}
return relationNames, cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveDefault
}
}
func readSchema(cmd *cobra.Command) (*compiler.CompiledSchema, error) {
// TODO: we should find a way to cache this
client, err := client.NewClient(cmd)
if err != nil {
return nil, err
}
request := &v1.ReadSchemaRequest{}
resp, err := client.ReadSchema(cmd.Context(), request)
if err != nil {
return nil, err
}
schemaText := resp.SchemaText
if len(schemaText) == 0 {
return nil, errors.New("no schema defined")
}
compiledSchema, err := compiler.Compile(
compiler.InputSchema{Source: "schema", SchemaString: schemaText},
compiler.AllowUnprefixedObjectType(),
compiler.SkipValidation(),
)
if err != nil {
return nil, err
}
return compiledSchema, nil
}
|