package namespace import ( "context" "github.com/authzed/spicedb/pkg/datastore" "github.com/authzed/spicedb/pkg/genutil/mapz" core "github.com/authzed/spicedb/pkg/proto/core/v1" ) // ReadNamespaceAndRelation checks that the specified namespace and relation exist in the // datastore. // // Returns NamespaceNotFoundError if the namespace cannot be found. // Returns RelationNotFoundError if the relation was not found in the namespace. // Returns the direct downstream error for all other unknown error. func ReadNamespaceAndRelation( ctx context.Context, namespace string, relation string, ds datastore.Reader, ) (*core.NamespaceDefinition, *core.Relation, error) { config, _, err := ds.ReadNamespaceByName(ctx, namespace) if err != nil { return nil, nil, err } for _, rel := range config.Relation { if rel.Name == relation { return config, rel, nil } } return nil, nil, NewRelationNotFoundErr(namespace, relation) } // TypeAndRelationToCheck is a single check of a namespace+relation pair. type TypeAndRelationToCheck struct { // NamespaceName is the namespace name to ensure exists. NamespaceName string // RelationName is the relation name to ensure exists under the namespace. RelationName string // AllowEllipsis, if true, allows for the ellipsis as the RelationName. AllowEllipsis bool } // CheckNamespaceAndRelations ensures that the given namespace+relation checks all succeed. If any fail, returns an error. // // Returns NamespaceNotFoundError if the namespace cannot be found. // Returns RelationNotFoundError if the relation was not found in the namespace. // Returns the direct downstream error for all other unknown error. func CheckNamespaceAndRelations(ctx context.Context, checks []TypeAndRelationToCheck, ds datastore.Reader) error { nsNames := mapz.NewSet[string]() for _, toCheck := range checks { nsNames.Insert(toCheck.NamespaceName) } if nsNames.IsEmpty() { return nil } namespaces, err := ds.LookupNamespacesWithNames(ctx, nsNames.AsSlice()) if err != nil { return err } mappedNamespaces := make(map[string]*core.NamespaceDefinition, len(namespaces)) for _, namespace := range namespaces { mappedNamespaces[namespace.Definition.Name] = namespace.Definition } for _, toCheck := range checks { nsDef, ok := mappedNamespaces[toCheck.NamespaceName] if !ok { return NewNamespaceNotFoundErr(toCheck.NamespaceName) } if toCheck.AllowEllipsis && toCheck.RelationName == datastore.Ellipsis { continue } foundRelation := false for _, rel := range nsDef.Relation { if rel.Name == toCheck.RelationName { foundRelation = true break } } if !foundRelation { return NewRelationNotFoundErr(toCheck.NamespaceName, toCheck.RelationName) } } return nil } // CheckNamespaceAndRelation checks that the specified namespace and relation exist in the // datastore. // // Returns datastore.NamespaceNotFoundError if the namespace cannot be found. // Returns RelationNotFoundError if the relation was not found in the namespace. // Returns the direct downstream error for all other unknown error. func CheckNamespaceAndRelation( ctx context.Context, namespace string, relation string, allowEllipsis bool, ds datastore.Reader, ) error { config, _, err := ds.ReadNamespaceByName(ctx, namespace) if err != nil { return err } if allowEllipsis && relation == datastore.Ellipsis { return nil } for _, rel := range config.Relation { if rel.Name == relation { return nil } } return NewRelationNotFoundErr(namespace, relation) } // ListReferencedNamespaces returns the names of all namespaces referenced in the // given namespace definitions. This includes the namespaces themselves, as well as // any found in type information on relations. func ListReferencedNamespaces(nsdefs []*core.NamespaceDefinition) []string { referencedNamespaceNamesSet := mapz.NewSet[string]() for _, nsdef := range nsdefs { referencedNamespaceNamesSet.Insert(nsdef.Name) for _, relation := range nsdef.Relation { if relation.GetTypeInformation() != nil { for _, allowedRel := range relation.GetTypeInformation().AllowedDirectRelations { referencedNamespaceNamesSet.Insert(allowedRel.GetNamespace()) } } } } return referencedNamespaceNamesSet.AsSlice() }