Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
131 lines
3.6 KiB
Go
131 lines
3.6 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package schema
|
|
|
|
import "github.com/ory/keto/internal/namespace/ast"
|
|
|
|
type (
|
|
namespaceQuery []namespace
|
|
relationQuery []ast.Relation
|
|
typeCheck func(p *parser)
|
|
)
|
|
|
|
func (p *parser) query() namespaceQuery {
|
|
return p.namespaces
|
|
}
|
|
|
|
func (ns namespaceQuery) find(name string) (*namespace, bool) {
|
|
for _, n := range ns {
|
|
if n.Name == name {
|
|
return &n, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (ns namespaceQuery) findRelation(namespace, relation string) (*ast.Relation, bool) {
|
|
n, ok := ns.find(namespace)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return relationQuery(n.Relations).find(relation)
|
|
}
|
|
|
|
func (rs relationQuery) find(name string) (*ast.Relation, bool) {
|
|
for _, r := range rs {
|
|
if r.Name == name {
|
|
return &r, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (p *parser) typeCheck() {
|
|
for _, check := range p.checks {
|
|
check(p)
|
|
}
|
|
}
|
|
|
|
func (p *parser) addCheck(check typeCheck) {
|
|
p.checks = append(p.checks, check)
|
|
}
|
|
|
|
// checkNamespace checks that the there exists a namespace with the given name.
|
|
func checkNamespaceExists(namespace item) typeCheck {
|
|
return func(p *parser) {
|
|
if _, ok := namespaceQuery(p.namespaces).find(namespace.Val); ok {
|
|
return
|
|
}
|
|
p.addErr(namespace, "namespace %q was not declared", namespace.Val)
|
|
}
|
|
}
|
|
|
|
// checkNamespaceHasRelation checks that 1. there exists the given namespace,
|
|
// and 2. that there exists the given relation in that namespace.
|
|
func checkNamespaceHasRelation(namespace, relation item) typeCheck {
|
|
return func(p *parser) {
|
|
if n, ok := namespaceQuery(p.namespaces).find(namespace.Val); ok {
|
|
if _, ok := relationQuery(n.Relations).find(relation.Val); ok {
|
|
return
|
|
}
|
|
p.addErr(relation,
|
|
"namespace %q did not declare relation %q",
|
|
namespace.Val, relation.Val)
|
|
return
|
|
}
|
|
p.addErr(namespace, "namespace %q was not declared", namespace.Val)
|
|
}
|
|
}
|
|
|
|
// checkCurrentNamespaceHasRelation checks that the give relation exists in the
|
|
// current namespace.
|
|
func checkCurrentNamespaceHasRelation(current *namespace, relation item) typeCheck {
|
|
namespace := current.Name
|
|
return func(p *parser) {
|
|
if n, ok := namespaceQuery(p.namespaces).find(namespace); ok {
|
|
if _, ok := relationQuery(n.Relations).find(relation.Val); ok {
|
|
return
|
|
}
|
|
p.addErr(relation,
|
|
"namespace %q did not declare relation %q",
|
|
namespace, relation.Val)
|
|
return
|
|
}
|
|
p.addErr(relation, "namespace %q was not declared", namespace)
|
|
}
|
|
}
|
|
|
|
func checkAllRelationsTypesHaveRelation(current *namespace, relationType item, relation string) typeCheck {
|
|
namespace := current.Name
|
|
return func(p *parser) {
|
|
recursiveCheckAllRelationsTypesHaveRelation(p, relationType, namespace, relationType.Val, relation, tupleToSubjectSetTypeCheckMaxDepth)
|
|
}
|
|
}
|
|
|
|
func recursiveCheckAllRelationsTypesHaveRelation(p *parser, item item, namespace string, relationType string, relation string, depth int) {
|
|
if depth < 0 {
|
|
p.addErr(item, "could not typecheck deeply nested SubjectSet further")
|
|
return
|
|
}
|
|
r, ok := namespaceQuery(p.namespaces).findRelation(namespace, relationType)
|
|
if !ok {
|
|
p.addErr(item, "relation %q was not declared in namespace %q",
|
|
relationType, namespace)
|
|
return
|
|
}
|
|
for _, t := range r.Types {
|
|
if t.Relation == "" {
|
|
if _, ok := p.query().findRelation(t.Namespace, relation); !ok {
|
|
p.addErr(item, "relation %q was not declared in namespace %q",
|
|
relation, t.Namespace)
|
|
}
|
|
} else {
|
|
// Type is a subject set, we need to recursively check if the type has
|
|
// the required relation.
|
|
recursiveCheckAllRelationsTypesHaveRelation(
|
|
p, item, t.Namespace, t.Relation, relation, depth-1)
|
|
}
|
|
}
|
|
}
|