github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/cursor/cursor.go (about)

     1  package cursor
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  
     8  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
     9  
    10  	"github.com/authzed/spicedb/pkg/datastore"
    11  	dispatch "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
    12  	impl "github.com/authzed/spicedb/pkg/proto/impl/v1"
    13  	"github.com/authzed/spicedb/pkg/spiceerrors"
    14  )
    15  
    16  // Encode converts a decoded cursor to its opaque version.
    17  func Encode(decoded *impl.DecodedCursor) (*v1.Cursor, error) {
    18  	marshalled, err := decoded.MarshalVT()
    19  	if err != nil {
    20  		return nil, NewInvalidCursorErr(fmt.Errorf(errEncodeError, err))
    21  	}
    22  
    23  	return &v1.Cursor{
    24  		Token: base64.StdEncoding.EncodeToString(marshalled),
    25  	}, nil
    26  }
    27  
    28  // Decode converts an encoded cursor to its decoded version.
    29  func Decode(encoded *v1.Cursor) (*impl.DecodedCursor, error) {
    30  	if encoded == nil {
    31  		return nil, NewInvalidCursorErr(errors.New("cursor pointer was nil"))
    32  	}
    33  
    34  	decodedBytes, err := base64.StdEncoding.DecodeString(encoded.Token)
    35  	if err != nil {
    36  		return nil, NewInvalidCursorErr(fmt.Errorf(errDecodeError, err))
    37  	}
    38  
    39  	decoded := &impl.DecodedCursor{}
    40  	if err := decoded.UnmarshalVT(decodedBytes); err != nil {
    41  		return nil, NewInvalidCursorErr(fmt.Errorf(errDecodeError, err))
    42  	}
    43  
    44  	return decoded, nil
    45  }
    46  
    47  // EncodeFromDispatchCursor encodes an internal dispatching cursor into a V1 cursor for external
    48  // consumption, including the provided call context to ensure the API cursor reflects the calling
    49  // API method. The call hash should contain all the parameters of the calling API function,
    50  // as well as its revision and name.
    51  func EncodeFromDispatchCursor(dispatchCursor *dispatch.Cursor, callAndParameterHash string, revision datastore.Revision) (*v1.Cursor, error) {
    52  	if dispatchCursor == nil {
    53  		return nil, spiceerrors.MustBugf("got nil dispatch cursor")
    54  	}
    55  
    56  	return Encode(&impl.DecodedCursor{
    57  		VersionOneof: &impl.DecodedCursor_V1{
    58  			V1: &impl.V1Cursor{
    59  				Revision:              revision.String(),
    60  				DispatchVersion:       dispatchCursor.DispatchVersion,
    61  				Sections:              dispatchCursor.Sections,
    62  				CallAndParametersHash: callAndParameterHash,
    63  			},
    64  		},
    65  	})
    66  }
    67  
    68  // DecodeToDispatchCursor decodes an encoded API cursor into an internal dispatching cursor,
    69  // ensuring that the provided call context matches that encoded into the API cursor. The call
    70  // hash should contain all the parameters of the calling API function, as well as its revision
    71  // and name.
    72  func DecodeToDispatchCursor(encoded *v1.Cursor, callAndParameterHash string) (*dispatch.Cursor, error) {
    73  	decoded, err := Decode(encoded)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	v1decoded := decoded.GetV1()
    79  	if v1decoded == nil {
    80  		return nil, NewInvalidCursorErr(ErrNilCursor)
    81  	}
    82  
    83  	if v1decoded.CallAndParametersHash != callAndParameterHash {
    84  		return nil, NewInvalidCursorErr(ErrHashMismatch)
    85  	}
    86  
    87  	return &dispatch.Cursor{
    88  		DispatchVersion: v1decoded.DispatchVersion,
    89  		Sections:        v1decoded.Sections,
    90  	}, nil
    91  }
    92  
    93  // DecodeToDispatchRevision decodes an encoded API cursor into an internal dispatch revision.
    94  // NOTE: this method does *not* verify the caller's method signature.
    95  func DecodeToDispatchRevision(encoded *v1.Cursor, ds revisionDecoder) (datastore.Revision, error) {
    96  	decoded, err := Decode(encoded)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	v1decoded := decoded.GetV1()
   102  	if v1decoded == nil {
   103  		return nil, ErrNilCursor
   104  	}
   105  
   106  	parsed, err := ds.RevisionFromString(v1decoded.Revision)
   107  	if err != nil {
   108  		return datastore.NoRevision, fmt.Errorf(errDecodeError, err)
   109  	}
   110  
   111  	return parsed, nil
   112  }
   113  
   114  type revisionDecoder interface {
   115  	RevisionFromString(string) (datastore.Revision, error)
   116  }