1use std::{
4 collections::{HashMap, HashSet},
5 slice,
6 sync::Arc,
7};
8
9use thiserror::Error;
10
11use crate::{
12 domain::{ObjectType, RelationName, Relationship, SubjectRef},
13 error::ZanzibarError,
14 model::{NamespaceConfig, UsersetExpression as LegacyUsersetExpression},
15 parser::{self, LegacyNamespaceAst, LegacyRelationAst},
16};
17
18#[cfg_attr(
20 feature = "serde",
21 derive(serde::Serialize, serde::Deserialize),
22 serde(rename_all = "camelCase", deny_unknown_fields)
23)]
24#[derive(Debug, Clone, Copy)]
25pub struct SchemaSource<'a> {
26 pub name: Option<&'a str>,
28 pub text: &'a str,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Error)]
34pub enum SchemaError {
35 #[error("duplicate namespace definition '{namespace}'")]
37 DuplicateNamespace {
38 namespace: String,
40 },
41
42 #[error("duplicate relation '{relation}' in namespace '{namespace}'")]
44 DuplicateRelation {
45 namespace: String,
47 relation: String,
49 },
50
51 #[error("namespace '{namespace}' not found")]
53 NamespaceNotFound {
54 namespace: String,
56 },
57
58 #[error("relation '{relation}' not found in namespace '{namespace}'")]
60 RelationNotFound {
61 namespace: String,
63 relation: String,
65 },
66
67 #[error(
69 "relation '{relation}' in '{namespace}.{owner}' references missing relation '{missing}'"
70 )]
71 MissingRelationReference {
72 namespace: String,
74 owner: String,
76 relation: &'static str,
78 missing: String,
80 },
81
82 #[error(
84 "tuple-to-userset in '{namespace}.{owner}' references unavailable target relation \
85 '{missing}'"
86 )]
87 MissingTupleToUsersetTarget {
88 namespace: String,
90 owner: String,
92 missing: String,
94 },
95
96 #[error("{operator} in '{namespace}.{owner}' must contain at least {min_operands} operands")]
98 EmptyExpression {
99 namespace: String,
101 owner: String,
103 operator: &'static str,
105 min_operands: usize,
107 },
108}
109
110#[derive(Debug, Clone)]
112pub struct CompiledSchema {
113 definitions: Arc<[NamespaceDefinition]>,
114 resolver: SchemaResolver,
115}
116
117impl CompiledSchema {
118 fn new(definitions: Arc<[NamespaceDefinition]>) -> Result<Self, SchemaError> {
119 let resolver = SchemaResolver::new(Arc::clone(&definitions))?;
120 Ok(Self {
121 definitions,
122 resolver,
123 })
124 }
125
126 pub fn from_definitions(
133 definitions: impl IntoIterator<Item = NamespaceDefinition>,
134 ) -> Result<Self, SchemaError> {
135 let definitions = definitions.into_iter().collect::<Vec<_>>();
136 let compiled = Self::new(Arc::from(definitions.into_boxed_slice()))?;
137 validate_references(&compiled)?;
138 Self::new(Arc::from(
139 compile_resolved_definitions(&compiled)?.into_boxed_slice(),
140 ))
141 }
142
143 #[must_use]
145 pub fn definitions(&self) -> &[NamespaceDefinition] {
146 &self.definitions
147 }
148
149 #[must_use]
151 pub fn resolver(&self) -> &SchemaResolver {
152 &self.resolver
153 }
154
155 pub fn validate_relationship(&self, relationship: &Relationship) -> Result<(), SchemaError> {
162 self.resolver.relation(
163 relationship.resource().object_type(),
164 relationship.relation(),
165 )?;
166 if let SubjectRef::Userset { object, relation } = relationship.subject() {
167 self.resolver.relation(object.object_type(), relation)?;
168 }
169 Ok(())
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct NamespaceDefinition {
176 name: ObjectType,
177 relations: Arc<[RelationDefinition]>,
178}
179
180impl NamespaceDefinition {
181 #[must_use]
183 pub fn new(name: ObjectType, relations: Arc<[RelationDefinition]>) -> Self {
184 Self { name, relations }
185 }
186
187 #[must_use]
189 pub fn name(&self) -> &ObjectType {
190 &self.name
191 }
192
193 #[must_use]
195 pub fn relations(&self) -> &[RelationDefinition] {
196 &self.relations
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct RelationDefinition {
203 name: RelationName,
204 allowed_subject_types: AllowedSubjectTypes,
205 userset_rewrite: Option<UsersetExpression>,
206 compiled_userset_rewrite: Option<CompiledUsersetExpression>,
207}
208
209impl RelationDefinition {
210 #[must_use]
212 pub fn new(name: RelationName, userset_rewrite: Option<UsersetExpression>) -> Self {
213 Self {
214 name,
215 allowed_subject_types: AllowedSubjectTypes::Unspecified,
216 userset_rewrite,
217 compiled_userset_rewrite: None,
218 }
219 }
220
221 #[must_use]
223 pub fn with_allowed_subject_types(
224 name: RelationName,
225 allowed_subject_types: Arc<[ObjectType]>,
226 userset_rewrite: Option<UsersetExpression>,
227 ) -> Self {
228 Self {
229 name,
230 allowed_subject_types: AllowedSubjectTypes::Explicit(allowed_subject_types),
231 userset_rewrite,
232 compiled_userset_rewrite: None,
233 }
234 }
235
236 #[must_use]
238 pub fn name(&self) -> &RelationName {
239 &self.name
240 }
241
242 #[must_use]
244 pub fn allowed_subject_types(&self) -> &AllowedSubjectTypes {
245 &self.allowed_subject_types
246 }
247
248 #[must_use]
250 pub fn userset_rewrite(&self) -> Option<&UsersetExpression> {
251 self.userset_rewrite.as_ref()
252 }
253
254 pub(crate) fn compiled_userset_rewrite(&self) -> Option<&CompiledUsersetExpression> {
255 self.compiled_userset_rewrite.as_ref()
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum AllowedSubjectTypes {
262 Unspecified,
264 Explicit(Arc<[ObjectType]>),
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
270pub enum UsersetExpression {
271 This,
273 ComputedUserset {
275 relation: RelationName,
277 },
278 TupleToUserset {
280 tupleset_relation: RelationName,
282 computed_userset_relation: RelationName,
284 },
285 Union(Vec<UsersetExpression>),
287 Intersection(Vec<UsersetExpression>),
289 Exclusion {
291 base: Box<UsersetExpression>,
293 exclude: Box<UsersetExpression>,
295 },
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
300pub(crate) struct SchemaRelationId {
301 namespace_index: usize,
302 relation_index: usize,
303}
304
305impl SchemaRelationId {
306 const fn new(namespace_index: usize, relation_index: usize) -> Self {
307 Self {
308 namespace_index,
309 relation_index,
310 }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
316pub(crate) enum CompiledUsersetExpression {
317 This,
319 ComputedUserset {
321 relation: RelationName,
323 relation_id: SchemaRelationId,
325 target_has_rewrite: bool,
327 },
328 TupleToUserset {
330 tupleset_relation: RelationName,
332 tupleset_relation_id: SchemaRelationId,
334 computed_userset_relation: RelationName,
338 },
339 Union(Vec<CompiledUsersetExpression>),
341 Intersection(Vec<CompiledUsersetExpression>),
343 Exclusion {
345 base: Box<CompiledUsersetExpression>,
347 exclude: Box<CompiledUsersetExpression>,
349 },
350}
351
352#[derive(Debug, Clone)]
354pub struct SchemaResolver {
355 definitions: Arc<[NamespaceDefinition]>,
356 namespace_indexes: HashMap<ObjectType, usize>,
357 relation_indexes: HashMap<ObjectType, HashMap<RelationName, usize>>,
358 sorted_relation_indexes: HashMap<ObjectType, Arc<[usize]>>,
359}
360
361impl SchemaResolver {
362 fn new(definitions: Arc<[NamespaceDefinition]>) -> Result<Self, SchemaError> {
363 let mut namespace_indexes = HashMap::with_capacity(definitions.len());
364 let mut relation_indexes = HashMap::with_capacity(definitions.len());
365 let mut sorted_relation_indexes = HashMap::with_capacity(definitions.len());
366
367 for (namespace_index, namespace) in definitions.iter().enumerate() {
368 let previous = namespace_indexes.insert(namespace.name.clone(), namespace_index);
369 if previous.is_some() {
370 return Err(SchemaError::DuplicateNamespace {
371 namespace: namespace.name.to_string(),
372 });
373 }
374
375 let mut relations = HashMap::with_capacity(namespace.relations.len());
376 for (relation_index, relation) in namespace.relations.iter().enumerate() {
377 let previous = relations.insert(relation.name.clone(), relation_index);
378 if previous.is_some() {
379 return Err(SchemaError::DuplicateRelation {
380 namespace: namespace.name.to_string(),
381 relation: relation.name.to_string(),
382 });
383 }
384 }
385 let mut sorted_indexes = (0..namespace.relations.len()).collect::<Vec<_>>();
386 sorted_indexes.sort_by(|left, right| {
387 namespace
388 .relations
389 .get(*left)
390 .map(RelationDefinition::name)
391 .cmp(
392 &namespace
393 .relations
394 .get(*right)
395 .map(RelationDefinition::name),
396 )
397 });
398 sorted_relation_indexes.insert(
399 namespace.name.clone(),
400 Arc::from(sorted_indexes.into_boxed_slice()),
401 );
402 relation_indexes.insert(namespace.name.clone(), relations);
403 }
404
405 Ok(Self {
406 definitions,
407 namespace_indexes,
408 relation_indexes,
409 sorted_relation_indexes,
410 })
411 }
412
413 pub fn namespace(&self, object_type: &ObjectType) -> Result<&NamespaceDefinition, SchemaError> {
419 let index = self.namespace_indexes.get(object_type).ok_or_else(|| {
420 SchemaError::NamespaceNotFound {
421 namespace: object_type.to_string(),
422 }
423 })?;
424 self.definitions
425 .get(*index)
426 .ok_or_else(|| SchemaError::NamespaceNotFound {
427 namespace: object_type.to_string(),
428 })
429 }
430
431 pub fn relation(
438 &self,
439 object_type: &ObjectType,
440 relation: &RelationName,
441 ) -> Result<&RelationDefinition, SchemaError> {
442 let namespace = self.namespace(object_type)?;
443 let relations = self.relation_indexes.get(object_type).ok_or_else(|| {
444 SchemaError::NamespaceNotFound {
445 namespace: object_type.to_string(),
446 }
447 })?;
448 let relation_index =
449 relations
450 .get(relation)
451 .ok_or_else(|| SchemaError::RelationNotFound {
452 namespace: object_type.to_string(),
453 relation: relation.to_string(),
454 })?;
455 namespace
456 .relations
457 .get(*relation_index)
458 .ok_or_else(|| SchemaError::RelationNotFound {
459 namespace: object_type.to_string(),
460 relation: relation.to_string(),
461 })
462 }
463
464 pub(crate) fn relation_id(
465 &self,
466 object_type: &ObjectType,
467 relation: &RelationName,
468 ) -> Result<SchemaRelationId, SchemaError> {
469 let namespace_index = self
470 .namespace_indexes
471 .get(object_type)
472 .copied()
473 .ok_or_else(|| SchemaError::NamespaceNotFound {
474 namespace: object_type.to_string(),
475 })?;
476 let relations = self.relation_indexes.get(object_type).ok_or_else(|| {
477 SchemaError::NamespaceNotFound {
478 namespace: object_type.to_string(),
479 }
480 })?;
481 let relation_index =
482 relations
483 .get(relation)
484 .copied()
485 .ok_or_else(|| SchemaError::RelationNotFound {
486 namespace: object_type.to_string(),
487 relation: relation.to_string(),
488 })?;
489 Ok(SchemaRelationId::new(namespace_index, relation_index))
490 }
491
492 pub(crate) fn relation_by_id(
493 &self,
494 relation_id: SchemaRelationId,
495 ) -> Option<&RelationDefinition> {
496 self.definitions
497 .get(relation_id.namespace_index)
498 .and_then(|namespace| namespace.relations().get(relation_id.relation_index))
499 }
500
501 pub(crate) fn sorted_relations(
502 &self,
503 object_type: &ObjectType,
504 ) -> Result<SortedRelations<'_>, SchemaError> {
505 let namespace = self.namespace(object_type)?;
506 let indexes = self
507 .sorted_relation_indexes
508 .get(object_type)
509 .ok_or_else(|| SchemaError::NamespaceNotFound {
510 namespace: object_type.to_string(),
511 })?;
512 Ok(SortedRelations {
513 namespace,
514 indexes: indexes.iter(),
515 })
516 }
517
518 fn relation_exists_anywhere(&self, relation: &RelationName) -> bool {
519 self.relation_indexes
520 .values()
521 .any(|relations| relations.contains_key(relation))
522 }
523}
524
525#[derive(Debug)]
527pub(crate) struct SortedRelations<'a> {
528 namespace: &'a NamespaceDefinition,
529 indexes: slice::Iter<'a, usize>,
530}
531
532impl<'a> Iterator for SortedRelations<'a> {
533 type Item = &'a RelationDefinition;
534
535 fn next(&mut self) -> Option<Self::Item> {
536 for index in self.indexes.by_ref() {
537 if let Some(relation) = self.namespace.relations().get(*index) {
538 return Some(relation);
539 }
540 }
541 None
542 }
543}
544
545pub fn compile_legacy_dsl(source: &str) -> Result<CompiledSchema, ZanzibarError> {
552 compile_legacy_ast(parser::parse_dsl_ast(source)?)
553}
554
555pub fn compile_legacy_configs(
562 configs: impl IntoIterator<Item = NamespaceConfig>,
563) -> Result<CompiledSchema, ZanzibarError> {
564 let mut namespaces = Vec::new();
565 for config in configs {
566 let mut relations = Vec::new();
567 for relation in config.relations.into_values() {
568 relations.push(LegacyRelationAst {
569 name: relation.name.0,
570 rewrite: relation.userset_rewrite,
571 });
572 }
573 namespaces.push(LegacyNamespaceAst {
574 name: config.name,
575 relations,
576 });
577 }
578 compile_legacy_ast(namespaces)
579}
580
581fn compile_legacy_ast(
582 namespaces: Vec<LegacyNamespaceAst>,
583) -> Result<CompiledSchema, ZanzibarError> {
584 let mut definitions = Vec::with_capacity(namespaces.len());
585 for namespace in namespaces {
586 let mut relations = Vec::with_capacity(namespace.relations.len());
587 for relation in namespace.relations {
588 relations.push(compile_legacy_relation(relation)?);
589 }
590 definitions.push(NamespaceDefinition::new(
591 ObjectType::try_from(namespace.name.as_str())?,
592 Arc::from(relations.into_boxed_slice()),
593 ));
594 }
595
596 CompiledSchema::from_definitions(definitions).map_err(Into::into)
597}
598
599fn compile_legacy_relation(
600 relation: LegacyRelationAst,
601) -> Result<RelationDefinition, ZanzibarError> {
602 Ok(RelationDefinition::new(
603 RelationName::try_from(relation.name.as_str())?,
604 relation
605 .rewrite
606 .map(compile_legacy_expression)
607 .transpose()?,
608 ))
609}
610
611fn compile_legacy_expression(
612 expression: LegacyUsersetExpression,
613) -> Result<UsersetExpression, ZanzibarError> {
614 match expression {
615 LegacyUsersetExpression::This => Ok(UsersetExpression::This),
616 LegacyUsersetExpression::ComputedUserset { relation } => {
617 Ok(UsersetExpression::ComputedUserset {
618 relation: RelationName::try_from(relation.0.as_str())?,
619 })
620 }
621 LegacyUsersetExpression::TupleToUserset {
622 tupleset_relation,
623 computed_userset_relation,
624 } => Ok(UsersetExpression::TupleToUserset {
625 tupleset_relation: RelationName::try_from(tupleset_relation.0.as_str())?,
626 computed_userset_relation: RelationName::try_from(
627 computed_userset_relation.0.as_str(),
628 )?,
629 }),
630 LegacyUsersetExpression::Union(expressions) => expressions
631 .into_iter()
632 .map(compile_legacy_expression)
633 .collect::<Result<Vec<_>, _>>()
634 .map(UsersetExpression::Union),
635 LegacyUsersetExpression::Intersection(expressions) => expressions
636 .into_iter()
637 .map(compile_legacy_expression)
638 .collect::<Result<Vec<_>, _>>()
639 .map(UsersetExpression::Intersection),
640 LegacyUsersetExpression::Exclusion { base, exclude } => Ok(UsersetExpression::Exclusion {
641 base: Box::new(compile_legacy_expression(*base)?),
642 exclude: Box::new(compile_legacy_expression(*exclude)?),
643 }),
644 }
645}
646
647fn compile_resolved_definitions(
648 compiled: &CompiledSchema,
649) -> Result<Vec<NamespaceDefinition>, SchemaError> {
650 let mut namespaces = Vec::with_capacity(compiled.definitions().len());
651 for namespace in compiled.definitions() {
652 let mut relations = Vec::with_capacity(namespace.relations().len());
653 for relation in namespace.relations() {
654 let compiled_userset_rewrite = relation
655 .userset_rewrite()
656 .map(|expression| {
657 compile_resolved_expression(compiled.resolver(), namespace.name(), expression)
658 })
659 .transpose()?;
660 relations.push(RelationDefinition {
661 name: relation.name.clone(),
662 allowed_subject_types: relation.allowed_subject_types.clone(),
663 userset_rewrite: relation.userset_rewrite.clone(),
664 compiled_userset_rewrite,
665 });
666 }
667 namespaces.push(NamespaceDefinition::new(
668 namespace.name.clone(),
669 Arc::from(relations.into_boxed_slice()),
670 ));
671 }
672 Ok(namespaces)
673}
674
675fn compile_resolved_expression(
676 resolver: &SchemaResolver,
677 namespace: &ObjectType,
678 expression: &UsersetExpression,
679) -> Result<CompiledUsersetExpression, SchemaError> {
680 match expression {
681 UsersetExpression::This => Ok(CompiledUsersetExpression::This),
682 UsersetExpression::ComputedUserset { relation } => {
683 let target_has_rewrite = resolver
684 .relation(namespace, relation)?
685 .userset_rewrite()
686 .is_some();
687 Ok(CompiledUsersetExpression::ComputedUserset {
688 relation: relation.clone(),
689 relation_id: resolver.relation_id(namespace, relation)?,
690 target_has_rewrite,
691 })
692 }
693 UsersetExpression::TupleToUserset {
694 tupleset_relation,
695 computed_userset_relation,
696 } => Ok(CompiledUsersetExpression::TupleToUserset {
697 tupleset_relation: tupleset_relation.clone(),
698 tupleset_relation_id: resolver.relation_id(namespace, tupleset_relation)?,
699 computed_userset_relation: computed_userset_relation.clone(),
700 }),
701 UsersetExpression::Union(expressions) => expressions
702 .iter()
703 .map(|expression| compile_resolved_expression(resolver, namespace, expression))
704 .collect::<Result<Vec<_>, _>>()
705 .map(CompiledUsersetExpression::Union),
706 UsersetExpression::Intersection(expressions) => expressions
707 .iter()
708 .map(|expression| compile_resolved_expression(resolver, namespace, expression))
709 .collect::<Result<Vec<_>, _>>()
710 .map(CompiledUsersetExpression::Intersection),
711 UsersetExpression::Exclusion { base, exclude } => {
712 Ok(CompiledUsersetExpression::Exclusion {
713 base: Box::new(compile_resolved_expression(resolver, namespace, base)?),
714 exclude: Box::new(compile_resolved_expression(resolver, namespace, exclude)?),
715 })
716 }
717 }
718}
719
720fn validate_references(compiled: &CompiledSchema) -> Result<(), SchemaError> {
721 for namespace in compiled.definitions() {
722 let mut relations = HashSet::with_capacity(namespace.relations().len());
723 for relation in namespace.relations() {
724 let inserted = relations.insert(relation.name().clone());
725 if !inserted {
726 return Err(SchemaError::DuplicateRelation {
727 namespace: namespace.name().to_string(),
728 relation: relation.name().to_string(),
729 });
730 }
731 }
732
733 for relation in namespace.relations() {
734 if let Some(expression) = relation.userset_rewrite() {
735 validate_expression(compiled, namespace, relation.name(), expression)?;
736 }
737 }
738 }
739 Ok(())
740}
741
742fn validate_expression(
743 compiled: &CompiledSchema,
744 namespace: &NamespaceDefinition,
745 owner: &RelationName,
746 expression: &UsersetExpression,
747) -> Result<(), SchemaError> {
748 match expression {
749 UsersetExpression::This => Ok(()),
750 UsersetExpression::ComputedUserset { relation } => ensure_relation_in_namespace(
751 compiled,
752 namespace.name(),
753 owner,
754 "computed userset",
755 relation,
756 )
757 .map(|_| ()),
758 UsersetExpression::TupleToUserset {
759 tupleset_relation,
760 computed_userset_relation,
761 } => {
762 let tupleset_relation_definition = ensure_relation_in_namespace(
763 compiled,
764 namespace.name(),
765 owner,
766 "tuple-to-userset tupleset",
767 tupleset_relation,
768 )?;
769 validate_tuple_to_userset_target(
770 compiled,
771 namespace,
772 owner,
773 tupleset_relation_definition,
774 computed_userset_relation,
775 )
776 }
777 UsersetExpression::Union(expressions) => {
778 validate_operands(namespace, owner, "union", 1, expressions)?;
779 for child in expressions {
780 validate_expression(compiled, namespace, owner, child)?;
781 }
782 Ok(())
783 }
784 UsersetExpression::Intersection(expressions) => {
785 validate_operands(namespace, owner, "intersection", 1, expressions)?;
786 for child in expressions {
787 validate_expression(compiled, namespace, owner, child)?;
788 }
789 Ok(())
790 }
791 UsersetExpression::Exclusion { base, exclude } => {
792 validate_expression(compiled, namespace, owner, base)?;
793 validate_expression(compiled, namespace, owner, exclude)
794 }
795 }
796}
797
798fn validate_operands(
799 namespace: &NamespaceDefinition,
800 owner: &RelationName,
801 operator: &'static str,
802 min_operands: usize,
803 expressions: &[UsersetExpression],
804) -> Result<(), SchemaError> {
805 if expressions.len() < min_operands {
806 return Err(SchemaError::EmptyExpression {
807 namespace: namespace.name().to_string(),
808 owner: owner.to_string(),
809 operator,
810 min_operands,
811 });
812 }
813 Ok(())
814}
815
816fn ensure_relation_in_namespace<'schema>(
817 compiled: &'schema CompiledSchema,
818 namespace: &ObjectType,
819 owner: &RelationName,
820 relation_kind: &'static str,
821 relation: &RelationName,
822) -> Result<&'schema RelationDefinition, SchemaError> {
823 match compiled.resolver().relation(namespace, relation) {
824 Ok(relation) => Ok(relation),
825 Err(SchemaError::RelationNotFound { .. }) => Err(SchemaError::MissingRelationReference {
826 namespace: namespace.to_string(),
827 owner: owner.to_string(),
828 relation: relation_kind,
829 missing: relation.to_string(),
830 }),
831 Err(error) => Err(error),
832 }
833}
834
835fn validate_tuple_to_userset_target(
836 compiled: &CompiledSchema,
837 namespace: &NamespaceDefinition,
838 owner: &RelationName,
839 tupleset_relation: &RelationDefinition,
840 computed_userset_relation: &RelationName,
841) -> Result<(), SchemaError> {
842 match tupleset_relation.allowed_subject_types() {
843 AllowedSubjectTypes::Explicit(object_types) => {
844 for object_type in object_types.iter() {
845 if compiled
846 .resolver()
847 .relation(object_type, computed_userset_relation)
848 .is_err()
849 {
850 return Err(SchemaError::MissingTupleToUsersetTarget {
851 namespace: namespace.name().to_string(),
852 owner: owner.to_string(),
853 missing: computed_userset_relation.to_string(),
854 });
855 }
856 }
857 Ok(())
858 }
859 AllowedSubjectTypes::Unspecified => {
860 if compiled
861 .resolver()
862 .relation_exists_anywhere(computed_userset_relation)
863 {
864 Ok(())
865 } else {
866 Err(SchemaError::MissingTupleToUsersetTarget {
867 namespace: namespace.name().to_string(),
868 owner: owner.to_string(),
869 missing: computed_userset_relation.to_string(),
870 })
871 }
872 }
873 }
874}