github.com/openfga/openfga@v1.5.4-rc1/pkg/storage/memory/memory.go (about)

     1  package memory
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/oklog/ulid/v2"
    13  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    14  	"go.opentelemetry.io/otel"
    15  	"google.golang.org/protobuf/types/known/structpb"
    16  	"google.golang.org/protobuf/types/known/timestamppb"
    17  
    18  	"github.com/openfga/openfga/pkg/storage"
    19  	"github.com/openfga/openfga/pkg/telemetry"
    20  	tupleUtils "github.com/openfga/openfga/pkg/tuple"
    21  )
    22  
    23  var tracer = otel.Tracer("openfga/pkg/storage/memory")
    24  
    25  type staticIterator struct {
    26  	records           []*storage.TupleRecord
    27  	continuationToken []byte
    28  	mu                sync.Mutex
    29  }
    30  
    31  // match returns true if all the fields in t [*storage.TupleRecord] are equal to
    32  // the same field in the target [*openfgav1.TupleKey]. If the input Object
    33  // doesn't specify an ID, only the Object Types are compared. If a field
    34  // in the input parameter is empty, it is ignored in the comparison.
    35  func match(t *storage.TupleRecord, target *openfgav1.TupleKey) bool {
    36  	if target.GetObject() != "" {
    37  		td, objectid := tupleUtils.SplitObject(target.GetObject())
    38  		if objectid == "" {
    39  			if td != t.ObjectType {
    40  				return false
    41  			}
    42  		} else {
    43  			if td != t.ObjectType || objectid != t.ObjectID {
    44  				return false
    45  			}
    46  		}
    47  	}
    48  	if target.GetRelation() != "" && t.Relation != target.GetRelation() {
    49  		return false
    50  	}
    51  	if target.GetUser() != "" && t.User != target.GetUser() {
    52  		return false
    53  	}
    54  	return true
    55  }
    56  
    57  // Next see [storage.Iterator].Next.
    58  func (s *staticIterator) Next(ctx context.Context) (*openfgav1.Tuple, error) {
    59  	if ctx.Err() != nil {
    60  		return nil, ctx.Err()
    61  	}
    62  
    63  	s.mu.Lock()
    64  	defer s.mu.Unlock()
    65  
    66  	if len(s.records) == 0 {
    67  		return nil, storage.ErrIteratorDone
    68  	}
    69  
    70  	next, rest := s.records[0], s.records[1:]
    71  	s.records = rest
    72  	return next.AsTuple(), nil
    73  }
    74  
    75  // Stop does not do anything for staticIterator.
    76  func (s *staticIterator) Stop() {}
    77  
    78  // ToArray converts the entire sequence of tuples in the staticIterator to an array format.
    79  func (s *staticIterator) ToArray(ctx context.Context) ([]*openfgav1.Tuple, []byte, error) {
    80  	var res []*openfgav1.Tuple
    81  	for range s.records {
    82  		t, err := s.Next(ctx)
    83  		if err != nil {
    84  			return nil, nil, err
    85  		}
    86  
    87  		res = append(res, t)
    88  	}
    89  
    90  	return res, s.continuationToken, nil
    91  }
    92  
    93  // StorageOption defines a function type used for configuring a [MemoryBackend] instance.
    94  type StorageOption func(dataStore *MemoryBackend)
    95  
    96  const (
    97  	defaultMaxTuplesPerWrite             = 100
    98  	defaultMaxTypesPerAuthorizationModel = 100
    99  )
   100  
   101  // MemoryBackend provides an ephemeral memory-backed implementation of [storage.OpenFGADatastore].
   102  // These instances may be safely shared by multiple go-routines.
   103  type MemoryBackend struct {
   104  	maxTuplesPerWrite             int
   105  	maxTypesPerAuthorizationModel int
   106  	mu                            sync.Mutex
   107  
   108  	// TupleBackend
   109  	// map: store => set of tuples
   110  	tuples map[string][]*storage.TupleRecord // GUARDED_BY(mu).
   111  
   112  	// ChangelogBackend
   113  	// map: store => set of changes
   114  	changes map[string][]*openfgav1.TupleChange // GUARDED_BY(mu_).
   115  
   116  	// AuthorizationModelBackend
   117  	// map: store = > map: type definition id => type definition
   118  	authorizationModels map[string]map[string]*AuthorizationModelEntry // GUARDED_BY(mu_).
   119  
   120  	// map: store id => store data
   121  	stores map[string]*openfgav1.Store // GUARDED_BY(mu_).
   122  
   123  	// map: store id | authz model id => assertions
   124  	assertions map[string][]*openfgav1.Assertion // GUARDED_BY(mu_).
   125  }
   126  
   127  // Ensures that [MemoryBackend] implements the [storage.OpenFGADatastore] interface.
   128  var _ storage.OpenFGADatastore = (*MemoryBackend)(nil)
   129  
   130  // AuthorizationModelEntry represents an entry in a storage system
   131  // that holds information about an authorization model.
   132  type AuthorizationModelEntry struct {
   133  	model  *openfgav1.AuthorizationModel
   134  	latest bool
   135  }
   136  
   137  // New creates a new [MemoryBackend] given the options.
   138  func New(opts ...StorageOption) storage.OpenFGADatastore {
   139  	ds := &MemoryBackend{
   140  		maxTuplesPerWrite:             defaultMaxTuplesPerWrite,
   141  		maxTypesPerAuthorizationModel: defaultMaxTypesPerAuthorizationModel,
   142  		tuples:                        make(map[string][]*storage.TupleRecord, 0),
   143  		changes:                       make(map[string][]*openfgav1.TupleChange, 0),
   144  		authorizationModels:           make(map[string]map[string]*AuthorizationModelEntry),
   145  		stores:                        make(map[string]*openfgav1.Store, 0),
   146  		assertions:                    make(map[string][]*openfgav1.Assertion, 0),
   147  	}
   148  
   149  	for _, opt := range opts {
   150  		opt(ds)
   151  	}
   152  
   153  	return ds
   154  }
   155  
   156  // WithMaxTuplesPerWrite returns a [StorageOption] that sets the maximum number of tuples allowed in a single write operation.
   157  // This option is used to configure a [MemoryBackend] instance, providing a limit to the number of tuples that can be written at once.
   158  // This helps in managing and controlling the load and performance of the memory storage during bulk write operations.
   159  func WithMaxTuplesPerWrite(n int) StorageOption {
   160  	return func(ds *MemoryBackend) { ds.maxTuplesPerWrite = n }
   161  }
   162  
   163  // WithMaxTypesPerAuthorizationModel returns a [StorageOption] that sets the maximum number of types allowed per authorization model.
   164  // This configuration is particularly useful for limiting the complexity or size of an authorization model in a MemoryBackend instance,
   165  // ensuring that models remain manageable and within predefined resource constraints.
   166  func WithMaxTypesPerAuthorizationModel(n int) StorageOption {
   167  	return func(ds *MemoryBackend) { ds.maxTypesPerAuthorizationModel = n }
   168  }
   169  
   170  // Close does not do anything for [MemoryBackend].
   171  func (s *MemoryBackend) Close() {}
   172  
   173  // Read see [storage.RelationshipTupleReader].Read.
   174  func (s *MemoryBackend) Read(ctx context.Context, store string, key *openfgav1.TupleKey) (storage.TupleIterator, error) {
   175  	ctx, span := tracer.Start(ctx, "memory.Read")
   176  	defer span.End()
   177  
   178  	return s.read(ctx, store, key, storage.PaginationOptions{})
   179  }
   180  
   181  // ReadPage see [storage.RelationshipTupleReader].ReadPage.
   182  func (s *MemoryBackend) ReadPage(
   183  	ctx context.Context,
   184  	store string,
   185  	key *openfgav1.TupleKey,
   186  	paginationOptions storage.PaginationOptions,
   187  ) ([]*openfgav1.Tuple, []byte, error) {
   188  	ctx, span := tracer.Start(ctx, "memory.ReadPage")
   189  	defer span.End()
   190  
   191  	it, err := s.read(ctx, store, key, paginationOptions)
   192  	if err != nil {
   193  		return nil, nil, err
   194  	}
   195  
   196  	return it.ToArray(ctx)
   197  }
   198  
   199  // ReadChanges see [storage.ChangelogBackend].ReadChanges.
   200  func (s *MemoryBackend) ReadChanges(
   201  	ctx context.Context,
   202  	store,
   203  	objectType string,
   204  	paginationOptions storage.PaginationOptions,
   205  	horizonOffset time.Duration,
   206  ) ([]*openfgav1.TupleChange, []byte, error) {
   207  	_, span := tracer.Start(ctx, "memory.ReadChanges")
   208  	defer span.End()
   209  
   210  	s.mu.Lock()
   211  	defer s.mu.Unlock()
   212  
   213  	var err error
   214  	var from int64
   215  	var typeInToken string
   216  	var continuationToken string
   217  	if paginationOptions.From != "" {
   218  		tokens := strings.Split(paginationOptions.From, "|")
   219  		if len(tokens) == 2 {
   220  			concreteToken := tokens[0]
   221  			typeInToken = tokens[1]
   222  			from, err = strconv.ParseInt(concreteToken, 10, 32)
   223  			if err != nil {
   224  				return nil, nil, err
   225  			}
   226  		}
   227  	}
   228  
   229  	if typeInToken != "" && typeInToken != objectType {
   230  		return nil, nil, storage.ErrMismatchObjectType
   231  	}
   232  
   233  	var allChanges []*openfgav1.TupleChange
   234  	now := time.Now().UTC()
   235  	for _, change := range s.changes[store] {
   236  		if objectType == "" || (objectType != "" && strings.HasPrefix(change.GetTupleKey().GetObject(), objectType+":")) {
   237  			if change.GetTimestamp().AsTime().After(now.Add(-horizonOffset)) {
   238  				break
   239  			}
   240  			allChanges = append(allChanges, change)
   241  		}
   242  	}
   243  	if len(allChanges) == 0 {
   244  		return nil, nil, storage.ErrNotFound
   245  	}
   246  
   247  	pageSize := storage.DefaultPageSize
   248  	if paginationOptions.PageSize > 0 {
   249  		pageSize = paginationOptions.PageSize
   250  	}
   251  	to := int(from) + pageSize
   252  	if len(allChanges) < to {
   253  		to = len(allChanges)
   254  	}
   255  	res := allChanges[from:to]
   256  	if len(res) == 0 {
   257  		return nil, nil, storage.ErrNotFound
   258  	}
   259  
   260  	continuationToken = strconv.Itoa(len(allChanges))
   261  	if to != len(allChanges) {
   262  		continuationToken = strconv.Itoa(to)
   263  	}
   264  	continuationToken += fmt.Sprintf("|%s", objectType)
   265  
   266  	return res, []byte(continuationToken), nil
   267  }
   268  
   269  func (s *MemoryBackend) read(ctx context.Context, store string, tk *openfgav1.TupleKey, paginationOptions storage.PaginationOptions) (*staticIterator, error) {
   270  	_, span := tracer.Start(ctx, "memory.read")
   271  	defer span.End()
   272  
   273  	s.mu.Lock()
   274  	defer s.mu.Unlock()
   275  
   276  	var matches []*storage.TupleRecord
   277  	if tk.GetObject() == "" && tk.GetRelation() == "" && tk.GetUser() == "" {
   278  		matches = make([]*storage.TupleRecord, len(s.tuples[store]))
   279  		copy(matches, s.tuples[store])
   280  	} else {
   281  		for _, t := range s.tuples[store] {
   282  			if match(t, tk) {
   283  				matches = append(matches, t)
   284  			}
   285  		}
   286  	}
   287  
   288  	var err error
   289  	var from int
   290  	if paginationOptions.From != "" {
   291  		from, err = strconv.Atoi(paginationOptions.From)
   292  		if err != nil {
   293  			telemetry.TraceError(span, err)
   294  			return nil, err
   295  		}
   296  	}
   297  
   298  	if from <= len(matches) {
   299  		matches = matches[from:]
   300  	}
   301  
   302  	to := paginationOptions.PageSize
   303  	if to != 0 && to < len(matches) {
   304  		return &staticIterator{records: matches[:to], continuationToken: []byte(strconv.Itoa(from + to))}, nil
   305  	}
   306  
   307  	return &staticIterator{records: matches}, nil
   308  }
   309  
   310  // Write see [storage.RelationshipTupleWriter].Write.
   311  func (s *MemoryBackend) Write(ctx context.Context, store string, deletes storage.Deletes, writes storage.Writes) error {
   312  	_, span := tracer.Start(ctx, "memory.Write")
   313  	defer span.End()
   314  
   315  	s.mu.Lock()
   316  	defer s.mu.Unlock()
   317  
   318  	now := timestamppb.Now()
   319  
   320  	if err := validateTuples(s.tuples[store], deletes, writes); err != nil {
   321  		return err
   322  	}
   323  
   324  	var records []*storage.TupleRecord
   325  Delete:
   326  	for _, tr := range s.tuples[store] {
   327  		t := tr.AsTuple()
   328  		tk := t.GetKey()
   329  		for _, k := range deletes {
   330  			if match(tr, tupleUtils.TupleKeyWithoutConditionToTupleKey(k)) {
   331  				s.changes[store] = append(
   332  					s.changes[store],
   333  					&openfgav1.TupleChange{
   334  						TupleKey:  tupleUtils.NewTupleKey(tk.GetObject(), tk.GetRelation(), tk.GetUser()), // Redact the condition info.
   335  						Operation: openfgav1.TupleOperation_TUPLE_OPERATION_DELETE,
   336  						Timestamp: now,
   337  					},
   338  				)
   339  				continue Delete
   340  			}
   341  		}
   342  		records = append(records, tr)
   343  	}
   344  
   345  Write:
   346  	for _, t := range writes {
   347  		for _, et := range records {
   348  			if match(et, t) {
   349  				continue Write
   350  			}
   351  		}
   352  
   353  		var conditionName string
   354  		var conditionContext *structpb.Struct
   355  		if condition := t.GetCondition(); condition != nil {
   356  			conditionName = condition.GetName()
   357  			conditionContext = condition.GetContext()
   358  		}
   359  
   360  		objectType, objectID := tupleUtils.SplitObject(t.GetObject())
   361  
   362  		records = append(records, &storage.TupleRecord{
   363  			Store:            store,
   364  			ObjectType:       objectType,
   365  			ObjectID:         objectID,
   366  			Relation:         t.GetRelation(),
   367  			User:             t.GetUser(),
   368  			ConditionName:    conditionName,
   369  			ConditionContext: conditionContext,
   370  			Ulid:             ulid.MustNew(ulid.Timestamp(now.AsTime()), ulid.DefaultEntropy()).String(),
   371  			InsertedAt:       now.AsTime(),
   372  		})
   373  
   374  		tk := tupleUtils.NewTupleKeyWithCondition(
   375  			tupleUtils.BuildObject(objectType, objectID),
   376  			t.GetRelation(),
   377  			t.GetUser(),
   378  			conditionName,
   379  			conditionContext,
   380  		)
   381  
   382  		s.changes[store] = append(s.changes[store], &openfgav1.TupleChange{
   383  			TupleKey:  tk,
   384  			Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
   385  			Timestamp: now,
   386  		})
   387  	}
   388  	s.tuples[store] = records
   389  	return nil
   390  }
   391  
   392  func validateTuples(
   393  	records []*storage.TupleRecord,
   394  	deletes []*openfgav1.TupleKeyWithoutCondition,
   395  	writes []*openfgav1.TupleKey,
   396  ) error {
   397  	for _, tk := range deletes {
   398  		if !find(records, tupleUtils.TupleKeyWithoutConditionToTupleKey(tk)) {
   399  			return storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_DELETE)
   400  		}
   401  	}
   402  	for _, tk := range writes {
   403  		if find(records, tk) {
   404  			return storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE)
   405  		}
   406  	}
   407  	return nil
   408  }
   409  
   410  // find returns true if there is any [*storage.TupleRecord] for which match returns true.
   411  func find(records []*storage.TupleRecord, tupleKey *openfgav1.TupleKey) bool {
   412  	for _, tr := range records {
   413  		if match(tr, tupleKey) {
   414  			return true
   415  		}
   416  	}
   417  	return false
   418  }
   419  
   420  // ReadUserTuple see [storage.RelationshipTupleReader].ReadUserTuple.
   421  func (s *MemoryBackend) ReadUserTuple(ctx context.Context, store string, key *openfgav1.TupleKey) (*openfgav1.Tuple, error) {
   422  	_, span := tracer.Start(ctx, "memory.ReadUserTuple")
   423  	defer span.End()
   424  
   425  	s.mu.Lock()
   426  	defer s.mu.Unlock()
   427  
   428  	for _, t := range s.tuples[store] {
   429  		if match(t, key) {
   430  			return t.AsTuple(), nil
   431  		}
   432  	}
   433  
   434  	telemetry.TraceError(span, storage.ErrNotFound)
   435  	return nil, storage.ErrNotFound
   436  }
   437  
   438  // ReadUsersetTuples see [storage.RelationshipTupleReader].ReadUsersetTuples.
   439  func (s *MemoryBackend) ReadUsersetTuples(
   440  	ctx context.Context,
   441  	store string,
   442  	filter storage.ReadUsersetTuplesFilter,
   443  ) (storage.TupleIterator, error) {
   444  	_, span := tracer.Start(ctx, "memory.ReadUsersetTuples")
   445  	defer span.End()
   446  
   447  	s.mu.Lock()
   448  	defer s.mu.Unlock()
   449  
   450  	var matches []*storage.TupleRecord
   451  	for _, t := range s.tuples[store] {
   452  		if match(t, &openfgav1.TupleKey{
   453  			Object:   filter.Object,
   454  			Relation: filter.Relation,
   455  		}) && tupleUtils.GetUserTypeFromUser(t.User) == tupleUtils.UserSet {
   456  			if len(filter.AllowedUserTypeRestrictions) == 0 { // 1.0 model.
   457  				matches = append(matches, t)
   458  				continue
   459  			}
   460  
   461  			// 1.1 model: see if the tuple found is of an allowed type.
   462  			userType := tupleUtils.GetType(t.User)
   463  			_, userRelation := tupleUtils.SplitObjectRelation(t.User)
   464  			for _, allowedType := range filter.AllowedUserTypeRestrictions {
   465  				if allowedType.GetType() == userType && allowedType.GetRelation() == userRelation {
   466  					matches = append(matches, t)
   467  					continue
   468  				}
   469  			}
   470  		}
   471  	}
   472  
   473  	return &staticIterator{records: matches}, nil
   474  }
   475  
   476  // ReadStartingWithUser see [storage.RelationshipTupleReader].ReadStartingWithUser.
   477  func (s *MemoryBackend) ReadStartingWithUser(
   478  	ctx context.Context,
   479  	store string,
   480  	filter storage.ReadStartingWithUserFilter,
   481  ) (storage.TupleIterator, error) {
   482  	_, span := tracer.Start(ctx, "memory.ReadStartingWithUser")
   483  	defer span.End()
   484  
   485  	s.mu.Lock()
   486  	defer s.mu.Unlock()
   487  
   488  	var matches []*storage.TupleRecord
   489  	for _, t := range s.tuples[store] {
   490  		if t.ObjectType != filter.ObjectType {
   491  			continue
   492  		}
   493  
   494  		if t.Relation != filter.Relation {
   495  			continue
   496  		}
   497  
   498  		for _, userFilter := range filter.UserFilter {
   499  			targetUser := userFilter.GetObject()
   500  			if userFilter.GetRelation() != "" {
   501  				targetUser = tupleUtils.GetObjectRelationAsString(userFilter)
   502  			}
   503  
   504  			if targetUser == t.User {
   505  				matches = append(matches, t)
   506  			}
   507  		}
   508  	}
   509  	return &staticIterator{records: matches}, nil
   510  }
   511  
   512  func findAuthorizationModelByID(
   513  	id string,
   514  	configurations map[string]*AuthorizationModelEntry,
   515  ) (*openfgav1.AuthorizationModel, bool) {
   516  	if id != "" {
   517  		if entry, ok := configurations[id]; ok {
   518  			return entry.model, true
   519  		}
   520  
   521  		return nil, false
   522  	}
   523  
   524  	for _, entry := range configurations {
   525  		if entry.latest {
   526  			return entry.model, true
   527  		}
   528  	}
   529  
   530  	return nil, false
   531  }
   532  
   533  // ReadAuthorizationModel see [storage.AuthorizationModelReadBackend].ReadAuthorizationModel.
   534  func (s *MemoryBackend) ReadAuthorizationModel(
   535  	ctx context.Context,
   536  	store string,
   537  	id string,
   538  ) (*openfgav1.AuthorizationModel, error) {
   539  	_, span := tracer.Start(ctx, "memory.ReadAuthorizationModel")
   540  	defer span.End()
   541  
   542  	s.mu.Lock()
   543  	defer s.mu.Unlock()
   544  
   545  	tm, ok := s.authorizationModels[store]
   546  	if !ok {
   547  		telemetry.TraceError(span, storage.ErrNotFound)
   548  		return nil, storage.ErrNotFound
   549  	}
   550  
   551  	if model, ok := findAuthorizationModelByID(id, tm); ok {
   552  		if model.GetTypeDefinitions() == nil || len(model.GetTypeDefinitions()) == 0 {
   553  			return nil, storage.ErrNotFound
   554  		}
   555  		return model, nil
   556  	}
   557  
   558  	telemetry.TraceError(span, storage.ErrNotFound)
   559  	return nil, storage.ErrNotFound
   560  }
   561  
   562  // ReadAuthorizationModels see [storage.AuthorizationModelReadBackend].ReadAuthorizationModels.
   563  func (s *MemoryBackend) ReadAuthorizationModels(
   564  	ctx context.Context,
   565  	store string,
   566  	options storage.PaginationOptions,
   567  ) ([]*openfgav1.AuthorizationModel, []byte, error) {
   568  	_, span := tracer.Start(ctx, "memory.ReadAuthorizationModels")
   569  	defer span.End()
   570  
   571  	s.mu.Lock()
   572  	defer s.mu.Unlock()
   573  
   574  	models := make([]*openfgav1.AuthorizationModel, 0, len(s.authorizationModels[store]))
   575  	for _, entry := range s.authorizationModels[store] {
   576  		models = append(models, entry.model)
   577  	}
   578  
   579  	// From newest to oldest.
   580  	sort.Slice(models, func(i, j int) bool {
   581  		return models[i].GetId() > models[j].GetId()
   582  	})
   583  
   584  	var from int64
   585  	continuationToken := ""
   586  	var err error
   587  
   588  	pageSize := storage.DefaultPageSize
   589  	if options.PageSize > 0 {
   590  		pageSize = options.PageSize
   591  	}
   592  
   593  	if options.From != "" {
   594  		from, err = strconv.ParseInt(options.From, 10, 32)
   595  		if err != nil {
   596  			return nil, nil, err
   597  		}
   598  	}
   599  
   600  	to := int(from) + pageSize
   601  	if len(models) < to {
   602  		to = len(models)
   603  	}
   604  	res := models[from:to]
   605  
   606  	if to != len(models) {
   607  		continuationToken = strconv.Itoa(to)
   608  	}
   609  
   610  	return res, []byte(continuationToken), nil
   611  }
   612  
   613  // FindLatestAuthorizationModel see [storage.AuthorizationModelReadBackend].FindLatestAuthorizationModel.
   614  func (s *MemoryBackend) FindLatestAuthorizationModel(ctx context.Context, store string) (*openfgav1.AuthorizationModel, error) {
   615  	_, span := tracer.Start(ctx, "memory.FindLatestAuthorizationModel")
   616  	defer span.End()
   617  
   618  	s.mu.Lock()
   619  	defer s.mu.Unlock()
   620  
   621  	tm, ok := s.authorizationModels[store]
   622  	if !ok {
   623  		telemetry.TraceError(span, storage.ErrNotFound)
   624  		return nil, storage.ErrNotFound
   625  	}
   626  
   627  	// Find latest model.
   628  	nsc, ok := findAuthorizationModelByID("", tm)
   629  	if !ok {
   630  		telemetry.TraceError(span, storage.ErrNotFound)
   631  		return nil, storage.ErrNotFound
   632  	}
   633  	return nsc, nil
   634  }
   635  
   636  // WriteAuthorizationModel see [storage.TypeDefinitionWriteBackend].WriteAuthorizationModel.
   637  func (s *MemoryBackend) WriteAuthorizationModel(ctx context.Context, store string, model *openfgav1.AuthorizationModel) error {
   638  	_, span := tracer.Start(ctx, "memory.WriteAuthorizationModel")
   639  	defer span.End()
   640  
   641  	s.mu.Lock()
   642  	defer s.mu.Unlock()
   643  
   644  	if _, ok := s.authorizationModels[store]; !ok {
   645  		s.authorizationModels[store] = make(map[string]*AuthorizationModelEntry)
   646  	}
   647  
   648  	for _, entry := range s.authorizationModels[store] {
   649  		entry.latest = false
   650  	}
   651  
   652  	s.authorizationModels[store][model.GetId()] = &AuthorizationModelEntry{
   653  		model:  model,
   654  		latest: true,
   655  	}
   656  
   657  	return nil
   658  }
   659  
   660  // CreateStore adds a new store to the [MemoryBackend].
   661  func (s *MemoryBackend) CreateStore(ctx context.Context, newStore *openfgav1.Store) (*openfgav1.Store, error) {
   662  	_, span := tracer.Start(ctx, "memory.CreateStore")
   663  	defer span.End()
   664  
   665  	s.mu.Lock()
   666  	defer s.mu.Unlock()
   667  
   668  	if _, ok := s.stores[newStore.GetId()]; ok {
   669  		return nil, storage.ErrCollision
   670  	}
   671  
   672  	now := timestamppb.New(time.Now().UTC())
   673  	s.stores[newStore.GetId()] = &openfgav1.Store{
   674  		Id:        newStore.GetId(),
   675  		Name:      newStore.GetName(),
   676  		CreatedAt: now,
   677  		UpdatedAt: now,
   678  	}
   679  
   680  	return s.stores[newStore.GetId()], nil
   681  }
   682  
   683  // DeleteStore removes a store from the [MemoryBackend].
   684  func (s *MemoryBackend) DeleteStore(ctx context.Context, id string) error {
   685  	_, span := tracer.Start(ctx, "memory.DeleteStore")
   686  	defer span.End()
   687  
   688  	s.mu.Lock()
   689  	defer s.mu.Unlock()
   690  
   691  	delete(s.stores, id)
   692  	return nil
   693  }
   694  
   695  // WriteAssertions see [storage.AssertionsBackend].WriteAssertions.
   696  func (s *MemoryBackend) WriteAssertions(ctx context.Context, store, modelID string, assertions []*openfgav1.Assertion) error {
   697  	_, span := tracer.Start(ctx, "memory.WriteAssertions")
   698  	defer span.End()
   699  
   700  	s.mu.Lock()
   701  	defer s.mu.Unlock()
   702  
   703  	assertionsID := fmt.Sprintf("%s|%s", store, modelID)
   704  	s.assertions[assertionsID] = assertions
   705  
   706  	return nil
   707  }
   708  
   709  // ReadAssertions see [storage.AssertionsBackend].ReadAssertions.
   710  func (s *MemoryBackend) ReadAssertions(ctx context.Context, store, modelID string) ([]*openfgav1.Assertion, error) {
   711  	_, span := tracer.Start(ctx, "memory.ReadAssertions")
   712  	defer span.End()
   713  
   714  	s.mu.Lock()
   715  	defer s.mu.Unlock()
   716  
   717  	assertionsID := fmt.Sprintf("%s|%s", store, modelID)
   718  	assertions, ok := s.assertions[assertionsID]
   719  	if !ok {
   720  		return []*openfgav1.Assertion{}, nil
   721  	}
   722  	return assertions, nil
   723  }
   724  
   725  // MaxTuplesPerWrite see [storage.RelationshipTupleWriter].MaxTuplesPerWrite.
   726  func (s *MemoryBackend) MaxTuplesPerWrite() int {
   727  	return s.maxTuplesPerWrite
   728  }
   729  
   730  // MaxTypesPerAuthorizationModel see [storage.TypeDefinitionWriteBackend].MaxTypesPerAuthorizationModel.
   731  func (s *MemoryBackend) MaxTypesPerAuthorizationModel() int {
   732  	return s.maxTypesPerAuthorizationModel
   733  }
   734  
   735  // GetStore retrieves the details of a specific store from the MemoryBackend using its storeID.
   736  func (s *MemoryBackend) GetStore(ctx context.Context, storeID string) (*openfgav1.Store, error) {
   737  	_, span := tracer.Start(ctx, "memory.GetStore")
   738  	defer span.End()
   739  
   740  	s.mu.Lock()
   741  	defer s.mu.Unlock()
   742  
   743  	if s.stores[storeID] == nil {
   744  		return nil, storage.ErrNotFound
   745  	}
   746  
   747  	return s.stores[storeID], nil
   748  }
   749  
   750  // ListStores provides a paginated list of all stores present in the MemoryBackend.
   751  func (s *MemoryBackend) ListStores(ctx context.Context, paginationOptions storage.PaginationOptions) ([]*openfgav1.Store, []byte, error) {
   752  	_, span := tracer.Start(ctx, "memory.ListStores")
   753  	defer span.End()
   754  
   755  	s.mu.Lock()
   756  	defer s.mu.Unlock()
   757  
   758  	stores := make([]*openfgav1.Store, 0, len(s.stores))
   759  	for _, t := range s.stores {
   760  		stores = append(stores, t)
   761  	}
   762  
   763  	// From oldest to newest.
   764  	sort.SliceStable(stores, func(i, j int) bool {
   765  		return stores[i].GetId() < stores[j].GetId()
   766  	})
   767  
   768  	var err error
   769  	var from int64
   770  	if paginationOptions.From != "" {
   771  		from, err = strconv.ParseInt(paginationOptions.From, 10, 32)
   772  		if err != nil {
   773  			return nil, nil, err
   774  		}
   775  	}
   776  	pageSize := storage.DefaultPageSize
   777  	if paginationOptions.PageSize > 0 {
   778  		pageSize = paginationOptions.PageSize
   779  	}
   780  	to := int(from) + pageSize
   781  	if len(stores) < to {
   782  		to = len(stores)
   783  	}
   784  	res := stores[from:to]
   785  	if len(res) == 0 {
   786  		return nil, nil, nil
   787  	}
   788  
   789  	continuationToken := ""
   790  	if to != len(stores) {
   791  		continuationToken = strconv.Itoa(to)
   792  	}
   793  
   794  	return res, []byte(continuationToken), nil
   795  }
   796  
   797  // IsReady see [storage.OpenFGADatastore].IsReady.
   798  func (s *MemoryBackend) IsReady(context.Context) (storage.ReadinessStatus, error) {
   799  	return storage.ReadinessStatus{IsReady: true}, nil
   800  }