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 }