github.com/thiagoyeds/go-cloud@v0.26.0/docstore/docstore.go (about)

     1  // Copyright 2019 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package docstore
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"log"
    22  	"reflect"
    23  	"runtime"
    24  	"sort"
    25  	"strings"
    26  	"sync"
    27  	"unicode/utf8"
    28  
    29  	"gocloud.dev/docstore/driver"
    30  	"gocloud.dev/gcerrors"
    31  	"gocloud.dev/internal/gcerr"
    32  	"gocloud.dev/internal/oc"
    33  )
    34  
    35  // A Document is a set of field-value pairs. One or more fields, called the key
    36  // fields, must uniquely identify the document in the collection. You specify the key
    37  // fields when you open a collection.
    38  // A field name must be a valid UTF-8 string that does not contain a '.'.
    39  //
    40  // A Document can be represented as a map[string]int or a pointer to a struct. For
    41  // structs, the exported fields are the document fields.
    42  type Document = interface{}
    43  
    44  // A Collection represents a set of documents. It provides an easy and portable
    45  // way to interact with document stores.
    46  // To create a Collection, use constructors found in driver subpackages.
    47  type Collection struct {
    48  	driver driver.Collection
    49  	tracer *oc.Tracer
    50  	mu     sync.Mutex
    51  	closed bool
    52  }
    53  
    54  const pkgName = "gocloud.dev/docstore"
    55  
    56  var (
    57  	latencyMeasure = oc.LatencyMeasure(pkgName)
    58  
    59  	// OpenCensusViews are predefined views for OpenCensus metrics.
    60  	// The views include counts and latency distributions for API method calls.
    61  	// See the example at https://godoc.org/go.opencensus.io/stats/view for usage.
    62  	OpenCensusViews = oc.Views(pkgName, latencyMeasure)
    63  )
    64  
    65  // NewCollection is intended for use by drivers only. Do not use in application code.
    66  var NewCollection = newCollection
    67  
    68  // newCollection makes a Collection.
    69  func newCollection(d driver.Collection) *Collection {
    70  	c := &Collection{
    71  		driver: d,
    72  		tracer: &oc.Tracer{
    73  			Package:        pkgName,
    74  			Provider:       oc.ProviderName(d),
    75  			LatencyMeasure: latencyMeasure,
    76  		},
    77  	}
    78  	_, file, lineno, ok := runtime.Caller(1)
    79  	runtime.SetFinalizer(c, func(c *Collection) {
    80  		c.mu.Lock()
    81  		closed := c.closed
    82  		c.mu.Unlock()
    83  		if !closed {
    84  			var caller string
    85  			if ok {
    86  				caller = fmt.Sprintf(" (%s:%d)", file, lineno)
    87  			}
    88  			log.Printf("A docstore.Collection was never closed%s", caller)
    89  		}
    90  	})
    91  	return c
    92  }
    93  
    94  // DefaultRevisionField is the default name of the document field used for document revision
    95  // information, to implement optimistic locking.
    96  // See the Revisions section of the package documentation.
    97  const DefaultRevisionField = "DocstoreRevision"
    98  
    99  func (c *Collection) revisionField() string {
   100  	if r := c.driver.RevisionField(); r != "" {
   101  		return r
   102  	}
   103  	return DefaultRevisionField
   104  }
   105  
   106  // A FieldPath is a dot-separated sequence of UTF-8 field names. Examples:
   107  //   room
   108  //   room.size
   109  //   room.size.width
   110  //
   111  // A FieldPath can be used select top-level fields or elements of sub-documents.
   112  // There is no way to select a single list element.
   113  type FieldPath string
   114  
   115  // Actions returns an ActionList that can be used to perform
   116  // actions on the collection's documents.
   117  func (c *Collection) Actions() *ActionList {
   118  	return &ActionList{coll: c}
   119  }
   120  
   121  // An ActionList is a group of actions that affect a single collection.
   122  //
   123  // The writes in an action list (Put, Create, Replace, Update and Delete actions)
   124  // must refer to distinct documents and are unordered with respect to each other.
   125  // Each write happens independently of the others: all actions will be executed, even
   126  // if some fail.
   127  //
   128  // The Gets in an action list must also refer to distinct documents and are unordered
   129  // and independent of each other.
   130  //
   131  // A Get and a write may refer to the same document. Each write may be paired with
   132  // only one Get in this way. The Get and write will be executed in the order
   133  // specified in the list: a Get before a write will see the old value of the
   134  // document; a Get after the write will see the new value if the service is strongly
   135  // consistent, but may see the old value if the service is eventually consistent.
   136  type ActionList struct {
   137  	coll     *Collection
   138  	actions  []*Action
   139  	beforeDo func(asFunc func(interface{}) bool) error
   140  }
   141  
   142  // An Action is a read or write on a single document.
   143  // Use the methods of ActionList to create and execute Actions.
   144  type Action struct {
   145  	kind       driver.ActionKind
   146  	doc        Document
   147  	fieldpaths []FieldPath // paths to retrieve, for Get
   148  	mods       Mods        // modifications to make, for Update
   149  }
   150  
   151  func (l *ActionList) add(a *Action) *ActionList {
   152  	l.actions = append(l.actions, a)
   153  	return l
   154  }
   155  
   156  // Create adds an action that creates a new document to the given ActionList, and
   157  // returns the ActionList. The document must not already exist; an error with code
   158  // AlreadyExists is returned if it does. (See gocloud.dev/gcerrors for more on error
   159  // codes.)
   160  //
   161  // If the document doesn't have key fields, or the key fields are empty, meaning
   162  // 0, a nil interface value, or any empty array or string, key fields with
   163  // unique values will be created and doc will be populated with them if there is
   164  // a way to assign those keys, see each driver for details on the requirement of
   165  // generating keys.
   166  //
   167  // The revision field of the document must be absent or nil.
   168  //
   169  // Except for setting the revision field and possibly setting the key fields, the doc
   170  // argument is not modified.
   171  func (l *ActionList) Create(doc Document) *ActionList {
   172  	return l.add(&Action{kind: driver.Create, doc: doc})
   173  }
   174  
   175  // Replace adds an action that replaces a document to the given ActionList, and
   176  // returns the ActionList. The key fields of the doc argument must be set. The
   177  // document must already exist; an error with code NotFound is returned if it does
   178  // not (or possibly FailedPrecondition, if the doc argument has a non-nil revision).
   179  // (See gocloud.dev/gcerrors for more on error codes.)
   180  //
   181  // See the Revisions section of the package documentation for how revisions are
   182  // handled.
   183  func (l *ActionList) Replace(doc Document) *ActionList {
   184  	return l.add(&Action{kind: driver.Replace, doc: doc})
   185  }
   186  
   187  // Put adds an action that adds or replaces a document to the given ActionList, and returns the ActionList.
   188  // The key fields must be set.
   189  //
   190  // If the revision field is non-nil, then Put behaves exactly like Replace, returning
   191  // an error if the document does not exist. Otherwise, Put will create the document
   192  // if it does not exist.
   193  //
   194  // See the Revisions section of the package documentation for how revisions are
   195  // handled.
   196  func (l *ActionList) Put(doc Document) *ActionList {
   197  	return l.add(&Action{kind: driver.Put, doc: doc})
   198  }
   199  
   200  // Delete adds an action that deletes a document to the given ActionList, and returns
   201  // the ActionList. Only the key and revision fields of doc are used.
   202  // See the Revisions section of the package documentation for how revisions are
   203  // handled.
   204  // If doc has no revision and the document doesn't exist, nothing happens and no
   205  // error is returned.
   206  func (l *ActionList) Delete(doc Document) *ActionList {
   207  	// Rationale for not returning an error if the document does not exist:
   208  	// Returning an error might be informative and could be ignored, but if the
   209  	// semantics of an action list are to stop at first error, then we might abort a
   210  	// list of Deletes just because one of the docs was not present, and that seems
   211  	// wrong, or at least something you'd want to turn off.
   212  	return l.add(&Action{kind: driver.Delete, doc: doc})
   213  }
   214  
   215  // Get adds an action that retrieves a document to the given ActionList, and
   216  // returns the ActionList.
   217  // Only the key fields of doc are used.
   218  // If fps is omitted, doc will contain all the fields of the retrieved document.
   219  // If fps is present, only the given field paths are retrieved. It is undefined
   220  // whether other fields of doc at the time of the call are removed, unchanged,
   221  // or zeroed, so for portable behavior doc should contain only the key fields.
   222  // If you plan to write the document back and let Docstore to perform optimistic
   223  // locking, include the revision field in fps. See more about revision at
   224  // https://godoc.org/gocloud.dev/docstore#hdr-Revisions.
   225  func (l *ActionList) Get(doc Document, fps ...FieldPath) *ActionList {
   226  	return l.add(&Action{
   227  		kind:       driver.Get,
   228  		doc:        doc,
   229  		fieldpaths: fps,
   230  	})
   231  }
   232  
   233  // Update atomically applies Mods to doc, which must exist.
   234  // Only the key and revision fields of doc are used.
   235  // It is an error to pass an empty Mods to Update.
   236  //
   237  // A modification will create a field if it doesn't exist.
   238  //
   239  // No field path in mods can be a prefix of another. (It makes no sense
   240  // to, say, set foo but increment foo.bar.)
   241  //
   242  // See the Revisions section of the package documentation for how revisions are
   243  // handled.
   244  //
   245  // It is undefined whether updating a sub-field of a non-map field will succeed.
   246  // For instance, if the current document is {a: 1} and Update is called with the
   247  // mod "a.b": 2, then either Update will fail, or it will succeed with the result
   248  // {a: {b: 2}}.
   249  //
   250  // Update does not modify its doc argument, except to set the new revision. To obtain
   251  // the updated document, call Get after calling Update.
   252  func (l *ActionList) Update(doc Document, mods Mods) *ActionList {
   253  	return l.add(&Action{
   254  		kind: driver.Update,
   255  		doc:  doc,
   256  		mods: mods,
   257  	})
   258  }
   259  
   260  // Mods is a map from field paths to modifications.
   261  // At present, a modification is one of:
   262  //  - nil, to delete the field
   263  //  - an Increment value, to add a number to the field
   264  //  - any other value, to set the field to that value
   265  // See ActionList.Update.
   266  type Mods map[FieldPath]interface{}
   267  
   268  // Increment returns a modification that results in a field being incremented. It
   269  // should only be used as a value in a Mods map, like so:
   270  //
   271  //    docstore.Mods{"count": docstore.Increment(1)}
   272  //
   273  // The amount must be an integer or floating-point value.
   274  func Increment(amount interface{}) interface{} {
   275  	return driver.IncOp{amount}
   276  }
   277  
   278  // An ActionListError is returned by ActionList.Do. It contains all the errors
   279  // encountered while executing the ActionList, and the positions of the corresponding
   280  // actions.
   281  type ActionListError []struct {
   282  	Index int
   283  	Err   error
   284  }
   285  
   286  // TODO(jba): use xerrors formatting.
   287  
   288  func (e ActionListError) Error() string {
   289  	var s []string
   290  	for _, x := range e {
   291  		s = append(s, fmt.Sprintf("at %d: %v", x.Index, x.Err))
   292  	}
   293  	return strings.Join(s, "; ")
   294  }
   295  
   296  // Unwrap returns the error in e, if there is exactly one. If there is more than one
   297  // error, Unwrap returns nil, since there is no way to determine which should be
   298  // returned.
   299  func (e ActionListError) Unwrap() error {
   300  	if len(e) == 1 {
   301  		return e[0].Err
   302  	}
   303  	// Return nil when e is nil, or has more than one error.
   304  	// When there are multiple errors, it doesn't make sense to return any of them.
   305  	return nil
   306  }
   307  
   308  // BeforeDo takes a callback function that will be called before the ActionList is
   309  // executed by the underlying service. It may be invoked multiple times for a single
   310  // call to ActionList.Do, because the driver may split the action list into several
   311  // service calls. If any callback invocation returns an error, ActionList.Do returns
   312  // an error.
   313  //
   314  // The callback takes a parameter, asFunc, that converts its argument to
   315  // driver-specific types. See https://gocloud.dev/concepts/as for background
   316  // information.
   317  func (l *ActionList) BeforeDo(f func(asFunc func(interface{}) bool) error) *ActionList {
   318  	l.beforeDo = f
   319  	return l
   320  }
   321  
   322  // Do executes the action list.
   323  //
   324  // If Do returns a non-nil error, it will be of type ActionListError. If any action
   325  // fails, the returned error will contain the position in the ActionList of each
   326  // failed action.
   327  //
   328  // All the actions will be executed. Docstore tries to execute the actions as
   329  // efficiently as possible. Sometimes this makes it impossible to attribute failures
   330  // to specific actions; in such cases, the returned ActionListError will have entries
   331  // whose Index field is negative.
   332  func (l *ActionList) Do(ctx context.Context) error {
   333  	return l.do(ctx, true)
   334  }
   335  
   336  // do implements Do with optional OpenCensus tracing, so it can be used internally.
   337  func (l *ActionList) do(ctx context.Context, oc bool) (err error) {
   338  	if err := l.coll.checkClosed(); err != nil {
   339  		return ActionListError{{-1, errClosed}}
   340  	}
   341  
   342  	if oc {
   343  		ctx = l.coll.tracer.Start(ctx, "ActionList.Do")
   344  		defer func() { l.coll.tracer.End(ctx, err) }()
   345  	}
   346  
   347  	das, err := l.toDriverActions()
   348  	if err != nil {
   349  		return err
   350  	}
   351  	dopts := &driver.RunActionsOptions{BeforeDo: l.beforeDo}
   352  	alerr := ActionListError(l.coll.driver.RunActions(ctx, das, dopts))
   353  	if len(alerr) == 0 {
   354  		return nil // Explicitly return nil, because alerr is not of type error.
   355  	}
   356  	for i := range alerr {
   357  		alerr[i].Err = wrapError(l.coll.driver, alerr[i].Err)
   358  	}
   359  	return alerr
   360  }
   361  
   362  func (l *ActionList) toDriverActions() ([]*driver.Action, error) {
   363  	var das []*driver.Action
   364  	var alerr ActionListError
   365  	// Create a set of (document key, is Get action) pairs for detecting duplicates:
   366  	// an action list can have at most one get and at most one write for each key.
   367  	type keyAndKind struct {
   368  		key   interface{}
   369  		isGet bool
   370  	}
   371  	seen := map[keyAndKind]bool{}
   372  	for i, a := range l.actions {
   373  		d, err := l.coll.toDriverAction(a)
   374  		// Check for duplicate key.
   375  		if err == nil && d.Key != nil {
   376  			kk := keyAndKind{d.Key, d.Kind == driver.Get}
   377  			if seen[kk] {
   378  				err = gcerr.Newf(gcerr.InvalidArgument, nil, "duplicate key in action list: %v", d.Key)
   379  			} else {
   380  				seen[kk] = true
   381  			}
   382  		}
   383  		if err != nil {
   384  			alerr = append(alerr, struct {
   385  				Index int
   386  				Err   error
   387  			}{i, wrapError(l.coll.driver, err)})
   388  		} else {
   389  			d.Index = i
   390  			das = append(das, d)
   391  		}
   392  	}
   393  	if len(alerr) > 0 {
   394  		return nil, alerr
   395  	}
   396  	return das, nil
   397  }
   398  
   399  func (c *Collection) toDriverAction(a *Action) (*driver.Action, error) {
   400  	ddoc, err := driver.NewDocument(a.doc)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	key, err := c.driver.Key(ddoc)
   405  	if err != nil {
   406  		if gcerrors.Code(err) != gcerr.InvalidArgument {
   407  			err = gcerr.Newf(gcerr.InvalidArgument, err, "bad document key")
   408  		}
   409  		return nil, err
   410  	}
   411  	if key == nil || driver.IsEmptyValue(reflect.ValueOf(key)) {
   412  		if a.kind != driver.Create {
   413  			return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "missing document key")
   414  		}
   415  		// set the key to nil so that the following code does not need to check for
   416  		// empty.
   417  		key = nil
   418  	}
   419  	if reflect.ValueOf(key).Kind() == reflect.Ptr {
   420  		return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "keys cannot be pointers")
   421  	}
   422  	rev, _ := ddoc.GetField(c.revisionField())
   423  	if a.kind == driver.Create && rev != nil {
   424  		return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "cannot create a document with a revision field")
   425  	}
   426  	kind := a.kind
   427  	if kind == driver.Put && rev != nil {
   428  		// A Put with a revision field is equivalent to a Replace.
   429  		kind = driver.Replace
   430  	}
   431  	d := &driver.Action{Kind: kind, Doc: ddoc, Key: key}
   432  	if a.fieldpaths != nil {
   433  		d.FieldPaths, err = parseFieldPaths(a.fieldpaths)
   434  		if err != nil {
   435  			return nil, err
   436  		}
   437  	}
   438  	if a.kind == driver.Update {
   439  		d.Mods, err = toDriverMods(a.mods)
   440  		if err != nil {
   441  			return nil, err
   442  		}
   443  	}
   444  	return d, nil
   445  }
   446  
   447  func parseFieldPaths(fps []FieldPath) ([][]string, error) {
   448  	res := make([][]string, len(fps))
   449  	for i, s := range fps {
   450  		fp, err := parseFieldPath(s)
   451  		if err != nil {
   452  			return nil, err
   453  		}
   454  		res[i] = fp
   455  	}
   456  	return res, nil
   457  }
   458  
   459  func toDriverMods(mods Mods) ([]driver.Mod, error) {
   460  	// Convert mods from a map to a slice of (fieldPath, value) pairs.
   461  	// The map is easier for users to write, but the slice is easier
   462  	// to process.
   463  	if len(mods) == 0 {
   464  		return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "no mods passed to Update")
   465  	}
   466  
   467  	// Sort keys so tests are deterministic.
   468  	// After sorting, a key might not immediately follow its prefix. Consider the
   469  	// sorted list of keys "a", "a+b", "a.b". "a" is prefix of "a.b", but since '+'
   470  	// sorts before '.', it is not adjacent to it. All we can assume is that the
   471  	// prefix is before the key.
   472  	var keys []string
   473  	for k := range mods {
   474  		keys = append(keys, string(k))
   475  	}
   476  	sort.Strings(keys)
   477  
   478  	var dmods []driver.Mod
   479  	for _, k := range keys {
   480  		k := FieldPath(k)
   481  		v := mods[k]
   482  		fp, err := parseFieldPath(k)
   483  		if err != nil {
   484  			return nil, err
   485  		}
   486  		for _, d := range dmods {
   487  			if fpHasPrefix(fp, d.FieldPath) {
   488  				return nil, gcerr.Newf(gcerr.InvalidArgument, nil,
   489  					"field path %q is a prefix of %q", strings.Join(d.FieldPath, "."), k)
   490  			}
   491  		}
   492  		if inc, ok := v.(driver.IncOp); ok && !isIncNumber(inc.Amount) {
   493  			return nil, gcerr.Newf(gcerr.InvalidArgument, nil,
   494  				"Increment amount %v of type %[1]T must be an integer or floating-point number", inc.Amount)
   495  		}
   496  		dmods = append(dmods, driver.Mod{FieldPath: fp, Value: v})
   497  	}
   498  	return dmods, nil
   499  }
   500  
   501  // fpHasPrefix reports whether the field path fp begins with prefix.
   502  func fpHasPrefix(fp, prefix []string) bool {
   503  	if len(fp) < len(prefix) {
   504  		return false
   505  	}
   506  	for i, p := range prefix {
   507  		if fp[i] != p {
   508  			return false
   509  		}
   510  	}
   511  	return true
   512  }
   513  
   514  func isIncNumber(x interface{}) bool {
   515  	switch reflect.TypeOf(x).Kind() {
   516  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   517  		return true
   518  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   519  		return true
   520  	case reflect.Float32, reflect.Float64:
   521  		return true
   522  	default:
   523  		return false
   524  	}
   525  }
   526  
   527  func (l *ActionList) String() string {
   528  	var as []string
   529  	for _, a := range l.actions {
   530  		as = append(as, a.String())
   531  	}
   532  	return "[" + strings.Join(as, ", ") + "]"
   533  }
   534  
   535  func (a *Action) String() string {
   536  	buf := &strings.Builder{}
   537  	fmt.Fprintf(buf, "%s(%v", a.kind, a.doc)
   538  	for _, fp := range a.fieldpaths {
   539  		fmt.Fprintf(buf, ", %s", fp)
   540  	}
   541  	for _, m := range a.mods {
   542  		fmt.Fprintf(buf, ", %v", m)
   543  	}
   544  	fmt.Fprint(buf, ")")
   545  	return buf.String()
   546  }
   547  
   548  // Create is a convenience for building and running a single-element action list.
   549  // See ActionList.Create.
   550  func (c *Collection) Create(ctx context.Context, doc Document) error {
   551  	if err := c.Actions().Create(doc).Do(ctx); err != nil {
   552  		return err.(ActionListError).Unwrap()
   553  	}
   554  	return nil
   555  }
   556  
   557  // Replace is a convenience for building and running a single-element action list.
   558  // See ActionList.Replace.
   559  func (c *Collection) Replace(ctx context.Context, doc Document) error {
   560  	if err := c.Actions().Replace(doc).Do(ctx); err != nil {
   561  		return err.(ActionListError).Unwrap()
   562  	}
   563  	return nil
   564  }
   565  
   566  // Put is a convenience for building and running a single-element action list.
   567  // See ActionList.Put.
   568  func (c *Collection) Put(ctx context.Context, doc Document) error {
   569  	if err := c.Actions().Put(doc).Do(ctx); err != nil {
   570  		return err.(ActionListError).Unwrap()
   571  	}
   572  	return nil
   573  }
   574  
   575  // Delete is a convenience for building and running a single-element action list.
   576  // See ActionList.Delete.
   577  func (c *Collection) Delete(ctx context.Context, doc Document) error {
   578  	if err := c.Actions().Delete(doc).Do(ctx); err != nil {
   579  		return err.(ActionListError).Unwrap()
   580  	}
   581  	return nil
   582  }
   583  
   584  // Get is a convenience for building and running a single-element action list.
   585  // See ActionList.Get.
   586  func (c *Collection) Get(ctx context.Context, doc Document, fps ...FieldPath) error {
   587  	if err := c.Actions().Get(doc, fps...).Do(ctx); err != nil {
   588  		return err.(ActionListError).Unwrap()
   589  	}
   590  	return nil
   591  }
   592  
   593  // Update is a convenience for building and running a single-element action list.
   594  // See ActionList.Update.
   595  func (c *Collection) Update(ctx context.Context, doc Document, mods Mods) error {
   596  	if err := c.Actions().Update(doc, mods).Do(ctx); err != nil {
   597  		return err.(ActionListError).Unwrap()
   598  	}
   599  	return nil
   600  }
   601  
   602  func parseFieldPath(fp FieldPath) ([]string, error) {
   603  	if len(fp) == 0 {
   604  		return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "empty field path")
   605  	}
   606  	if !utf8.ValidString(string(fp)) {
   607  		return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "invalid UTF-8 field path %q", fp)
   608  	}
   609  	parts := strings.Split(string(fp), ".")
   610  	for _, p := range parts {
   611  		if p == "" {
   612  			return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "empty component in field path %q", fp)
   613  		}
   614  	}
   615  	return parts, nil
   616  }
   617  
   618  // RevisionToString converts a document revision to a string. The returned
   619  // string should be treated as opaque; its only use is to provide a serialized
   620  // form that can be passed around (e.g., as a hidden field on a web form)
   621  // and then turned back into a revision using StringToRevision. The string is safe
   622  // for use in URLs and HTTP forms.
   623  func (c *Collection) RevisionToString(rev interface{}) (string, error) {
   624  	if rev == nil {
   625  		return "", gcerr.Newf(gcerr.InvalidArgument, nil, "RevisionToString: nil revision")
   626  	}
   627  	bytes, err := c.driver.RevisionToBytes(rev)
   628  	if err != nil {
   629  		return "", wrapError(c.driver, err)
   630  	}
   631  	return base64.RawURLEncoding.EncodeToString(bytes), nil
   632  }
   633  
   634  // StringToRevision converts a string obtained with RevisionToString
   635  // to a revision.
   636  func (c *Collection) StringToRevision(s string) (interface{}, error) {
   637  	if s == "" {
   638  		return "", gcerr.Newf(gcerr.InvalidArgument, nil, "StringToRevision: empty string")
   639  	}
   640  	bytes, err := base64.RawURLEncoding.DecodeString(s)
   641  	if err != nil {
   642  		return nil, err
   643  	}
   644  	rev, err := c.driver.BytesToRevision(bytes)
   645  	if err != nil {
   646  		return "", wrapError(c.driver, err)
   647  	}
   648  	return rev, nil
   649  }
   650  
   651  // As converts i to driver-specific types.
   652  // See https://gocloud.dev/concepts/as/ for background information, the "As"
   653  // examples in this package for examples, and the driver package
   654  // documentation for the specific types supported for that driver.
   655  func (c *Collection) As(i interface{}) bool {
   656  	if i == nil {
   657  		return false
   658  	}
   659  	return c.driver.As(i)
   660  }
   661  
   662  var errClosed = gcerr.Newf(gcerr.FailedPrecondition, nil, "docstore: Collection has been closed")
   663  
   664  // Close releases any resources used for the collection.
   665  func (c *Collection) Close() error {
   666  	c.mu.Lock()
   667  	prev := c.closed
   668  	c.closed = true
   669  	c.mu.Unlock()
   670  	if prev {
   671  		return errClosed
   672  	}
   673  	return wrapError(c.driver, c.driver.Close())
   674  }
   675  
   676  func (c *Collection) checkClosed() error {
   677  	c.mu.Lock()
   678  	defer c.mu.Unlock()
   679  	if c.closed {
   680  		return errClosed
   681  	}
   682  	return nil
   683  }
   684  
   685  func wrapError(c driver.Collection, err error) error {
   686  	if err == nil {
   687  		return nil
   688  	}
   689  	if gcerr.DoNotWrap(err) {
   690  		return err
   691  	}
   692  	if _, ok := err.(*gcerr.Error); ok {
   693  		return err
   694  	}
   695  	return gcerr.New(c.ErrorCode(err), err, 2, "docstore")
   696  }
   697  
   698  // ErrorAs converts i to driver-specific types. See
   699  // https://gocloud.dev/concepts/as/ for background information and the
   700  // driver package documentation for the specific types supported for
   701  // that driver.
   702  //
   703  // When the error is an ActionListError, ErrorAs works on individual errors in
   704  // the slice, not the slice itself.
   705  //
   706  // ErrorAs panics if i is nil or not a pointer.
   707  // ErrorAs returns false if err == nil.
   708  func (c *Collection) ErrorAs(err error, i interface{}) bool {
   709  	return gcerr.ErrorAs(err, i, c.driver.ErrorAs)
   710  }