Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/spicedb/codegen/codegen.go
2498 views
1
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package main
6
7
import (
8
"context"
9
"errors"
10
"fmt"
11
"os"
12
"strings"
13
14
"github.com/authzed/spicedb/pkg/development"
15
"github.com/authzed/spicedb/pkg/namespace"
16
corev1 "github.com/authzed/spicedb/pkg/proto/core/v1"
17
dev_v1 "github.com/authzed/spicedb/pkg/proto/developer/v1"
18
implv1 "github.com/authzed/spicedb/pkg/proto/impl/v1"
19
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
20
"gopkg.in/yaml.v2"
21
)
22
23
type Schema struct {
24
Schema string `yaml:"schema"`
25
}
26
27
func main() {
28
schema := GetCompiledSchema()
29
fmt.Print(GenerateDefinition(schema))
30
}
31
32
func GetCompiledSchema() *compiler.CompiledSchema {
33
// read schemaFile
34
b, err := os.ReadFile("../schema/schema.yaml")
35
if err != nil {
36
panic(err)
37
}
38
39
var schema Schema
40
err = yaml.Unmarshal(b, &schema)
41
if err != nil {
42
panic(err)
43
}
44
45
devCtx, devErrs, err := development.NewDevContext(context.Background(), &dev_v1.RequestContext{
46
Schema: schema.Schema,
47
Relationships: nil,
48
})
49
if err != nil {
50
panic(err)
51
}
52
if devErrs != nil {
53
panic(errors.New(devErrs.InputErrors[0].Message))
54
}
55
return devCtx.CompiledSchema
56
}
57
58
func firstUpper(in string) string {
59
return strings.ToUpper(string(in[0])) + in[1:]
60
}
61
62
func GenerateDefinition(schema *compiler.CompiledSchema) string {
63
64
resource := "export type ResourceType ="
65
resourceTypes := "export const AllResourceTypes: ResourceType[] = ["
66
relation := "export type Relation ="
67
permission := "export type Permission ="
68
other := ""
69
fluentApi := ""
70
71
// list definitions
72
for _, def := range schema.ObjectDefinitions {
73
// make sure the first character is upper case
74
simpleName := firstUpper(def.Name)
75
resourceTypeName := simpleName + "ResourceType"
76
resource += "\n | " + resourceTypeName + ""
77
resourceTypes += "\n \"" + def.Name + "\","
78
other += "\nexport type " + resourceTypeName + " = \"" + def.Name + "\";\n"
79
fluentApi += "\n" + generateFluentAPI(def)
80
// check if relations exists
81
hasRelations := false
82
for _, rel := range def.Relation {
83
if namespace.GetRelationKind(rel) == implv1.RelationMetadata_RELATION {
84
hasRelations = true
85
break
86
}
87
}
88
if hasRelations {
89
relation += "\n | " + simpleName + "Relation"
90
other += "\nexport type " + simpleName + "Relation ="
91
for _, rel := range def.Relation {
92
if namespace.GetRelationKind(rel) == implv1.RelationMetadata_RELATION {
93
other += "\n | \"" + rel.Name + "\""
94
}
95
}
96
other += ";\n"
97
}
98
// check if permissions exists
99
hasPermissions := false
100
for _, rel := range def.Relation {
101
if namespace.GetRelationKind(rel) == implv1.RelationMetadata_PERMISSION {
102
hasPermissions = true
103
break
104
}
105
}
106
if hasPermissions {
107
permission += "\n | " + simpleName + "Permission"
108
// permissions
109
other += "\nexport type " + simpleName + "Permission ="
110
for _, rel := range def.Relation {
111
if namespace.GetRelationKind(rel) == implv1.RelationMetadata_PERMISSION {
112
other += "\n | \"" + rel.Name + "\""
113
}
114
}
115
other += ";\n"
116
}
117
}
118
119
return `
120
/**
121
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
122
* Licensed under the GNU Affero General Public License (AGPL).
123
* See License.AGPL.txt in the project root for license information.
124
*/
125
126
// This file is generated by the spicedb/codegen/codegen.go. Do not edit manually.
127
128
import { v1 } from "@authzed/authzed-node";
129
130
export const InstallationID = "1";
131
132
` + resource + `;
133
134
` + resourceTypes + `
135
];
136
137
` + relation + `;
138
139
` + permission + `;
140
141
` + other + `
142
143
export const rel = {
144
` + fluentApi + `
145
};
146
`
147
}
148
149
func generateFluentAPI(def *corev1.NamespaceDefinition) string {
150
ifElse := func(cond bool, ifTrue string, ifFalse string) string {
151
if cond {
152
return ifTrue
153
}
154
return ifFalse
155
}
156
157
relations := ""
158
for _, rel := range def.Relation {
159
if namespace.GetRelationKind(rel) != implv1.RelationMetadata_RELATION {
160
continue
161
}
162
163
objects := ``
164
for _, t := range rel.TypeInformation.AllowedDirectRelations {
165
if t.GetPublicWildcard() != nil {
166
objects += `get any` + firstUpper(t.Namespace) + `() {
167
return {
168
...result2,
169
subject: {
170
object: {
171
objectType: "` + t.Namespace + `",
172
objectId: "*",
173
},
174
175
},
176
} as v1.Relationship;
177
},`
178
} else {
179
hasRelation := t.GetRelation() != "..."
180
name := ifElse(hasRelation, t.Namespace+"_"+t.GetRelation(), t.Namespace)
181
objects += `
182
` + ifElse(t.Namespace == "installation",
183
"get "+name+"()",
184
name+"(objectId: string)") + ` {
185
return {
186
...result2,
187
subject: {
188
object: {
189
objectType: "` + t.Namespace + `",
190
objectId: ` + ifElse(t.Namespace == "installation", `InstallationID`, "objectId") + `,
191
},
192
` + ifElse(hasRelation, `optionalRelation: "`+t.GetRelation()+`",`, "") + `
193
},
194
} as v1.Relationship;
195
},`
196
}
197
}
198
199
relations += `
200
get ` + rel.Name + `() {
201
const result2 = {
202
...result,
203
relation: "` + rel.Name + `",
204
};
205
return {
206
` + objects + `
207
};
208
},
209
210
`
211
}
212
213
return `
214
` + ifElse(def.Name == "installation",
215
`get `+def.Name+`() {`,
216
``+def.Name+`(id: string) {`) + `
217
const result: Partial<v1.Relationship> = {
218
resource: {
219
objectType: "` + def.Name + `",
220
objectId: ` + ifElse(def.Name == "installation", `InstallationID`, "id") + `
221
},
222
};
223
return {
224
` + relations + `
225
};
226
},
227
`
228
}
229
230