Skip to content

Custom store

All persistence in scimply goes through the store.ResourceStore interface. Implementing it lets you connect to any database, cache, or external API.

store/store.go
type ResourceStore interface {
Create(ctx context.Context, resourceType string, res *resource.Resource) (*resource.Resource, error)
Get(ctx context.Context, resourceType string, id string) (*resource.Resource, error)
List(ctx context.Context, resourceType string, params ListParams) (*ListResult, error)
Replace(ctx context.Context, resourceType string, id string, res *resource.Resource) (*resource.Resource, error)
Patch(ctx context.Context, resourceType string, id string, ops []resource.PatchOp) (*resource.Resource, error)
Delete(ctx context.Context, resourceType string, id string) error
}

All implementations must be safe for concurrent use.

Return these from your store so the server produces the correct HTTP status code:

ErrorHTTP statusWhen to return
store.ErrNotFound404Resource does not exist
store.ErrConflict409Uniqueness constraint violated (e.g. duplicate userName)
package mystore
import (
"context"
"sync"
"github.com/cerberauth/scimply/resource"
"github.com/cerberauth/scimply/store"
)
type MyStore struct {
mu sync.RWMutex
data map[string]map[string]*resource.Resource // resourceType → id → resource
}
func New() *MyStore {
return &MyStore{data: make(map[string]map[string]*resource.Resource)}
}
func (s *MyStore) Create(ctx context.Context, resourceType string, res *resource.Resource) (*resource.Resource, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.data[resourceType]; !ok {
s.data[resourceType] = make(map[string]*resource.Resource)
}
// TODO: generate ID, set meta fields, check uniqueness
s.data[resourceType][res.ID] = res
return res, nil
}
func (s *MyStore) Get(ctx context.Context, resourceType, id string) (*resource.Resource, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if m, ok := s.data[resourceType]; ok {
if r, ok := m[id]; ok {
return r, nil
}
}
return nil, store.ErrNotFound
}
// ... implement remaining methods

If your backend cannot translate params.Filter into a native query, set NeedsClientFilter: true in the returned ListResult. The server will apply the SCIM filter in-process using the built-in evaluator.

func (s *MyStore) List(ctx context.Context, resourceType string, params store.ListParams) (*store.ListResult, error) {
// Fetch all resources without filtering
all := s.fetchAll(resourceType)
return &store.ListResult{
Resources: all,
TotalResults: len(all),
StartIndex: params.StartIndex,
ItemsPerPage: len(all),
NeedsClientFilter: true, // server will apply params.Filter
}, nil
}

Pass your store to server.New via WithStore:

srv, err := server.New(
server.WithStore(mystore.New()),
server.WithSchemaRegistry(reg),
)