github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/development/warnings.go (about) 1 package development 2 3 import ( 4 "context" 5 6 "github.com/authzed/spicedb/pkg/namespace" 7 corev1 "github.com/authzed/spicedb/pkg/proto/core/v1" 8 devinterface "github.com/authzed/spicedb/pkg/proto/developer/v1" 9 "github.com/authzed/spicedb/pkg/spiceerrors" 10 "github.com/authzed/spicedb/pkg/typesystem" 11 ) 12 13 var allChecks = checkers{ 14 relationCheckers: []relationChecker{ 15 lintRelationReferencesParentType, 16 }, 17 computedUsersetCheckers: []computedUsersetChecker{ 18 lintPermissionReferencingItself, 19 }, 20 ttuCheckers: []ttuChecker{ 21 lintArrowReferencingRelation, 22 lintArrowReferencingUnreachable, 23 lintArrowOverSubRelation, 24 }, 25 } 26 27 func warningForMetadata(message string, metadata namespace.WithSourcePosition) *devinterface.DeveloperWarning { 28 return warningForPosition(message, metadata.GetSourcePosition()) 29 } 30 31 func warningForPosition(message string, sourcePosition *corev1.SourcePosition) *devinterface.DeveloperWarning { 32 if sourcePosition == nil { 33 return &devinterface.DeveloperWarning{ 34 Message: message, 35 } 36 } 37 38 lineNumber := sourcePosition.ZeroIndexedLineNumber + 1 39 columnNumber := sourcePosition.ZeroIndexedColumnPosition + 1 40 41 return &devinterface.DeveloperWarning{ 42 Message: message, 43 Line: uint32(lineNumber), 44 Column: uint32(columnNumber), 45 } 46 } 47 48 // GetWarnings returns a list of warnings for the given developer context. 49 func GetWarnings(ctx context.Context, devCtx *DevContext) ([]*devinterface.DeveloperWarning, error) { 50 warnings := []*devinterface.DeveloperWarning{} 51 resolver := typesystem.ResolverForSchema(*devCtx.CompiledSchema) 52 53 for _, def := range devCtx.CompiledSchema.ObjectDefinitions { 54 found, err := addDefinitionWarnings(ctx, def, resolver) 55 if err != nil { 56 return nil, err 57 } 58 warnings = append(warnings, found...) 59 } 60 61 return warnings, nil 62 } 63 64 type contextKey string 65 66 var relationKey = contextKey("relation") 67 68 func addDefinitionWarnings(ctx context.Context, def *corev1.NamespaceDefinition, resolver typesystem.Resolver) ([]*devinterface.DeveloperWarning, error) { 69 ts, err := typesystem.NewNamespaceTypeSystem(def, resolver) 70 if err != nil { 71 return nil, err 72 } 73 74 warnings := []*devinterface.DeveloperWarning{} 75 for _, rel := range def.Relation { 76 ctx = context.WithValue(ctx, relationKey, rel) 77 for _, checker := range allChecks.relationCheckers { 78 checkerWarning, err := checker(ctx, rel, ts) 79 if err != nil { 80 return nil, err 81 } 82 83 if checkerWarning != nil { 84 warnings = append(warnings, checkerWarning) 85 } 86 } 87 88 if ts.IsPermission(rel.Name) { 89 found, err := walkUsersetRewrite(ctx, rel.UsersetRewrite, allChecks, ts) 90 if err != nil { 91 return nil, err 92 } 93 94 warnings = append(warnings, found...) 95 } 96 } 97 98 return warnings, nil 99 } 100 101 type ( 102 relationChecker func(ctx context.Context, relation *corev1.Relation, vts *typesystem.TypeSystem) (*devinterface.DeveloperWarning, error) 103 computedUsersetChecker func(ctx context.Context, computedUserset *corev1.ComputedUserset, sourcePosition *corev1.SourcePosition, vts *typesystem.TypeSystem) (*devinterface.DeveloperWarning, error) 104 ttuChecker func(ctx context.Context, ttu *corev1.TupleToUserset, sourcePosition *corev1.SourcePosition, vts *typesystem.TypeSystem) (*devinterface.DeveloperWarning, error) 105 ) 106 107 type checkers struct { 108 relationCheckers []relationChecker 109 computedUsersetCheckers []computedUsersetChecker 110 ttuCheckers []ttuChecker 111 } 112 113 func walkUsersetRewrite(ctx context.Context, rewrite *corev1.UsersetRewrite, checkers checkers, ts *typesystem.TypeSystem) ([]*devinterface.DeveloperWarning, error) { 114 if rewrite == nil { 115 return nil, nil 116 } 117 118 switch t := (rewrite.RewriteOperation).(type) { 119 case *corev1.UsersetRewrite_Union: 120 return walkUsersetOperations(ctx, t.Union.Child, checkers, ts) 121 122 case *corev1.UsersetRewrite_Intersection: 123 return walkUsersetOperations(ctx, t.Intersection.Child, checkers, ts) 124 125 case *corev1.UsersetRewrite_Exclusion: 126 return walkUsersetOperations(ctx, t.Exclusion.Child, checkers, ts) 127 128 default: 129 return nil, spiceerrors.MustBugf("unexpected rewrite operation type %T", t) 130 } 131 } 132 133 func walkUsersetOperations(ctx context.Context, ops []*corev1.SetOperation_Child, checkers checkers, ts *typesystem.TypeSystem) ([]*devinterface.DeveloperWarning, error) { 134 warnings := []*devinterface.DeveloperWarning{} 135 for _, op := range ops { 136 switch t := op.ChildType.(type) { 137 case *corev1.SetOperation_Child_XThis: 138 continue 139 140 case *corev1.SetOperation_Child_ComputedUserset: 141 for _, checker := range checkers.computedUsersetCheckers { 142 checkerWarning, err := checker(ctx, t.ComputedUserset, op.SourcePosition, ts) 143 if err != nil { 144 return nil, err 145 } 146 147 if checkerWarning != nil { 148 warnings = append(warnings, checkerWarning) 149 } 150 } 151 152 case *corev1.SetOperation_Child_UsersetRewrite: 153 found, err := walkUsersetRewrite(ctx, t.UsersetRewrite, checkers, ts) 154 if err != nil { 155 return nil, err 156 } 157 158 warnings = append(warnings, found...) 159 160 case *corev1.SetOperation_Child_TupleToUserset: 161 for _, checker := range checkers.ttuCheckers { 162 checkerWarning, err := checker(ctx, t.TupleToUserset, op.SourcePosition, ts) 163 if err != nil { 164 return nil, err 165 } 166 167 if checkerWarning != nil { 168 warnings = append(warnings, checkerWarning) 169 } 170 } 171 172 case *corev1.SetOperation_Child_XNil: 173 continue 174 175 default: 176 return nil, spiceerrors.MustBugf("unexpected set operation type %T", t) 177 } 178 } 179 180 return warnings, nil 181 }