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 }