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  }