Skip to content

Commit

Permalink
feat: add ErrNotFound
Browse files Browse the repository at this point in the history
* Intended to replace github.com/ipfs/go-ipld-format#ErrNotFound
* A new IsNotFound() that uses feature detection rather than type checking so
  it's compatible with old and new forms.

Ref: ipld/go-car#363
Ref: #493
  • Loading branch information
rvagg committed Jun 13, 2023
1 parent f9fb3bd commit 5020b66
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 4 deletions.
9 changes: 5 additions & 4 deletions storage/memstore/memstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package memstore
import (
"bytes"
"context"
"fmt"
"io"

"github.com/ipld/go-ipld-prime/storage"
)

// Store is a simple in-memory storage.
Expand Down Expand Up @@ -56,7 +57,7 @@ func (store *Store) Get(ctx context.Context, key string) ([]byte, error) {
store.beInitialized()
content, exists := store.Bag[key]
if !exists {
return nil, fmt.Errorf("404") // FIXME this needs a standard error type
return nil, storage.ErrNotFound{Key: key}
}
cpy := make([]byte, len(content))
copy(cpy, content)
Expand All @@ -82,7 +83,7 @@ func (store *Store) Put(ctx context.Context, key string, content []byte) error {
func (store *Store) GetStream(ctx context.Context, key string) (io.ReadCloser, error) {
content, exists := store.Bag[key]
if !exists {
return nil, fmt.Errorf("404") // FIXME this needs a standard error type
return nil, storage.ErrNotFound{Key: key}
}
return noopCloser{bytes.NewReader(content)}, nil
}
Expand All @@ -91,7 +92,7 @@ func (store *Store) GetStream(ctx context.Context, key string) (io.ReadCloser, e
func (store *Store) Peek(ctx context.Context, key string) ([]byte, io.Closer, error) {
content, exists := store.Bag[key]
if !exists {
return nil, nil, fmt.Errorf("404") // FIXME this needs a standard error type
return nil, nil, storage.ErrNotFound{Key: key}
}
return content, noopCloser{nil}, nil
}
Expand Down
60 changes: 60 additions & 0 deletions storage/notfound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package storage

import "github.com/ipfs/go-cid"

// compatible with the go-ipld-format ErrNotFound, match against
// interface{NotFound() bool}
// this could go into go-ipld-prime, but for now we'll just exercise the
// feature-test pattern

// ErrNotFound is a 404, but for block storage systems. It is returned when
// a block is not found. The Key is typically the binary form of a CID
// (CID#KeyString()).
//
// ErrNotFound implements `interface{NotFound() bool}`, which makes it roughly
// compatible with the legacy github.com/ipfs/go-ipld-format#ErrNotFound.
// The IsNotFound() function here will test for this and therefore be compatible
// with this ErrNotFound, and the legacy ErrNotFound. The same is not true for
// the legacy github.com/ipfs/go-ipld-format#IsNotFound.
type ErrNotFound struct {
Key string
}

// NewErrNotFound is a convenience factory that creates a new ErrNotFound error
// from a CID.
func NewErrNotFound(c cid.Cid) ErrNotFound {
return ErrNotFound{Key: c.KeyString()}
}

func (e ErrNotFound) Error() string {
if c, err := cid.Cast([]byte(e.Key)); err == nil && c != cid.Undef {
return "ipld: could not find " + c.String()
}
return "ipld: could not find " + e.Key
}

// NotFound always returns true, and is used to feature-test for ErrNotFound
// errors.
func (e ErrNotFound) NotFound() bool {
return true
}

// Is allows errors.Is to work with this error type.
func (e ErrNotFound) Is(err error) bool {
switch err.(type) {
case ErrNotFound:
return true
default:
return false
}
}

// IsNotFound returns true if the error is a ErrNotFound. As it uses a
// feature-test, it is also compatible with the legacy
// github.com/ipfs/go-ipld-format#ErrNotFound.
func IsNotFound(err error) bool {
if nf, ok := err.(interface{ NotFound() bool }); ok {
return nf.NotFound()
}
return false
}
58 changes: 58 additions & 0 deletions storage/notfound_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package storage_test

import (
"testing"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/storage"
)

func TestNotFound(t *testing.T) {
nf := storage.ErrNotFound{Key: "foo"}
if !storage.IsNotFound(nf) {
t.Fatal("expected ErrNotFound to be a NotFound error")
}
if nf.Error() != "ipld: could not find foo" {
t.Fatal("unexpected error message")
}

nf = storage.NewErrNotFound(cid.MustParse("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"))
if !storage.IsNotFound(nf) {
t.Fatal("expected ErrNotFound to be a NotFound error")
}
if nf.Error() != "ipld: could not find bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" {
t.Fatal("unexpected error message")
}

wnf := &weirdNotFoundError{}
if !storage.IsNotFound(wnf) {
t.Fatal("expected weirdNotFoundError to be a NotFound error")
}

// a weirder case, this one implements `NotFound()` but it returns false, so
// this shouldn't be a NotFound error
wnnf := &weirdNotNotFoundError{}
if storage.IsNotFound(wnnf) {
t.Fatal("expected weirdNotNotFoundError to NOT be a NotFound error")
}
}

type weirdNotFoundError struct{}

func (weirdNotFoundError) NotFound() bool {
return true
}

func (weirdNotFoundError) Error() string {
return "weird not found error"
}

type weirdNotNotFoundError struct{}

func (weirdNotNotFoundError) NotFound() bool {
return false
}

func (weirdNotNotFoundError) Error() string {
return "weird not NOT found error"
}

0 comments on commit 5020b66

Please sign in to comment.