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

     1  // Package server contains the endpoint handlers.
     2  package server
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"sort"
    10  	"strconv"
    11  	"time"
    12  
    13  	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
    14  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/prometheus/client_golang/prometheus/promauto"
    17  	"go.opentelemetry.io/otel"
    18  	"go.opentelemetry.io/otel/attribute"
    19  	"go.opentelemetry.io/otel/trace"
    20  	"go.uber.org/zap"
    21  	"google.golang.org/grpc/codes"
    22  	"google.golang.org/grpc/status"
    23  
    24  	"github.com/openfga/openfga/internal/build"
    25  	"github.com/openfga/openfga/internal/condition"
    26  	"github.com/openfga/openfga/internal/graph"
    27  	serverconfig "github.com/openfga/openfga/internal/server/config"
    28  	"github.com/openfga/openfga/internal/utils"
    29  	"github.com/openfga/openfga/internal/validation"
    30  	"github.com/openfga/openfga/pkg/encoder"
    31  	"github.com/openfga/openfga/pkg/gateway"
    32  	"github.com/openfga/openfga/pkg/logger"
    33  	httpmiddleware "github.com/openfga/openfga/pkg/middleware/http"
    34  	"github.com/openfga/openfga/pkg/middleware/validator"
    35  	"github.com/openfga/openfga/pkg/server/commands"
    36  	serverErrors "github.com/openfga/openfga/pkg/server/errors"
    37  	"github.com/openfga/openfga/pkg/storage"
    38  	"github.com/openfga/openfga/pkg/storage/storagewrappers"
    39  	"github.com/openfga/openfga/pkg/telemetry"
    40  	"github.com/openfga/openfga/pkg/tuple"
    41  	"github.com/openfga/openfga/pkg/typesystem"
    42  )
    43  
    44  type ExperimentalFeatureFlag string
    45  
    46  const (
    47  	AuthorizationModelIDHeader                          = "Openfga-Authorization-Model-Id"
    48  	authorizationModelIDKey                             = "authorization_model_id"
    49  	ExperimentalEnableListUsers ExperimentalFeatureFlag = "enable-list-users"
    50  )
    51  
    52  var tracer = otel.Tracer("openfga/pkg/server")
    53  
    54  var (
    55  	dispatchCountHistogramName = "dispatch_count"
    56  
    57  	dispatchCountHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
    58  		Namespace:                       build.ProjectName,
    59  		Name:                            dispatchCountHistogramName,
    60  		Help:                            "The number of dispatches required to resolve a query (e.g. Check).",
    61  		Buckets:                         []float64{1, 5, 20, 50, 100, 150, 225, 400, 500, 750, 1000},
    62  		NativeHistogramBucketFactor:     1.1,
    63  		NativeHistogramMaxBucketNumber:  100,
    64  		NativeHistogramMinResetDuration: time.Hour,
    65  	}, []string{"grpc_service", "grpc_method"})
    66  
    67  	datastoreQueryCountHistogramName = "datastore_query_count"
    68  
    69  	datastoreQueryCountHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
    70  		Namespace:                       build.ProjectName,
    71  		Name:                            datastoreQueryCountHistogramName,
    72  		Help:                            "The number of database queries required to resolve a query (e.g. Check, ListObjects or ListUsers).",
    73  		Buckets:                         []float64{1, 5, 20, 50, 100, 150, 225, 400, 500, 750, 1000},
    74  		NativeHistogramBucketFactor:     1.1,
    75  		NativeHistogramMaxBucketNumber:  100,
    76  		NativeHistogramMinResetDuration: time.Hour,
    77  	}, []string{"grpc_service", "grpc_method"})
    78  
    79  	requestDurationHistogramName = "request_duration_ms"
    80  
    81  	requestDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
    82  		Namespace:                       build.ProjectName,
    83  		Name:                            requestDurationHistogramName,
    84  		Help:                            "The request duration (in ms) labeled by method and buckets of datastore query counts and number of dispatches. This allows for reporting percentiles based on the number of datastore queries and number of dispatches required to resolve the request.",
    85  		Buckets:                         []float64{1, 5, 10, 25, 50, 80, 100, 150, 200, 300, 1000, 2000, 5000},
    86  		NativeHistogramBucketFactor:     1.1,
    87  		NativeHistogramMaxBucketNumber:  100,
    88  		NativeHistogramMinResetDuration: time.Hour,
    89  	}, []string{"grpc_service", "grpc_method", "datastore_query_count", "dispatch_count"})
    90  )
    91  
    92  // A Server implements the OpenFGA service backend as both
    93  // a GRPC and HTTP server.
    94  type Server struct {
    95  	openfgav1.UnimplementedOpenFGAServiceServer
    96  
    97  	logger                           logger.Logger
    98  	datastore                        storage.OpenFGADatastore
    99  	encoder                          encoder.Encoder
   100  	transport                        gateway.Transport
   101  	resolveNodeLimit                 uint32
   102  	resolveNodeBreadthLimit          uint32
   103  	changelogHorizonOffset           int
   104  	listObjectsDeadline              time.Duration
   105  	listObjectsMaxResults            uint32
   106  	listUsersDeadline                time.Duration
   107  	listUsersMaxResults              uint32
   108  	maxConcurrentReadsForListObjects uint32
   109  	maxConcurrentReadsForCheck       uint32
   110  	maxConcurrentReadsForListUsers   uint32
   111  	maxAuthorizationModelSizeInBytes int
   112  	experimentals                    []ExperimentalFeatureFlag
   113  	serviceName                      string
   114  
   115  	// NOTE don't use this directly, use function resolveTypesystem. See https://github.com/openfga/openfga/issues/1527
   116  	typesystemResolver     typesystem.TypesystemResolverFunc
   117  	typesystemResolverStop func()
   118  
   119  	checkQueryCacheEnabled bool
   120  	checkQueryCacheLimit   uint32
   121  	checkQueryCacheTTL     time.Duration
   122  	cachedCheckResolver    *graph.CachedCheckResolver
   123  
   124  	checkResolver graph.CheckResolver
   125  
   126  	requestDurationByQueryHistogramBuckets         []uint
   127  	requestDurationByDispatchCountHistogramBuckets []uint
   128  
   129  	dispatchThrottlingCheckResolverEnabled   bool
   130  	dispatchThrottlingCheckResolverFrequency time.Duration
   131  	dispatchThrottlingDefaultThreshold       uint32
   132  	dispatchThrottlingMaxThreshold           uint32
   133  
   134  	dispatchThrottlingCheckResolver *graph.DispatchThrottlingCheckResolver
   135  }
   136  
   137  type OpenFGAServiceV1Option func(s *Server)
   138  
   139  // WithDatastore passes a datastore to the Server.
   140  // You must call [storage.OpenFGADatastore.Close] on it after you have stopped using it.
   141  func WithDatastore(ds storage.OpenFGADatastore) OpenFGAServiceV1Option {
   142  	return func(s *Server) {
   143  		s.datastore = ds
   144  	}
   145  }
   146  
   147  func WithLogger(l logger.Logger) OpenFGAServiceV1Option {
   148  	return func(s *Server) {
   149  		s.logger = l
   150  	}
   151  }
   152  
   153  func WithTokenEncoder(encoder encoder.Encoder) OpenFGAServiceV1Option {
   154  	return func(s *Server) {
   155  		s.encoder = encoder
   156  	}
   157  }
   158  
   159  // WithTransport sets the connection transport.
   160  func WithTransport(t gateway.Transport) OpenFGAServiceV1Option {
   161  	return func(s *Server) {
   162  		s.transport = t
   163  	}
   164  }
   165  
   166  // WithResolveNodeLimit sets a limit on the number of recursive calls that one Check, ListObjects or ListUsers call will allow.
   167  // Thinking of a request as a tree of evaluations, this option controls
   168  // how many levels we will evaluate before throwing an error that the authorization model is too complex.
   169  func WithResolveNodeLimit(limit uint32) OpenFGAServiceV1Option {
   170  	return func(s *Server) {
   171  		s.resolveNodeLimit = limit
   172  	}
   173  }
   174  
   175  // WithResolveNodeBreadthLimit sets a limit on the number of goroutines that can be created
   176  // when evaluating a subtree of a Check, ListObjects or ListUsers call.
   177  // Thinking of a Check request as a tree of evaluations, this option controls,
   178  // on a given level of the tree, the maximum number of nodes that can be evaluated concurrently (the breadth).
   179  // If your authorization models are very complex (e.g. one relation is a union of many relations, or one relation
   180  // is deeply nested), or if you have lots of users for (object, relation) pairs,
   181  // you should set this option to be a low number (e.g. 1000).
   182  func WithResolveNodeBreadthLimit(limit uint32) OpenFGAServiceV1Option {
   183  	return func(s *Server) {
   184  		s.resolveNodeBreadthLimit = limit
   185  	}
   186  }
   187  
   188  // WithChangelogHorizonOffset sets an offset (in minutes) from the current time.
   189  // Changes that occur after this offset will not be included in the response of ReadChanges API.
   190  // If your datastore is eventually consistent or if you have a database with replication delay, we recommend setting this (e.g. 1 minute).
   191  func WithChangelogHorizonOffset(offset int) OpenFGAServiceV1Option {
   192  	return func(s *Server) {
   193  		s.changelogHorizonOffset = offset
   194  	}
   195  }
   196  
   197  // WithListObjectsDeadline affect the ListObjects API only.
   198  // It sets the maximum amount of time that the server will spend gathering results.
   199  func WithListObjectsDeadline(deadline time.Duration) OpenFGAServiceV1Option {
   200  	return func(s *Server) {
   201  		s.listObjectsDeadline = deadline
   202  	}
   203  }
   204  
   205  // WithListObjectsMaxResults affects the ListObjects API only.
   206  // It sets the maximum number of results that this API will return.
   207  func WithListObjectsMaxResults(limit uint32) OpenFGAServiceV1Option {
   208  	return func(s *Server) {
   209  		s.listObjectsMaxResults = limit
   210  	}
   211  }
   212  
   213  // WithListUsersDeadline affect the ListUsers API only.
   214  // It sets the maximum amount of time that the server will spend gathering results.
   215  func WithListUsersDeadline(deadline time.Duration) OpenFGAServiceV1Option {
   216  	return func(s *Server) {
   217  		s.listUsersDeadline = deadline
   218  	}
   219  }
   220  
   221  // WithListUsersMaxResults affects the ListUsers API only.
   222  // It sets the maximum number of results that this API will return.
   223  // If it's zero, all results will be attempted to be returned.
   224  func WithListUsersMaxResults(limit uint32) OpenFGAServiceV1Option {
   225  	return func(s *Server) {
   226  		s.listUsersMaxResults = limit
   227  	}
   228  }
   229  
   230  // WithMaxConcurrentReadsForListObjects sets a limit on the number of datastore reads that can be in flight for a given ListObjects call.
   231  // This number should be set depending on the RPS expected for Check and ListObjects APIs, the number of OpenFGA replicas running,
   232  // and the number of connections the datastore allows.
   233  // E.g. If Datastore.MaxOpenConns = 100 and assuming that each ListObjects call takes 1 second and no traffic to Check API:
   234  // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1.
   235  // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100.
   236  // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50.
   237  func WithMaxConcurrentReadsForListObjects(max uint32) OpenFGAServiceV1Option {
   238  	return func(s *Server) {
   239  		s.maxConcurrentReadsForListObjects = max
   240  	}
   241  }
   242  
   243  // WithMaxConcurrentReadsForCheck sets a limit on the number of datastore reads that can be in flight for a given Check call.
   244  // This number should be set depending on the RPS expected for Check and ListObjects APIs, the number of OpenFGA replicas running,
   245  // and the number of connections the datastore allows.
   246  // E.g. If Datastore.MaxOpenConns = 100 and assuming that each Check call takes 1 second and no traffic to ListObjects API:
   247  // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1.
   248  // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100.
   249  // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50.
   250  func WithMaxConcurrentReadsForCheck(max uint32) OpenFGAServiceV1Option {
   251  	return func(s *Server) {
   252  		s.maxConcurrentReadsForCheck = max
   253  	}
   254  }
   255  
   256  // WithMaxConcurrentReadsForListUsers sets a limit on the number of datastore reads that can be in flight for a given ListUsers call.
   257  // This number should be set depending on the RPS expected for all query APIs, the number of OpenFGA replicas running,
   258  // and the number of connections the datastore allows.
   259  // E.g. If Datastore.MaxOpenConns = 100 and assuming that each ListUsers call takes 1 second and no traffic to other query APIs:
   260  // - One OpenFGA replica and expected traffic of 100 RPS => set it to 1.
   261  // - One OpenFGA replica and expected traffic of 1 RPS => set it to 100.
   262  // - Two OpenFGA replicas and expected traffic of 1 RPS => set it to 50.
   263  func WithMaxConcurrentReadsForListUsers(max uint32) OpenFGAServiceV1Option {
   264  	return func(s *Server) {
   265  		s.maxConcurrentReadsForListUsers = max
   266  	}
   267  }
   268  
   269  func WithExperimentals(experimentals ...ExperimentalFeatureFlag) OpenFGAServiceV1Option {
   270  	return func(s *Server) {
   271  		s.experimentals = experimentals
   272  	}
   273  }
   274  
   275  // WithCheckQueryCacheEnabled enables caching of Check results for the Check and List objects APIs.
   276  // This cache is shared for all requests.
   277  // See also WithCheckQueryCacheLimit and WithCheckQueryCacheTTL.
   278  func WithCheckQueryCacheEnabled(enabled bool) OpenFGAServiceV1Option {
   279  	return func(s *Server) {
   280  		s.checkQueryCacheEnabled = enabled
   281  	}
   282  }
   283  
   284  // WithCheckQueryCacheLimit sets the cache size limit (in items)
   285  // Needs WithCheckQueryCacheEnabled set to true.
   286  func WithCheckQueryCacheLimit(limit uint32) OpenFGAServiceV1Option {
   287  	return func(s *Server) {
   288  		s.checkQueryCacheLimit = limit
   289  	}
   290  }
   291  
   292  // WithCheckQueryCacheTTL sets the TTL of cached checks and list objects partial results
   293  // Needs WithCheckQueryCacheEnabled set to true.
   294  func WithCheckQueryCacheTTL(ttl time.Duration) OpenFGAServiceV1Option {
   295  	return func(s *Server) {
   296  		s.checkQueryCacheTTL = ttl
   297  	}
   298  }
   299  
   300  // WithRequestDurationByQueryHistogramBuckets sets the buckets used in labelling the requestDurationByQueryAndDispatchHistogram.
   301  func WithRequestDurationByQueryHistogramBuckets(buckets []uint) OpenFGAServiceV1Option {
   302  	return func(s *Server) {
   303  		sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] })
   304  		s.requestDurationByQueryHistogramBuckets = buckets
   305  	}
   306  }
   307  
   308  // WithRequestDurationByDispatchCountHistogramBuckets sets the buckets used in labelling the requestDurationByQueryAndDispatchHistogram.
   309  func WithRequestDurationByDispatchCountHistogramBuckets(buckets []uint) OpenFGAServiceV1Option {
   310  	return func(s *Server) {
   311  		sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] })
   312  		s.requestDurationByDispatchCountHistogramBuckets = buckets
   313  	}
   314  }
   315  
   316  func WithMaxAuthorizationModelSizeInBytes(size int) OpenFGAServiceV1Option {
   317  	return func(s *Server) {
   318  		s.maxAuthorizationModelSizeInBytes = size
   319  	}
   320  }
   321  
   322  // WithDispatchThrottlingCheckResolverEnabled sets whether dispatch throttling is enabled.
   323  // Enabling this feature will prioritize dispatched requests requiring less than the configured dispatch
   324  // threshold over requests whose dispatch count exceeds the configured threshold.
   325  func WithDispatchThrottlingCheckResolverEnabled(enabled bool) OpenFGAServiceV1Option {
   326  	return func(s *Server) {
   327  		s.dispatchThrottlingCheckResolverEnabled = enabled
   328  	}
   329  }
   330  
   331  // WithDispatchThrottlingCheckResolverFrequency defines how frequent dispatch throttling will be evaluated.
   332  // Frequency controls how frequently throttled dispatch requests are evaluated to determine whether
   333  // it can be processed.
   334  // This value should not be too small (i.e., in the ns ranges) as i) there are limitation in timer resolution
   335  // and ii) very small value will result in a higher frequency of processing dispatches,
   336  // which diminishes the value of the throttling.
   337  func WithDispatchThrottlingCheckResolverFrequency(frequency time.Duration) OpenFGAServiceV1Option {
   338  	return func(s *Server) {
   339  		s.dispatchThrottlingCheckResolverFrequency = frequency
   340  	}
   341  }
   342  
   343  // WithDispatchThrottlingCheckResolverThreshold define the number of dispatches to be throttled.
   344  // In addition, it will update dispatchThrottlingMaxThreshold if required.
   345  func WithDispatchThrottlingCheckResolverThreshold(defaultThreshold uint32) OpenFGAServiceV1Option {
   346  	return func(s *Server) {
   347  		s.dispatchThrottlingDefaultThreshold = defaultThreshold
   348  	}
   349  }
   350  
   351  // WithDispatchThrottlingCheckResolverMaxThreshold define the maximum threshold values allowed
   352  // It will ensure dispatchThrottlingMaxThreshold will never be smaller than threshold.
   353  func WithDispatchThrottlingCheckResolverMaxThreshold(maxThreshold uint32) OpenFGAServiceV1Option {
   354  	return func(s *Server) {
   355  		s.dispatchThrottlingMaxThreshold = maxThreshold
   356  	}
   357  }
   358  
   359  // MustNewServerWithOpts see NewServerWithOpts.
   360  func MustNewServerWithOpts(opts ...OpenFGAServiceV1Option) *Server {
   361  	s, err := NewServerWithOpts(opts...)
   362  	if err != nil {
   363  		panic(fmt.Errorf("failed to construct the OpenFGA server: %w", err))
   364  	}
   365  
   366  	return s
   367  }
   368  
   369  func (s *Server) IsExperimentallyEnabled(flag ExperimentalFeatureFlag) bool {
   370  	for _, e := range s.experimentals {
   371  		if e == flag {
   372  			return true
   373  		}
   374  	}
   375  	return false
   376  }
   377  
   378  // NewServerWithOpts returns a new server.
   379  // You must call Close on it after you are done using it.
   380  func NewServerWithOpts(opts ...OpenFGAServiceV1Option) (*Server, error) {
   381  	s := &Server{
   382  		logger:                           logger.NewNoopLogger(),
   383  		encoder:                          encoder.NewBase64Encoder(),
   384  		transport:                        gateway.NewNoopTransport(),
   385  		changelogHorizonOffset:           serverconfig.DefaultChangelogHorizonOffset,
   386  		resolveNodeLimit:                 serverconfig.DefaultResolveNodeLimit,
   387  		resolveNodeBreadthLimit:          serverconfig.DefaultResolveNodeBreadthLimit,
   388  		listObjectsDeadline:              serverconfig.DefaultListObjectsDeadline,
   389  		listObjectsMaxResults:            serverconfig.DefaultListObjectsMaxResults,
   390  		listUsersDeadline:                serverconfig.DefaultListUsersDeadline,
   391  		listUsersMaxResults:              serverconfig.DefaultListUsersMaxResults,
   392  		maxConcurrentReadsForCheck:       serverconfig.DefaultMaxConcurrentReadsForCheck,
   393  		maxConcurrentReadsForListObjects: serverconfig.DefaultMaxConcurrentReadsForListObjects,
   394  		maxConcurrentReadsForListUsers:   serverconfig.DefaultMaxConcurrentReadsForListUsers,
   395  		maxAuthorizationModelSizeInBytes: serverconfig.DefaultMaxAuthorizationModelSizeInBytes,
   396  		experimentals:                    make([]ExperimentalFeatureFlag, 0, 10),
   397  
   398  		checkQueryCacheEnabled: serverconfig.DefaultCheckQueryCacheEnable,
   399  		checkQueryCacheLimit:   serverconfig.DefaultCheckQueryCacheLimit,
   400  		checkQueryCacheTTL:     serverconfig.DefaultCheckQueryCacheTTL,
   401  		checkResolver:          nil,
   402  
   403  		requestDurationByQueryHistogramBuckets:         []uint{50, 200},
   404  		requestDurationByDispatchCountHistogramBuckets: []uint{50, 200},
   405  		serviceName: openfgav1.OpenFGAService_ServiceDesc.ServiceName,
   406  
   407  		dispatchThrottlingCheckResolverEnabled:   serverconfig.DefaultDispatchThrottlingEnabled,
   408  		dispatchThrottlingCheckResolverFrequency: serverconfig.DefaultDispatchThrottlingFrequency,
   409  		dispatchThrottlingDefaultThreshold:       serverconfig.DefaultDispatchThrottlingDefaultThreshold,
   410  	}
   411  
   412  	for _, opt := range opts {
   413  		opt(s)
   414  	}
   415  
   416  	cycleDetectionCheckResolver := graph.NewCycleDetectionCheckResolver()
   417  	s.checkResolver = cycleDetectionCheckResolver
   418  
   419  	localChecker := graph.NewLocalChecker(
   420  		graph.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit),
   421  	)
   422  
   423  	cycleDetectionCheckResolver.SetDelegate(localChecker)
   424  	localChecker.SetDelegate(cycleDetectionCheckResolver)
   425  
   426  	if s.dispatchThrottlingCheckResolverEnabled {
   427  		dispatchThrottlingConfig := graph.DispatchThrottlingCheckResolverConfig{
   428  			Frequency:        s.dispatchThrottlingCheckResolverFrequency,
   429  			DefaultThreshold: s.dispatchThrottlingDefaultThreshold,
   430  			MaxThreshold:     s.dispatchThrottlingMaxThreshold,
   431  		}
   432  
   433  		if s.dispatchThrottlingMaxThreshold != 0 && s.dispatchThrottlingDefaultThreshold > s.dispatchThrottlingMaxThreshold {
   434  			return nil, fmt.Errorf("default dispatch throttling threshold must be equal or smaller than max dispatch threshold")
   435  		}
   436  
   437  		s.logger.Info("Enabling dispatch throttling",
   438  			zap.Duration("Frequency", s.dispatchThrottlingCheckResolverFrequency),
   439  			zap.Uint32("DefaultThreshold", s.dispatchThrottlingDefaultThreshold),
   440  			zap.Uint32("MaxThreshold", s.dispatchThrottlingMaxThreshold),
   441  		)
   442  
   443  		dispatchThrottlingCheckResolver := graph.NewDispatchThrottlingCheckResolver(dispatchThrottlingConfig)
   444  		dispatchThrottlingCheckResolver.SetDelegate(localChecker)
   445  		s.dispatchThrottlingCheckResolver = dispatchThrottlingCheckResolver
   446  
   447  		cycleDetectionCheckResolver.SetDelegate(dispatchThrottlingCheckResolver)
   448  	}
   449  
   450  	if s.checkQueryCacheEnabled {
   451  		s.logger.Info("Check query cache is enabled and may lead to stale query results up to the configured query cache TTL",
   452  			zap.Duration("CheckQueryCacheTTL", s.checkQueryCacheTTL),
   453  			zap.Uint32("CheckQueryCacheLimit", s.checkQueryCacheLimit))
   454  
   455  		cachedCheckResolver := graph.NewCachedCheckResolver(
   456  			graph.WithMaxCacheSize(int64(s.checkQueryCacheLimit)),
   457  			graph.WithLogger(s.logger),
   458  			graph.WithCacheTTL(s.checkQueryCacheTTL),
   459  		)
   460  		s.cachedCheckResolver = cachedCheckResolver
   461  
   462  		cachedCheckResolver.SetDelegate(localChecker)
   463  		if s.dispatchThrottlingCheckResolver != nil {
   464  			s.dispatchThrottlingCheckResolver.SetDelegate(cachedCheckResolver)
   465  		} else {
   466  			cycleDetectionCheckResolver.SetDelegate(cachedCheckResolver)
   467  		}
   468  	}
   469  
   470  	if s.datastore == nil {
   471  		return nil, fmt.Errorf("a datastore option must be provided")
   472  	}
   473  
   474  	if len(s.requestDurationByQueryHistogramBuckets) == 0 {
   475  		return nil, fmt.Errorf("request duration datastore count buckets must not be empty")
   476  	}
   477  
   478  	if len(s.requestDurationByDispatchCountHistogramBuckets) == 0 {
   479  		return nil, fmt.Errorf("request duration by dispatch count buckets must not be empty")
   480  	}
   481  
   482  	s.typesystemResolver, s.typesystemResolverStop = typesystem.MemoizedTypesystemResolverFunc(s.datastore)
   483  
   484  	return s, nil
   485  }
   486  
   487  // Close releases the server resources.
   488  func (s *Server) Close() {
   489  	if s.dispatchThrottlingCheckResolver != nil {
   490  		s.dispatchThrottlingCheckResolver.Close()
   491  	}
   492  
   493  	if s.cachedCheckResolver != nil {
   494  		s.cachedCheckResolver.Close()
   495  	}
   496  
   497  	if s.checkResolver != nil {
   498  		s.checkResolver.Close()
   499  	}
   500  
   501  	s.typesystemResolverStop()
   502  }
   503  
   504  func (s *Server) ListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
   505  	start := time.Now()
   506  
   507  	targetObjectType := req.GetType()
   508  
   509  	ctx, span := tracer.Start(ctx, "ListObjects", trace.WithAttributes(
   510  		attribute.String("object_type", targetObjectType),
   511  		attribute.String("relation", req.GetRelation()),
   512  		attribute.String("user", req.GetUser()),
   513  	))
   514  	defer span.End()
   515  
   516  	if !validator.RequestIsValidatedFromContext(ctx) {
   517  		if err := req.Validate(); err != nil {
   518  			return nil, status.Error(codes.InvalidArgument, err.Error())
   519  		}
   520  	}
   521  
   522  	const methodName = "listobjects"
   523  
   524  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   525  		Service: s.serviceName,
   526  		Method:  methodName,
   527  	})
   528  
   529  	storeID := req.GetStoreId()
   530  
   531  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	q, err := commands.NewListObjectsQuery(
   537  		s.datastore,
   538  		s.checkResolver,
   539  		commands.WithLogger(s.logger),
   540  		commands.WithListObjectsDeadline(s.listObjectsDeadline),
   541  		commands.WithListObjectsMaxResults(s.listObjectsMaxResults),
   542  		commands.WithResolveNodeLimit(s.resolveNodeLimit),
   543  		commands.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit),
   544  		commands.WithMaxConcurrentReads(s.maxConcurrentReadsForListObjects),
   545  	)
   546  	if err != nil {
   547  		return nil, serverErrors.NewInternalError("", err)
   548  	}
   549  
   550  	result, err := q.Execute(
   551  		typesystem.ContextWithTypesystem(ctx, typesys),
   552  		&openfgav1.ListObjectsRequest{
   553  			StoreId:              storeID,
   554  			ContextualTuples:     req.GetContextualTuples(),
   555  			AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id
   556  			Type:                 targetObjectType,
   557  			Relation:             req.GetRelation(),
   558  			User:                 req.GetUser(),
   559  			Context:              req.GetContext(),
   560  		},
   561  	)
   562  	if err != nil {
   563  		telemetry.TraceError(span, err)
   564  		if errors.Is(err, condition.ErrEvaluationFailed) {
   565  			return nil, serverErrors.ValidationError(err)
   566  		}
   567  
   568  		return nil, err
   569  	}
   570  	datastoreQueryCount := float64(*result.ResolutionMetadata.DatastoreQueryCount)
   571  
   572  	grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, datastoreQueryCount)
   573  	span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, datastoreQueryCount))
   574  	datastoreQueryCountHistogram.WithLabelValues(
   575  		s.serviceName,
   576  		methodName,
   577  	).Observe(datastoreQueryCount)
   578  
   579  	dispatchCount := float64(*result.ResolutionMetadata.DispatchCount)
   580  
   581  	grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount)
   582  	span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount))
   583  	dispatchCountHistogram.WithLabelValues(
   584  		s.serviceName,
   585  		methodName,
   586  	).Observe(dispatchCount)
   587  
   588  	requestDurationHistogram.WithLabelValues(
   589  		s.serviceName,
   590  		methodName,
   591  		utils.Bucketize(uint(*result.ResolutionMetadata.DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets),
   592  		utils.Bucketize(uint(*result.ResolutionMetadata.DispatchCount), s.requestDurationByDispatchCountHistogramBuckets),
   593  	).Observe(float64(time.Since(start).Milliseconds()))
   594  
   595  	return &openfgav1.ListObjectsResponse{
   596  		Objects: result.Objects,
   597  	}, nil
   598  }
   599  
   600  func (s *Server) StreamedListObjects(req *openfgav1.StreamedListObjectsRequest, srv openfgav1.OpenFGAService_StreamedListObjectsServer) error {
   601  	start := time.Now()
   602  
   603  	ctx := srv.Context()
   604  	ctx, span := tracer.Start(ctx, "StreamedListObjects", trace.WithAttributes(
   605  		attribute.String("object_type", req.GetType()),
   606  		attribute.String("relation", req.GetRelation()),
   607  		attribute.String("user", req.GetUser()),
   608  	))
   609  	defer span.End()
   610  
   611  	if !validator.RequestIsValidatedFromContext(ctx) {
   612  		if err := req.Validate(); err != nil {
   613  			return status.Error(codes.InvalidArgument, err.Error())
   614  		}
   615  	}
   616  
   617  	const methodName = "streamedlistobjects"
   618  
   619  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   620  		Service: s.serviceName,
   621  		Method:  methodName,
   622  	})
   623  
   624  	storeID := req.GetStoreId()
   625  
   626  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   627  	if err != nil {
   628  		return err
   629  	}
   630  
   631  	q, err := commands.NewListObjectsQuery(
   632  		s.datastore,
   633  		s.checkResolver,
   634  		commands.WithLogger(s.logger),
   635  		commands.WithListObjectsDeadline(s.listObjectsDeadline),
   636  		commands.WithListObjectsMaxResults(s.listObjectsMaxResults),
   637  		commands.WithResolveNodeLimit(s.resolveNodeLimit),
   638  		commands.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit),
   639  		commands.WithMaxConcurrentReads(s.maxConcurrentReadsForListObjects),
   640  	)
   641  	if err != nil {
   642  		return serverErrors.NewInternalError("", err)
   643  	}
   644  
   645  	req.AuthorizationModelId = typesys.GetAuthorizationModelID() // the resolved model id
   646  
   647  	resolutionMetadata, err := q.ExecuteStreamed(
   648  		typesystem.ContextWithTypesystem(ctx, typesys),
   649  		req,
   650  		srv,
   651  	)
   652  	if err != nil {
   653  		telemetry.TraceError(span, err)
   654  		return err
   655  	}
   656  	datastoreQueryCount := float64(*resolutionMetadata.DatastoreQueryCount)
   657  
   658  	grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, datastoreQueryCount)
   659  	span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, datastoreQueryCount))
   660  	datastoreQueryCountHistogram.WithLabelValues(
   661  		s.serviceName,
   662  		methodName,
   663  	).Observe(datastoreQueryCount)
   664  
   665  	dispatchCount := float64(*resolutionMetadata.DispatchCount)
   666  
   667  	grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount)
   668  	span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount))
   669  	dispatchCountHistogram.WithLabelValues(
   670  		s.serviceName,
   671  		methodName,
   672  	).Observe(dispatchCount)
   673  
   674  	requestDurationHistogram.WithLabelValues(
   675  		s.serviceName,
   676  		methodName,
   677  		utils.Bucketize(uint(*resolutionMetadata.DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets),
   678  		utils.Bucketize(uint(*resolutionMetadata.DispatchCount), s.requestDurationByDispatchCountHistogramBuckets),
   679  	).Observe(float64(time.Since(start).Milliseconds()))
   680  
   681  	return nil
   682  }
   683  
   684  func (s *Server) Read(ctx context.Context, req *openfgav1.ReadRequest) (*openfgav1.ReadResponse, error) {
   685  	tk := req.GetTupleKey()
   686  	ctx, span := tracer.Start(ctx, "Read", trace.WithAttributes(
   687  		attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())},
   688  		attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())},
   689  		attribute.KeyValue{Key: "user", Value: attribute.StringValue(tk.GetUser())},
   690  	))
   691  	defer span.End()
   692  
   693  	if !validator.RequestIsValidatedFromContext(ctx) {
   694  		if err := req.Validate(); err != nil {
   695  			return nil, status.Error(codes.InvalidArgument, err.Error())
   696  		}
   697  	}
   698  
   699  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   700  		Service: s.serviceName,
   701  		Method:  "Read",
   702  	})
   703  
   704  	q := commands.NewReadQuery(s.datastore,
   705  		commands.WithReadQueryLogger(s.logger),
   706  		commands.WithReadQueryEncoder(s.encoder),
   707  	)
   708  	return q.Execute(ctx, &openfgav1.ReadRequest{
   709  		StoreId:           req.GetStoreId(),
   710  		TupleKey:          tk,
   711  		PageSize:          req.GetPageSize(),
   712  		ContinuationToken: req.GetContinuationToken(),
   713  	})
   714  }
   715  
   716  func (s *Server) Write(ctx context.Context, req *openfgav1.WriteRequest) (*openfgav1.WriteResponse, error) {
   717  	ctx, span := tracer.Start(ctx, "Write")
   718  	defer span.End()
   719  
   720  	if !validator.RequestIsValidatedFromContext(ctx) {
   721  		if err := req.Validate(); err != nil {
   722  			return nil, status.Error(codes.InvalidArgument, err.Error())
   723  		}
   724  	}
   725  
   726  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   727  		Service: s.serviceName,
   728  		Method:  "Write",
   729  	})
   730  
   731  	storeID := req.GetStoreId()
   732  
   733  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  
   738  	cmd := commands.NewWriteCommand(
   739  		s.datastore,
   740  		commands.WithWriteCmdLogger(s.logger),
   741  	)
   742  	return cmd.Execute(ctx, &openfgav1.WriteRequest{
   743  		StoreId:              storeID,
   744  		AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id
   745  		Writes:               req.GetWrites(),
   746  		Deletes:              req.GetDeletes(),
   747  	})
   748  }
   749  
   750  func (s *Server) Check(ctx context.Context, req *openfgav1.CheckRequest) (*openfgav1.CheckResponse, error) {
   751  	start := time.Now()
   752  
   753  	tk := req.GetTupleKey()
   754  	ctx, span := tracer.Start(ctx, "Check", trace.WithAttributes(
   755  		attribute.KeyValue{Key: "store_id", Value: attribute.StringValue(req.GetStoreId())},
   756  		attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())},
   757  		attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())},
   758  		attribute.KeyValue{Key: "user", Value: attribute.StringValue(tk.GetUser())},
   759  	))
   760  	defer span.End()
   761  
   762  	if !validator.RequestIsValidatedFromContext(ctx) {
   763  		if err := req.Validate(); err != nil {
   764  			return nil, status.Error(codes.InvalidArgument, err.Error())
   765  		}
   766  	}
   767  
   768  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   769  		Service: s.serviceName,
   770  		Method:  "Check",
   771  	})
   772  
   773  	storeID := req.GetStoreId()
   774  
   775  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   776  	if err != nil {
   777  		return nil, err
   778  	}
   779  
   780  	if err := validation.ValidateUserObjectRelation(typesys, tuple.ConvertCheckRequestTupleKeyToTupleKey(tk)); err != nil {
   781  		return nil, serverErrors.ValidationError(err)
   782  	}
   783  
   784  	for _, ctxTuple := range req.GetContextualTuples().GetTupleKeys() {
   785  		if err := validation.ValidateTuple(typesys, ctxTuple); err != nil {
   786  			return nil, serverErrors.HandleTupleValidateError(err)
   787  		}
   788  	}
   789  
   790  	ctx = typesystem.ContextWithTypesystem(ctx, typesys)
   791  	ctx = storage.ContextWithRelationshipTupleReader(ctx,
   792  		storagewrappers.NewBoundedConcurrencyTupleReader(
   793  			storagewrappers.NewCombinedTupleReader(
   794  				s.datastore,
   795  				req.GetContextualTuples().GetTupleKeys(),
   796  			),
   797  			s.maxConcurrentReadsForCheck,
   798  		),
   799  	)
   800  
   801  	checkRequestMetadata := graph.NewCheckRequestMetadata(s.resolveNodeLimit)
   802  
   803  	resolveCheckRequest := graph.ResolveCheckRequest{
   804  		StoreID:              req.GetStoreId(),
   805  		AuthorizationModelID: typesys.GetAuthorizationModelID(), // the resolved model id
   806  		TupleKey:             tuple.ConvertCheckRequestTupleKeyToTupleKey(req.GetTupleKey()),
   807  		ContextualTuples:     req.GetContextualTuples().GetTupleKeys(),
   808  		Context:              req.GetContext(),
   809  		RequestMetadata:      checkRequestMetadata,
   810  	}
   811  
   812  	resp, err := s.checkResolver.ResolveCheck(ctx, &resolveCheckRequest)
   813  	if err != nil {
   814  		telemetry.TraceError(span, err)
   815  		if errors.Is(err, graph.ErrResolutionDepthExceeded) {
   816  			return nil, serverErrors.AuthorizationModelResolutionTooComplex
   817  		}
   818  
   819  		if errors.Is(err, condition.ErrEvaluationFailed) {
   820  			return nil, serverErrors.ValidationError(err)
   821  		}
   822  
   823  		if errors.Is(err, context.DeadlineExceeded) && resolveCheckRequest.GetRequestMetadata().WasThrottled.Load() {
   824  			return nil, serverErrors.ThrottledTimeout
   825  		}
   826  
   827  		return nil, serverErrors.HandleError("", err)
   828  	}
   829  
   830  	queryCount := float64(resp.GetResolutionMetadata().DatastoreQueryCount)
   831  	const methodName = "check"
   832  
   833  	grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, queryCount)
   834  	span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, queryCount))
   835  	datastoreQueryCountHistogram.WithLabelValues(
   836  		s.serviceName,
   837  		methodName,
   838  	).Observe(queryCount)
   839  
   840  	rawDispatchCount := checkRequestMetadata.DispatchCounter.Load()
   841  	dispatchCount := float64(rawDispatchCount)
   842  
   843  	grpc_ctxtags.Extract(ctx).Set(dispatchCountHistogramName, dispatchCount)
   844  	span.SetAttributes(attribute.Float64(dispatchCountHistogramName, dispatchCount))
   845  	dispatchCountHistogram.WithLabelValues(
   846  		s.serviceName,
   847  		methodName,
   848  	).Observe(dispatchCount)
   849  
   850  	res := &openfgav1.CheckResponse{
   851  		Allowed: resp.Allowed,
   852  	}
   853  
   854  	span.SetAttributes(attribute.KeyValue{Key: "allowed", Value: attribute.BoolValue(res.GetAllowed())})
   855  
   856  	requestDurationHistogram.WithLabelValues(
   857  		s.serviceName,
   858  		methodName,
   859  		utils.Bucketize(uint(resp.GetResolutionMetadata().DatastoreQueryCount), s.requestDurationByQueryHistogramBuckets),
   860  		utils.Bucketize(uint(rawDispatchCount), s.requestDurationByDispatchCountHistogramBuckets),
   861  	).Observe(float64(time.Since(start).Milliseconds()))
   862  
   863  	return res, nil
   864  }
   865  
   866  func (s *Server) Expand(ctx context.Context, req *openfgav1.ExpandRequest) (*openfgav1.ExpandResponse, error) {
   867  	tk := req.GetTupleKey()
   868  	ctx, span := tracer.Start(ctx, "Expand", trace.WithAttributes(
   869  		attribute.KeyValue{Key: "object", Value: attribute.StringValue(tk.GetObject())},
   870  		attribute.KeyValue{Key: "relation", Value: attribute.StringValue(tk.GetRelation())},
   871  	))
   872  	defer span.End()
   873  
   874  	if !validator.RequestIsValidatedFromContext(ctx) {
   875  		if err := req.Validate(); err != nil {
   876  			return nil, status.Error(codes.InvalidArgument, err.Error())
   877  		}
   878  	}
   879  
   880  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   881  		Service: s.serviceName,
   882  		Method:  "Expand",
   883  	})
   884  
   885  	storeID := req.GetStoreId()
   886  
   887  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   888  	if err != nil {
   889  		return nil, err
   890  	}
   891  
   892  	q := commands.NewExpandQuery(s.datastore, commands.WithExpandQueryLogger(s.logger))
   893  	return q.Execute(ctx, &openfgav1.ExpandRequest{
   894  		StoreId:              storeID,
   895  		AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id
   896  		TupleKey:             tk,
   897  	})
   898  }
   899  
   900  func (s *Server) ReadAuthorizationModel(ctx context.Context, req *openfgav1.ReadAuthorizationModelRequest) (*openfgav1.ReadAuthorizationModelResponse, error) {
   901  	ctx, span := tracer.Start(ctx, "ReadAuthorizationModel", trace.WithAttributes(
   902  		attribute.KeyValue{Key: authorizationModelIDKey, Value: attribute.StringValue(req.GetId())},
   903  	))
   904  	defer span.End()
   905  
   906  	if !validator.RequestIsValidatedFromContext(ctx) {
   907  		if err := req.Validate(); err != nil {
   908  			return nil, status.Error(codes.InvalidArgument, err.Error())
   909  		}
   910  	}
   911  
   912  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   913  		Service: s.serviceName,
   914  		Method:  "ReadAuthorizationModels",
   915  	})
   916  
   917  	q := commands.NewReadAuthorizationModelQuery(s.datastore, commands.WithReadAuthModelQueryLogger(s.logger))
   918  	return q.Execute(ctx, req)
   919  }
   920  
   921  func (s *Server) WriteAuthorizationModel(ctx context.Context, req *openfgav1.WriteAuthorizationModelRequest) (*openfgav1.WriteAuthorizationModelResponse, error) {
   922  	ctx, span := tracer.Start(ctx, "WriteAuthorizationModel")
   923  	defer span.End()
   924  
   925  	if !validator.RequestIsValidatedFromContext(ctx) {
   926  		if err := req.Validate(); err != nil {
   927  			return nil, status.Error(codes.InvalidArgument, err.Error())
   928  		}
   929  	}
   930  
   931  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   932  		Service: s.serviceName,
   933  		Method:  "WriteAuthorizationModel",
   934  	})
   935  
   936  	c := commands.NewWriteAuthorizationModelCommand(s.datastore,
   937  		commands.WithWriteAuthModelLogger(s.logger),
   938  		commands.WithWriteAuthModelMaxSizeInBytes(s.maxAuthorizationModelSizeInBytes),
   939  	)
   940  	res, err := c.Execute(ctx, req)
   941  	if err != nil {
   942  		return nil, err
   943  	}
   944  
   945  	s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusCreated))
   946  
   947  	return res, nil
   948  }
   949  
   950  func (s *Server) ReadAuthorizationModels(ctx context.Context, req *openfgav1.ReadAuthorizationModelsRequest) (*openfgav1.ReadAuthorizationModelsResponse, error) {
   951  	ctx, span := tracer.Start(ctx, "ReadAuthorizationModels")
   952  	defer span.End()
   953  
   954  	if !validator.RequestIsValidatedFromContext(ctx) {
   955  		if err := req.Validate(); err != nil {
   956  			return nil, status.Error(codes.InvalidArgument, err.Error())
   957  		}
   958  	}
   959  
   960  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   961  		Service: s.serviceName,
   962  		Method:  "ReadAuthorizationModels",
   963  	})
   964  
   965  	c := commands.NewReadAuthorizationModelsQuery(s.datastore,
   966  		commands.WithReadAuthModelsQueryLogger(s.logger),
   967  		commands.WithReadAuthModelsQueryEncoder(s.encoder),
   968  	)
   969  	return c.Execute(ctx, req)
   970  }
   971  
   972  func (s *Server) WriteAssertions(ctx context.Context, req *openfgav1.WriteAssertionsRequest) (*openfgav1.WriteAssertionsResponse, error) {
   973  	ctx, span := tracer.Start(ctx, "WriteAssertions")
   974  	defer span.End()
   975  
   976  	if !validator.RequestIsValidatedFromContext(ctx) {
   977  		if err := req.Validate(); err != nil {
   978  			return nil, status.Error(codes.InvalidArgument, err.Error())
   979  		}
   980  	}
   981  
   982  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
   983  		Service: s.serviceName,
   984  		Method:  "WriteAssertions",
   985  	})
   986  
   987  	storeID := req.GetStoreId()
   988  
   989  	typesys, err := s.resolveTypesystem(ctx, storeID, req.GetAuthorizationModelId())
   990  	if err != nil {
   991  		return nil, err
   992  	}
   993  
   994  	c := commands.NewWriteAssertionsCommand(s.datastore, commands.WithWriteAssertCmdLogger(s.logger))
   995  	res, err := c.Execute(ctx, &openfgav1.WriteAssertionsRequest{
   996  		StoreId:              storeID,
   997  		AuthorizationModelId: typesys.GetAuthorizationModelID(), // the resolved model id
   998  		Assertions:           req.GetAssertions(),
   999  	})
  1000  	if err != nil {
  1001  		return nil, err
  1002  	}
  1003  
  1004  	s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusNoContent))
  1005  
  1006  	return res, nil
  1007  }
  1008  
  1009  func (s *Server) ReadAssertions(ctx context.Context, req *openfgav1.ReadAssertionsRequest) (*openfgav1.ReadAssertionsResponse, error) {
  1010  	ctx, span := tracer.Start(ctx, "ReadAssertions")
  1011  	defer span.End()
  1012  
  1013  	if !validator.RequestIsValidatedFromContext(ctx) {
  1014  		if err := req.Validate(); err != nil {
  1015  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1016  		}
  1017  	}
  1018  
  1019  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1020  		Service: s.serviceName,
  1021  		Method:  "ReadAssertions",
  1022  	})
  1023  
  1024  	typesys, err := s.resolveTypesystem(ctx, req.GetStoreId(), req.GetAuthorizationModelId())
  1025  	if err != nil {
  1026  		return nil, err
  1027  	}
  1028  
  1029  	q := commands.NewReadAssertionsQuery(s.datastore, commands.WithReadAssertionsQueryLogger(s.logger))
  1030  	return q.Execute(ctx, req.GetStoreId(), typesys.GetAuthorizationModelID())
  1031  }
  1032  
  1033  func (s *Server) ReadChanges(ctx context.Context, req *openfgav1.ReadChangesRequest) (*openfgav1.ReadChangesResponse, error) {
  1034  	ctx, span := tracer.Start(ctx, "ReadChangesQuery", trace.WithAttributes(
  1035  		attribute.KeyValue{Key: "type", Value: attribute.StringValue(req.GetType())},
  1036  	))
  1037  	defer span.End()
  1038  
  1039  	if !validator.RequestIsValidatedFromContext(ctx) {
  1040  		if err := req.Validate(); err != nil {
  1041  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1042  		}
  1043  	}
  1044  
  1045  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1046  		Service: s.serviceName,
  1047  		Method:  "ReadChanges",
  1048  	})
  1049  
  1050  	q := commands.NewReadChangesQuery(s.datastore,
  1051  		commands.WithReadChangesQueryLogger(s.logger),
  1052  		commands.WithReadChangesQueryEncoder(s.encoder),
  1053  		commands.WithReadChangeQueryHorizonOffset(s.changelogHorizonOffset),
  1054  	)
  1055  	return q.Execute(ctx, req)
  1056  }
  1057  
  1058  func (s *Server) CreateStore(ctx context.Context, req *openfgav1.CreateStoreRequest) (*openfgav1.CreateStoreResponse, error) {
  1059  	ctx, span := tracer.Start(ctx, "CreateStore")
  1060  	defer span.End()
  1061  
  1062  	if !validator.RequestIsValidatedFromContext(ctx) {
  1063  		if err := req.Validate(); err != nil {
  1064  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1065  		}
  1066  	}
  1067  
  1068  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1069  		Service: s.serviceName,
  1070  		Method:  "CreateStore",
  1071  	})
  1072  
  1073  	c := commands.NewCreateStoreCommand(s.datastore, commands.WithCreateStoreCmdLogger(s.logger))
  1074  	res, err := c.Execute(ctx, req)
  1075  	if err != nil {
  1076  		return nil, err
  1077  	}
  1078  
  1079  	s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusCreated))
  1080  
  1081  	return res, nil
  1082  }
  1083  
  1084  func (s *Server) DeleteStore(ctx context.Context, req *openfgav1.DeleteStoreRequest) (*openfgav1.DeleteStoreResponse, error) {
  1085  	ctx, span := tracer.Start(ctx, "DeleteStore")
  1086  	defer span.End()
  1087  
  1088  	if !validator.RequestIsValidatedFromContext(ctx) {
  1089  		if err := req.Validate(); err != nil {
  1090  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1091  		}
  1092  	}
  1093  
  1094  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1095  		Service: s.serviceName,
  1096  		Method:  "DeleteStore",
  1097  	})
  1098  
  1099  	cmd := commands.NewDeleteStoreCommand(s.datastore, commands.WithDeleteStoreCmdLogger(s.logger))
  1100  	res, err := cmd.Execute(ctx, req)
  1101  	if err != nil {
  1102  		return nil, err
  1103  	}
  1104  
  1105  	s.transport.SetHeader(ctx, httpmiddleware.XHttpCode, strconv.Itoa(http.StatusNoContent))
  1106  
  1107  	return res, nil
  1108  }
  1109  
  1110  func (s *Server) GetStore(ctx context.Context, req *openfgav1.GetStoreRequest) (*openfgav1.GetStoreResponse, error) {
  1111  	ctx, span := tracer.Start(ctx, "GetStore")
  1112  	defer span.End()
  1113  
  1114  	if !validator.RequestIsValidatedFromContext(ctx) {
  1115  		if err := req.Validate(); err != nil {
  1116  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1117  		}
  1118  	}
  1119  
  1120  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1121  		Service: s.serviceName,
  1122  		Method:  "GetStore",
  1123  	})
  1124  
  1125  	q := commands.NewGetStoreQuery(s.datastore, commands.WithGetStoreQueryLogger(s.logger))
  1126  	return q.Execute(ctx, req)
  1127  }
  1128  
  1129  func (s *Server) ListStores(ctx context.Context, req *openfgav1.ListStoresRequest) (*openfgav1.ListStoresResponse, error) {
  1130  	ctx, span := tracer.Start(ctx, "ListStores")
  1131  	defer span.End()
  1132  
  1133  	if !validator.RequestIsValidatedFromContext(ctx) {
  1134  		if err := req.Validate(); err != nil {
  1135  			return nil, status.Error(codes.InvalidArgument, err.Error())
  1136  		}
  1137  	}
  1138  
  1139  	ctx = telemetry.ContextWithRPCInfo(ctx, telemetry.RPCInfo{
  1140  		Service: s.serviceName,
  1141  		Method:  "ListStores",
  1142  	})
  1143  
  1144  	q := commands.NewListStoresQuery(s.datastore,
  1145  		commands.WithListStoresQueryLogger(s.logger),
  1146  		commands.WithListStoresQueryEncoder(s.encoder),
  1147  	)
  1148  	return q.Execute(ctx, req)
  1149  }
  1150  
  1151  // IsReady reports whether the datastore is ready. Please see the implementation of [[storage.OpenFGADatastore.IsReady]]
  1152  // for your datastore.
  1153  func (s *Server) IsReady(ctx context.Context) (bool, error) {
  1154  	// for now we only depend on the datastore being ready, but in the future
  1155  	// server readiness may also depend on other criteria in addition to the
  1156  	// datastore being ready.
  1157  
  1158  	status, err := s.datastore.IsReady(ctx)
  1159  	if err != nil {
  1160  		return false, err
  1161  	}
  1162  
  1163  	if status.IsReady {
  1164  		return true, nil
  1165  	}
  1166  
  1167  	s.logger.WarnWithContext(ctx, "datastore is not ready", zap.Any("status", status.Message))
  1168  	return false, nil
  1169  }
  1170  
  1171  // resolveTypesystem resolves the underlying TypeSystem given the storeID and modelID and
  1172  // it sets some response metadata based on the model resolution.
  1173  func (s *Server) resolveTypesystem(ctx context.Context, storeID, modelID string) (*typesystem.TypeSystem, error) {
  1174  	ctx, span := tracer.Start(ctx, "resolveTypesystem")
  1175  	defer span.End()
  1176  
  1177  	typesys, err := s.typesystemResolver(ctx, storeID, modelID)
  1178  	if err != nil {
  1179  		telemetry.TraceError(span, err)
  1180  		if errors.Is(err, typesystem.ErrModelNotFound) {
  1181  			if modelID == "" {
  1182  				return nil, serverErrors.LatestAuthorizationModelNotFound(storeID)
  1183  			}
  1184  
  1185  			return nil, serverErrors.AuthorizationModelNotFound(modelID)
  1186  		}
  1187  
  1188  		if errors.Is(err, typesystem.ErrInvalidModel) {
  1189  			return nil, serverErrors.ValidationError(err)
  1190  		}
  1191  
  1192  		return nil, serverErrors.HandleError("", err)
  1193  	}
  1194  
  1195  	resolvedModelID := typesys.GetAuthorizationModelID()
  1196  
  1197  	span.SetAttributes(attribute.KeyValue{Key: authorizationModelIDKey, Value: attribute.StringValue(resolvedModelID)})
  1198  	grpc_ctxtags.Extract(ctx).Set(authorizationModelIDKey, resolvedModelID)
  1199  	s.transport.SetHeader(ctx, AuthorizationModelIDHeader, resolvedModelID)
  1200  
  1201  	return typesys, nil
  1202  }