github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/graph/lookupresources.go (about)

     1  package graph
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/authzed/spicedb/internal/dispatch"
     8  	"github.com/authzed/spicedb/pkg/datastore"
     9  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    10  	v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
    11  	"github.com/authzed/spicedb/pkg/tuple"
    12  )
    13  
    14  // NewCursoredLookupResources creates and instance of CursoredLookupResources.
    15  func NewCursoredLookupResources(c dispatch.Check, r dispatch.ReachableResources, concurrencyLimit uint16) *CursoredLookupResources {
    16  	return &CursoredLookupResources{c, r, concurrencyLimit}
    17  }
    18  
    19  // CursoredLookupResources exposes a method to perform LookupResources requests, and delegates subproblems to the
    20  // provided dispatch.Lookup instance.
    21  type CursoredLookupResources struct {
    22  	c                dispatch.Check
    23  	r                dispatch.ReachableResources
    24  	concurrencyLimit uint16
    25  }
    26  
    27  // ValidatedLookupResourcesRequest represents a request after it has been validated and parsed for internal
    28  // consumption.
    29  type ValidatedLookupResourcesRequest struct {
    30  	*v1.DispatchLookupResourcesRequest
    31  	Revision datastore.Revision
    32  }
    33  
    34  func (cl *CursoredLookupResources) LookupResources(
    35  	req ValidatedLookupResourcesRequest,
    36  	parentStream dispatch.LookupResourcesStream,
    37  ) error {
    38  	if req.Subject.ObjectId == tuple.PublicWildcard {
    39  		return NewErrInvalidArgument(errors.New("cannot perform lookup resources on wildcard"))
    40  	}
    41  
    42  	lookupContext := parentStream.Context()
    43  	limits := newLimitTracker(req.OptionalLimit)
    44  	reachableResourcesCursor := req.OptionalCursor
    45  
    46  	// Loop until the limit has been exhausted or no additional reachable resources are found (see below)
    47  	for !limits.hasExhaustedLimit() {
    48  		errCanceledBecauseNoAdditionalResourcesNeeded := errors.New("canceled because no additional reachable resources are needed")
    49  
    50  		// Create a new context for just the reachable resources. This is necessary because we don't want the cancelation
    51  		// of the reachable resources to cancel the lookup resources. The checking stream manually cancels the reachable
    52  		// resources context once the expected number of results has been reached.
    53  		reachableContext, cancelReachable := branchContext(lookupContext)
    54  
    55  		// Create a new handling stream that consumes the reachable resources results and publishes them
    56  		// to the parent stream, as found resources if they are properly checked.
    57  		checkingStream := newCheckingResourceStream(lookupContext, reachableContext, func() {
    58  			cancelReachable(errCanceledBecauseNoAdditionalResourcesNeeded)
    59  		}, req, cl.c, parentStream, limits, cl.concurrencyLimit)
    60  
    61  		err := cl.r.DispatchReachableResources(&v1.DispatchReachableResourcesRequest{
    62  			ResourceRelation: req.ObjectRelation,
    63  			SubjectRelation: &core.RelationReference{
    64  				Namespace: req.Subject.Namespace,
    65  				Relation:  req.Subject.Relation,
    66  			},
    67  			SubjectIds:     []string{req.Subject.ObjectId},
    68  			Metadata:       req.Metadata,
    69  			OptionalCursor: reachableResourcesCursor,
    70  		}, checkingStream)
    71  		if err != nil {
    72  			// If the reachable resources was canceled explicitly by the checking stream because the limit has been
    73  			// reached, then this error can safely be ignored. Otherwise, it must be returned.
    74  			isAllowedCancelErr := errors.Is(context.Cause(reachableContext), errCanceledBecauseNoAdditionalResourcesNeeded)
    75  			if !isAllowedCancelErr {
    76  				return err
    77  			}
    78  		}
    79  
    80  		reachableCount, newCursor, err := checkingStream.waitForPublishing()
    81  		if err != nil {
    82  			return err
    83  		}
    84  
    85  		reachableResourcesCursor = newCursor
    86  
    87  		// If no additional reachable results were found or the request was unlimited, then we can stop.
    88  		if reachableCount == 0 || req.OptionalLimit == 0 {
    89  			return nil
    90  		}
    91  	}
    92  
    93  	return nil
    94  }