blob: 76573801defdfb0986bb68f946ea9cbcf2a75ef9 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package server
import (
"strings"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
wire "v.io/v23/services/syncbase"
pubutil "v.io/v23/syncbase/util"
"v.io/v23/verror"
"v.io/x/ref/services/syncbase/common"
"v.io/x/ref/services/syncbase/server/interfaces"
)
type dispatcher struct {
s *service
}
var _ rpc.Dispatcher = (*dispatcher)(nil)
func NewDispatcher(s *service) *dispatcher {
return &dispatcher{s: s}
}
// We always return an AllowEveryone authorizer from Lookup(), and rely on our
// RPC method implementations to perform proper authorization.
var auth security.Authorizer = security.AllowEveryone()
func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
if len(suffix) == 0 {
return wire.ServiceServer(disp.s), auth, nil
}
// Namespace: <serviceName>/<encDbId>/<encCxId>/<encRowKey>
parts := strings.SplitN(suffix, "/", 3)
// If the first slash-separated component of suffix is SyncbaseSuffix,
// dispatch to the sync module.
if parts[0] == common.SyncbaseSuffix {
return interfaces.SyncServer(disp.s.sync), auth, nil
}
// Validate all name components up front, so that we can avoid doing so in all
// our method implementations.
// Note, the slice returned by strings.SplitN is guaranteed to contain at
// least one element.
dbId, err := pubutil.DecodeId(parts[0])
if err == nil {
err = pubutil.ValidateId(dbId)
}
if err != nil {
return nil, nil, verror.New(wire.ErrInvalidName, ctx, suffix, err)
}
var collectionId wire.Id
if len(parts) > 1 {
collectionId, err = pubutil.DecodeId(parts[1])
if err == nil {
err = pubutil.ValidateId(collectionId)
}
if err != nil {
return nil, nil, verror.New(wire.ErrInvalidName, ctx, suffix, err)
}
}
var rowKey string
if len(parts) > 2 {
rowKey, err = pubutil.Decode(parts[2])
if err == nil {
err = pubutil.ValidateRowKey(rowKey)
}
if err != nil {
return nil, nil, verror.New(wire.ErrInvalidName, ctx, suffix, err)
}
}
var d *database
if dInt, err := disp.s.Database(ctx, nil, dbId); err == nil {
d = dInt.(*database) // panics on failure, as desired
} else {
if verror.ErrorID(err) != verror.ErrNoExist.ID {
// TODO(ivanpi): Panic here? Database() seems to never return other errors.
return nil, nil, err
} else {
// Database does not exist. Create a short-lived database object to
// service this request.
d = disp.s.nonexistentDatabaseHandle(dbId)
}
}
// TODO(sadovsky): Is it possible to determine the RPC method from the
// context? If so, we can check here that the database exists (unless the
// client is calling Database.Create), and avoid the "d.exists" checks in all
// the RPC method implementations. Note, this would also require being able
// to authorize the user to return the appropriate error type (ErrNoExist vs
// ErrNoExistOrNoAccess), possibly by returning a specialized authorizer.
if len(parts) == 1 {
return wire.DatabaseServer(d), auth, nil
}
// Note, it's possible for the database to be deleted concurrently with
// downstream handling of this request. Depending on the order in which things
// execute, the client may not get an error, but in any case ultimately the
// store will end up in a consistent state.
cReq := &collectionReq{
id: collectionId,
d: d,
}
if len(parts) == 2 {
return wire.CollectionServer(cReq), auth, nil
}
rReq := &rowReq{
key: rowKey,
c: cReq,
}
if len(parts) == 3 {
return wire.RowServer(rReq), auth, nil
}
// TODO(ivanpi): Panic here? This should never happen since we do SplitN.
return nil, nil, verror.New(wire.ErrInvalidName, ctx, suffix, "too many name components")
}