Merge "TBR: ref: Replace V23_ROOT with JIRI_ROOT everywhere."
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index 9bcec0d..a2b1c4f 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -9,6 +9,7 @@
"sync"
"v.io/v23/context"
+ "v.io/v23/glob"
"v.io/v23/rpc"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
@@ -41,8 +42,6 @@
////////////////////////////////////////
// RPC methods
-// TODO(sadovsky): Implement Glob__ or GlobChildren__.
-
// TODO(sadovsky): Require the app name to match the client's blessing name.
// I.e. reserve names at the app level of the hierarchy.
func (a *app) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions) error {
@@ -84,17 +83,17 @@
return data.Perms, util.FormatVersion(data.Version), nil
}
-func (a *app) ListDatabases(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+func (a *app) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
if !a.exists {
- return nil, verror.New(verror.ErrNoExist, ctx, a.name)
+ return verror.New(verror.ErrNoExist, ctx, a.name)
}
// Check perms.
sn := a.s.st.NewSnapshot()
if err := util.GetWithAuth(ctx, call, sn, a.stKey(), &appData{}); err != nil {
sn.Abort()
- return nil, err
+ return err
}
- return util.ListChildren(ctx, call, sn, util.JoinKeyParts(util.DbInfoPrefix, a.name))
+ return util.GlobChildren(ctx, call, matcher, sn, sn.Abort, util.JoinKeyParts(util.DbInfoPrefix, a.name))
}
////////////////////////////////////////
diff --git a/services/syncbase/server/dispatcher.go b/services/syncbase/server/dispatcher.go
index 06a6757..a5b9bba 100644
--- a/services/syncbase/server/dispatcher.go
+++ b/services/syncbase/server/dispatcher.go
@@ -33,34 +33,32 @@
var auth security.Authorizer = security.AllowEveryone()
func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+ // TODO(sadovsky): Can we drop this TrimPrefix?
suffix = strings.TrimPrefix(suffix, "/")
-
if len(suffix) == 0 {
return wire.ServiceServer(disp.s), auth, nil
}
+ parts := strings.SplitN(suffix, "/", 2)
// If the first slash-separated component of suffix is SyncbaseSuffix,
// dispatch to the sync module.
- parts := strings.SplitN(suffix, "/", 2)
if parts[0] == util.SyncbaseSuffix {
return interfaces.SyncServer(disp.s.sync), auth, nil
}
- // Otherwise, split on NameSepWithSlashes to get hierarchy component names.
- parts = strings.SplitN(suffix, pubutil.NameSepWithSlashes, 2)
-
- // Validate all key atoms up front, so that we can avoid doing so in all our
- // method implementations.
- appName := parts[0]
- if !pubutil.ValidName(appName) {
+ // Validate all name components up front, so that we can avoid doing so in all
+ // our method implementations.
+ escAppName := parts[0]
+ appName, ok := pubutil.Unescape(escAppName)
+ if !ok || !pubutil.ValidAppName(appName) {
return nil, nil, wire.NewErrInvalidName(ctx, suffix)
}
- aExists := false
+ appExists := false
var a *app
if aInt, err := disp.s.App(nil, nil, appName); err == nil {
a = aInt.(*app) // panics on failure, as desired
- aExists = true
+ appExists = true
} else {
if verror.ErrorID(err) != verror.ErrNoExist.ID {
return nil, nil, err
@@ -78,7 +76,7 @@
// All database, table, and row methods require the app to exist. If it
// doesn't, abort early.
- if !aExists {
+ if !appExists {
return nil, nil, verror.New(verror.ErrNoExist, ctx, a.name)
}
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index f6e14e1..40da19e 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -7,11 +7,11 @@
import (
"math/rand"
"path"
- "strings"
"sync"
"time"
"v.io/v23/context"
+ "v.io/v23/glob"
"v.io/v23/rpc"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase/nosql"
@@ -127,8 +127,6 @@
////////////////////////////////////////
// RPC methods
-// TODO(sadovsky): Implement Glob__ or GlobChildren__.
-
func (d *databaseReq) Create(ctx *context.T, call rpc.ServerCall, metadata *wire.SchemaMetadata, perms access.Permissions) error {
if d.exists {
return verror.New(verror.ErrExist, ctx, d.name)
@@ -195,7 +193,7 @@
}
}
}
- return strings.Join([]string{d.name, util.JoinBatchInfo(batchType, id)}, util.BatchSep), nil
+ return util.BatchSep + util.JoinBatchInfo(batchType, id), nil
}
func (d *databaseReq) Commit(ctx *context.T, call rpc.ServerCall, schemaVersion int32) error {
@@ -318,24 +316,26 @@
return data.Perms, util.FormatVersion(data.Version), nil
}
-func (d *databaseReq) ListTables(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+func (d *databaseReq) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
if !d.exists {
- return nil, verror.New(verror.ErrNoExist, ctx, d.name)
+ return verror.New(verror.ErrNoExist, ctx, d.name)
}
- impl := func(sntx store.SnapshotOrTransaction) ([]string, error) {
+ impl := func(sntx store.SnapshotOrTransaction, closeSntx func() error) error {
// Check perms.
- if err := util.GetWithAuth(ctx, call, sntx, d.stKey(), &databaseData{}); err != nil {
- sntx.Abort()
- return nil, err
+ sn := d.st.NewSnapshot()
+ if err := util.GetWithAuth(ctx, call, sn, d.stKey(), &databaseData{}); err != nil {
+ sn.Abort()
+ return err
}
- return util.ListChildren(ctx, call, sntx, util.TablePrefix)
+ return util.GlobChildren(ctx, call, matcher, sn, closeSntx, util.TablePrefix)
}
if d.batchId != nil {
- return impl(d.batchReader())
+ return impl(d.batchReader(), func() error {
+ return nil
+ })
} else {
- sntx := d.st.NewSnapshot()
- defer sntx.Abort()
- return impl(sntx)
+ sn := d.st.NewSnapshot()
+ return impl(sn, sn.Abort)
}
}
diff --git a/services/syncbase/server/nosql/database_watch.go b/services/syncbase/server/nosql/database_watch.go
index 374cab4..1b8f8f9 100644
--- a/services/syncbase/server/nosql/database_watch.go
+++ b/services/syncbase/server/nosql/database_watch.go
@@ -171,8 +171,8 @@
continue
}
parts := util.SplitKeyParts(opKey)
- // TODO(rogulenko): Currently we process only rows, i.e. keys of the form
- // <RowPrefix>:xxx:yyy. Consider processing other keys.
+ // TODO(rogulenko): Currently we only process rows, i.e. keys of the form
+ // <rowPrefix>:xxx:yyy. Consider processing other keys.
if len(parts) != 3 || parts[0] != util.RowPrefix {
continue
}
@@ -188,7 +188,7 @@
continue
}
change := watch.Change{
- Name: naming.Join(table, pubutil.NameSep, row),
+ Name: naming.Join(table, row),
Continued: true,
}
switch op := logEntry.Op.(type) {
diff --git a/services/syncbase/server/nosql/dispatcher.go b/services/syncbase/server/nosql/dispatcher.go
index 4ec16c0..87ff495 100644
--- a/services/syncbase/server/nosql/dispatcher.go
+++ b/services/syncbase/server/nosql/dispatcher.go
@@ -14,7 +14,6 @@
nosqlWire "v.io/v23/services/syncbase/nosql"
pubutil "v.io/v23/syncbase/util"
"v.io/v23/verror"
- "v.io/x/lib/vlog"
"v.io/x/ref/services/syncbase/server/interfaces"
"v.io/x/ref/services/syncbase/server/util"
)
@@ -33,33 +32,60 @@
// RPC method implementations to perform proper authorization.
var auth security.Authorizer = security.AllowEveryone()
+// Note that our client libraries escape component names (app/db/table names,
+// row keys) embedded in object names, and our server dispatchers immediately
+// unescape them. The only parts of the Syncbase implementation that must be
+// aware of this escaping and unescaping are those parts that deal in object
+// names.
+// This approach confers the following benefits:
+// - the scan and exec implementations need not be aware of key escaping;
+// - row locality corresponds to the lexicographic order of unescaped keys (as
+// clients would expect); and
+// - the object names returned by glob, which are escaped by our GlobChildren
+// implementation, are always valid component names.
+//
+// TODO(sadovsky): Escape app, db, and table names (using an aggressive
+// escaping) when persisting them in underlying storage engines, to prevent
+// shadowing. This is not yet a problem because database and table names are
+// restricted to be valid identifiers, such that neither "<appName>:<dbName>"
+// nor "<tableName>:<rowKey>" can be ambiguous.
func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+ // TODO(sadovsky): Can we drop this TrimPrefix?
suffix = strings.TrimPrefix(suffix, "/")
- parts := strings.Split(suffix, pubutil.NameSepWithSlashes)
+ parts := strings.SplitN(suffix, "/", 3) // db, table, row
- if len(parts) == 0 {
- vlog.Fatal("invalid nosql.dispatcher Lookup")
- }
+ // Note, the slice returned by strings.SplitN is guaranteed to contain at
+ // least one element.
+ dbParts := strings.SplitN(parts[0], util.BatchSep, 2)
+ escDbName := dbParts[0]
- dParts := strings.SplitN(parts[0], util.BatchSep, 2)
- dName := dParts[0]
-
- // Validate all key atoms up front, so that we can avoid doing so in all our
- // method implementations.
- if !pubutil.ValidName(dName) {
+ // Validate all name components up front, so that we can avoid doing so in all
+ // our method implementations.
+ dbName, ok := pubutil.Unescape(escDbName)
+ if !ok || !pubutil.ValidDatabaseName(dbName) {
return nil, nil, wire.NewErrInvalidName(ctx, suffix)
}
- for _, s := range parts[1:] {
- if !pubutil.ValidName(s) {
+
+ var tableName, rowKey string
+ if len(parts) > 1 {
+ tableName, ok = pubutil.Unescape(parts[1])
+ if !ok || !pubutil.ValidTableName(tableName) {
return nil, nil, wire.NewErrInvalidName(ctx, suffix)
}
}
- dExists := false
+ if len(parts) > 2 {
+ rowKey, ok = pubutil.Unescape(parts[2])
+ if !ok || !pubutil.ValidRowKey(rowKey) {
+ return nil, nil, wire.NewErrInvalidName(ctx, suffix)
+ }
+ }
+
+ dbExists := false
var d *database
- if dInt, err := disp.a.NoSQLDatabase(nil, nil, dName); err == nil {
+ if dInt, err := disp.a.NoSQLDatabase(nil, nil, dbName); err == nil {
d = dInt.(*database) // panics on failure, as desired
- dExists = true
+ dbExists = true
} else {
if verror.ErrorID(err) != verror.ErrNoExist.ID {
return nil, nil, err
@@ -67,15 +93,15 @@
// Database does not exist. Create a short-lived database object to
// service this request.
d = &database{
- name: dName,
+ name: dbName,
a: disp.a,
}
}
}
dReq := &databaseReq{database: d}
- if len(dParts) == 2 {
- if !setBatchFields(dReq, dParts[1]) {
+ if len(dbParts) == 2 {
+ if !setBatchFields(dReq, dbParts[1]) {
return nil, nil, wire.NewErrInvalidName(ctx, suffix)
}
}
@@ -85,7 +111,7 @@
// All table and row methods require the database to exist. If it doesn't,
// abort early.
- if !dExists {
+ if !dbExists {
return nil, nil, verror.New(verror.ErrNoExist, ctx, d.name)
}
@@ -94,7 +120,7 @@
// execute, the client may not get an error, but in any case ultimately the
// store will end up in a consistent state.
tReq := &tableReq{
- name: parts[1],
+ name: tableName,
d: dReq,
}
if len(parts) == 2 {
@@ -102,7 +128,7 @@
}
rReq := &rowReq{
- key: parts[2],
+ key: rowKey,
t: tReq,
}
if len(parts) == 3 {
@@ -116,6 +142,8 @@
// value of batchInfo (suffix of the database name component). It returns false
// if batchInfo is malformed.
func setBatchFields(d *databaseReq, batchInfo string) bool {
+ // TODO(sadovsky): Maybe share a common keyspace between sns and txs so that
+ // we can avoid including the batch type in the batchInfo string.
batchType, batchId, err := util.SplitBatchInfo(batchInfo)
if err != nil {
return false
diff --git a/services/syncbase/server/nosql/row.go b/services/syncbase/server/nosql/row.go
index 1efe739..758c58a 100644
--- a/services/syncbase/server/nosql/row.go
+++ b/services/syncbase/server/nosql/row.go
@@ -26,8 +26,6 @@
////////////////////////////////////////
// RPC methods
-// TODO(sadovsky): Implement Glob__ or GlobChildren__.
-
func (r *rowReq) Exists(ctx *context.T, call rpc.ServerCall, schemaVersion int32) (bool, error) {
_, err := r.Get(ctx, call, schemaVersion)
return util.ErrorToExists(err)
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index 9c62d54..7da77fa 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -8,6 +8,7 @@
"strings"
"v.io/v23/context"
+ "v.io/v23/glob"
"v.io/v23/rpc"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase/nosql"
@@ -30,8 +31,6 @@
////////////////////////////////////////
// RPC methods
-// TODO(sadovsky): Implement Glob__ or GlobChildren__.
-
func (t *tableReq) Create(ctx *context.T, call rpc.ServerCall, schemaVersion int32, perms access.Permissions) error {
if t.d.batchId != nil {
return wire.NewErrBoundToBatch(ctx)
@@ -365,6 +364,25 @@
}
}
+func (t *tableReq) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
+ impl := func(sntx store.SnapshotOrTransaction, closeSntx func() error) error {
+ // Check perms.
+ if err := t.checkAccess(ctx, call, sntx, ""); err != nil {
+ closeSntx()
+ return err
+ }
+ return util.GlobChildren(ctx, call, matcher, sntx, closeSntx, util.JoinKeyParts(util.RowPrefix, t.name))
+ }
+ if t.d.batchId != nil {
+ return impl(t.d.batchReader(), func() error {
+ return nil
+ })
+ } else {
+ sn := t.d.st.NewSnapshot()
+ return impl(sn, sn.Abort)
+ }
+}
+
////////////////////////////////////////
// Internal helpers
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index f2ec671..2ddf195 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -13,6 +13,7 @@
"sync"
"v.io/v23/context"
+ "v.io/v23/glob"
"v.io/v23/rpc"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
@@ -135,8 +136,6 @@
////////////////////////////////////////
// RPC methods
-// TODO(sadovsky): Implement Glob__ or GlobChildren__.
-
func (s *service) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
return store.RunInTransaction(s.st, func(tx store.Transaction) error {
data := &serviceData{}
@@ -159,14 +158,14 @@
return data.Perms, util.FormatVersion(data.Version), nil
}
-func (s *service) ListApps(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+func (s *service) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
// Check perms.
sn := s.st.NewSnapshot()
if err := util.GetWithAuth(ctx, call, sn, s.stKey(), &serviceData{}); err != nil {
sn.Abort()
- return nil, err
+ return err
}
- return util.ListChildren(ctx, call, sn, util.AppPrefix)
+ return util.GlobChildren(ctx, call, matcher, sn, sn.Abort, util.AppPrefix)
}
////////////////////////////////////////
diff --git a/services/syncbase/server/util/constants.go b/services/syncbase/server/util/constants.go
index a14d006..5841aa9 100644
--- a/services/syncbase/server/util/constants.go
+++ b/services/syncbase/server/util/constants.go
@@ -9,8 +9,6 @@
)
// Constants related to storage engine keys.
-// Note, these are persisted and therefore must not be modified.
-// Below, they are ordered lexicographically by value.
const (
AppPrefix = "$app"
ClockPrefix = "$clock"
@@ -24,6 +22,8 @@
TablePrefix = "$table"
VersionPrefix = "$version"
+ // Note, these are persisted and therefore must not be modified.
+ // Below, they are ordered lexicographically by value.
// TODO(sadovsky): Changing these prefixes breaks various tests. Tests
// generally shouldn't depend on the values of these constants.
/*
@@ -41,7 +41,8 @@
*/
// Separator for parts of storage engine keys.
- // TODO(sadovsky): Allow ":" in names and use a different separator here.
+ // TODO(sadovsky): Allow ":" in names and use a different separator here,
+ // perhaps "%%".
KeyPartSep = ":"
// PrefixRangeLimitSuffix is a key suffix that indicates the end of a prefix
@@ -53,12 +54,13 @@
const (
// Object name component for Syncbase-to-Syncbase (sync) RPCs.
// Sync object names have the form:
- // <syncbase>/@@sync/...
- SyncbaseSuffix = "@@sync"
+ // <syncbase>/%%sync/...
+ // FIXME: update all apps, again...
+ SyncbaseSuffix = "%%sync"
// Separator for batch info in database names.
// Batch object names have the form:
- // <syncbase>/<app>/$/<database>@@<batchInfo>/...
- BatchSep = "@@"
+ // <syncbase>/<app>/<database>%%<batchInfo>/...
+ BatchSep = "%%"
)
// Constants related to syncbase clock.
diff --git a/services/syncbase/server/util/glob_children.go b/services/syncbase/server/util/glob_children.go
new file mode 100644
index 0000000..e874163
--- /dev/null
+++ b/services/syncbase/server/util/glob_children.go
@@ -0,0 +1,42 @@
+// 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 util
+
+import (
+ "v.io/v23/context"
+ "v.io/v23/glob"
+ "v.io/v23/naming"
+ "v.io/v23/rpc"
+ pubutil "v.io/v23/syncbase/util"
+ "v.io/x/ref/services/syncbase/store"
+)
+
+// Note, Syncbase handles Glob requests by implementing GlobChildren__ at each
+// level (service, app, database, table).
+
+// GlobChildren performs a glob. It calls closeSntx to close sntx.
+func GlobChildren(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element, sntx store.SnapshotOrTransaction, closeSntx func() error, stKeyPrefix string) error {
+ prefix, _ := matcher.FixedPrefix()
+ it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, prefix))
+ defer closeSntx()
+ key := []byte{}
+ for it.Advance() {
+ key = it.Key(key)
+ parts := SplitKeyParts(string(key))
+ name := parts[len(parts)-1]
+ if matcher.Match(name) {
+ // TODO(rogulenko): Check for resolve access. (For table glob, this means
+ // checking prefix perms.)
+ // For explanation of Escape(), see comment in server/nosql/dispatcher.go.
+ if err := call.SendStream().Send(naming.GlobChildrenReplyName{Value: pubutil.Escape(name)}); err != nil {
+ return err
+ }
+ }
+ }
+ if err := it.Err(); err != nil {
+ call.SendStream().Send(naming.GlobChildrenReplyError{Value: naming.GlobError{Error: err}})
+ }
+ return nil
+}
diff --git a/services/syncbase/server/util/key_util.go b/services/syncbase/server/util/key_util.go
index c0d79ad..f00dfef 100644
--- a/services/syncbase/server/util/key_util.go
+++ b/services/syncbase/server/util/key_util.go
@@ -13,13 +13,11 @@
)
// JoinKeyParts builds keys for accessing data in the storage engine.
-// TODO(sadovsky): Allow ":" in names and use a different separator here.
func JoinKeyParts(parts ...string) string {
return strings.Join(parts, KeyPartSep)
}
// SplitKeyParts is the inverse of JoinKeyParts.
-// TODO(sadovsky): Allow ":" in names and use a different separator here.
func SplitKeyParts(key string) []string {
return strings.Split(key, KeyPartSep)
}
diff --git a/services/syncbase/server/util/list_children.go b/services/syncbase/server/util/list_children.go
deleted file mode 100644
index eaa68bd..0000000
--- a/services/syncbase/server/util/list_children.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 util
-
-import (
- "v.io/v23/context"
- "v.io/v23/rpc"
- "v.io/x/ref/services/syncbase/store"
-)
-
-// ListChildren returns the names of all apps, databases, or tables with the
-// given key prefix. Designed for use by Service.ListApps, App.ListDatabases,
-// and Database.ListTables.
-func ListChildren(ctx *context.T, call rpc.ServerCall, sntx store.SnapshotOrTransaction, stKeyPrefix string) ([]string, error) {
- it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, ""))
- key := []byte{}
- res := []string{}
- for it.Advance() {
- key = it.Key(key)
- parts := SplitKeyParts(string(key))
- res = append(res, parts[len(parts)-1])
- }
- if err := it.Err(); err != nil {
- return nil, err
- }
- return res, nil
-}
diff --git a/services/syncbase/testutil/constants.go b/services/syncbase/testutil/constants.go
new file mode 100644
index 0000000..3f65999
--- /dev/null
+++ b/services/syncbase/testutil/constants.go
@@ -0,0 +1,40 @@
+// 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 testutil
+
+var invalidIdentifiers []string = []string{
+ "/",
+ "a/b",
+ "*",
+ "@@",
+ "dev.v.io/a/admin@myapp.com",
+ "안녕하세요",
+}
+
+var OkDbTableNames []string = []string{
+ "a",
+ "B",
+ "a_",
+ "a__",
+ "a0_",
+ "a_b",
+ "a_0",
+ "foobar",
+ "BARBAZ",
+}
+
+var NotOkAppRowNames []string = []string{
+ "",
+ ":",
+ "\x00",
+ "\xff",
+ "a:b",
+ "a\x00b",
+ "a\xffb",
+}
+
+var OkAppRowNames []string = append(OkDbTableNames, invalidIdentifiers...)
+
+var NotOkDbTableNames []string = append(NotOkAppRowNames, invalidIdentifiers...)
diff --git a/services/syncbase/testutil/layer.go b/services/syncbase/testutil/layer.go
index b490885..755aae9 100644
--- a/services/syncbase/testutil/layer.go
+++ b/services/syncbase/testutil/layer.go
@@ -35,7 +35,7 @@
assertExists(t, ctx, self, "self", false)
// TODO(ivanpi): Exists on child when parent does not exist currently fails
// with an error instead of returning false.
- //assertExists(t, ctx, child, "child", false)
+ // assertExists(t, ctx, child, "child", false)
// Create self.
if err := self.Create(ctx, nil); err != nil {
@@ -92,31 +92,29 @@
assertExists(t, ctx, self3, "self3", false)
}
-// Tests that non-ASCII UTF-8 chars are supported at all layers as long as they
-// satisfy util.ValidName.
-// This test only requires layer.Create to be implemented and thus works for
-// rows.
-func TestCreateNameValidation(t *testing.T, ctx *context.T, i interface{}) {
+// Tests that Create() checks name validity.
+func TestCreateNameValidation(t *testing.T, ctx *context.T, i interface{}, okNames, notOkNames []string) {
parent := makeLayer(i)
-
- // Invalid names.
- // TODO(sadovsky): Add names with slashes to this list once we implement
- // client-side name validation. As it stands, some names with slashes result
- // in RPCs against objects at the next layer of hierarchy, and naming.Join
- // drops some leading and trailing slashes before they reach the server-side
- // name-checking code.
- for _, name := range []string{"a\x00", "\x00a", "@@", "a@@", "@@a", "@@a", "$", "a/$", "$/a"} {
- if err := parent.Child(name).Create(ctx, nil); verror.ErrorID(err) != wire.ErrInvalidName.ID {
- t.Fatalf("Create(%q) should have failed: %v", name, err)
- }
- }
-
- // Valid names.
- for _, name := range []string{"a", "aa", "*", "a*", "*a", "a*b", "a/b", "a/$$", "$$/a", "a/$$/b", "dev.v.io/a/admin@myapp.com", "alice/bob", "안녕하세요"} {
+ for _, name := range okNames {
if err := parent.Child(name).Create(ctx, nil); err != nil {
t.Fatalf("Create(%q) failed: %v", name, err)
}
}
+ for _, name := range notOkNames {
+ if err := parent.Child(name).Create(ctx, nil); err == nil {
+ t.Fatalf("Create(%q) should have failed: %v", name, err)
+ // TODO(sadovsky): Currently for name "" we cannot check for
+ // ErrInvalidName, since parent.Child("") dispatches on the parent
+ // component. Create will still fail, but for a different reason. If/when
+ // we add client-side name validation, we'll be able to check for
+ // ErrInvalidName specifically.
+ // TODO(sadovsky): Will dropping the "TrimPrefix" calls in our server-side
+ // dispatchers fix this?
+ if name != "" && verror.ErrorID(err) != wire.ErrInvalidName.ID {
+ t.Fatalf("Create(%q) should have failed with ErrInvalidName: %v", name, err)
+ }
+ }
+ }
}
// TestDestroy tests that object destruction works as expected.
@@ -155,7 +153,7 @@
assertExists(t, ctx, self, "self", false)
// TODO(ivanpi): Exists on child when parent does not exist currently fails
// with an error instead of returning false.
- //assertExists(t, ctx, child, "child", false)
+ // assertExists(t, ctx, child, "child", false)
// self.Create should succeed, since self was destroyed.
if err := self.Create(ctx, nil); err != nil {
@@ -208,11 +206,11 @@
var err error
got, err = self.ListChildren(ctx)
- want = []string(nil)
+ want = []string{}
if err != nil {
t.Fatalf("self.ListChildren() failed: %v", err)
}
- if !reflect.DeepEqual(got, want) {
+ if !reflect.DeepEqual([]string(got), want) {
t.Fatalf("Lists do not match: got %v, want %v", got, want)
}
@@ -239,19 +237,6 @@
if !reflect.DeepEqual(got, want) {
t.Fatalf("Lists do not match: got %v, want %v", got, want)
}
-
- hello := "안녕하세요"
- if err := self.Child(hello).Create(ctx, nil); err != nil {
- t.Fatalf("hello.Create() failed: %v", err)
- }
- got, err = self.ListChildren(ctx)
- want = []string{"x", "y", hello}
- if err != nil {
- t.Fatalf("self.ListChildren() failed: %v", err)
- }
- if !reflect.DeepEqual(got, want) {
- t.Fatalf("Lists do not match: got %v, want %v", got, want)
- }
}
// TestPerms tests that {Set,Get}Permissions work as expected.