Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor gateway api to operate on higher level semantics #176

Merged
merged 70 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
8ba923b
feat: refactor gateway api to operate on higher level semantics
aschmahmann Feb 16, 2023
0ead6cd
fix: respond to comments
aschmahmann Feb 20, 2023
8704fef
next iteration: moved _redirects and index.html back to being the gat…
aschmahmann Feb 26, 2023
0b6c1c5
rename GatewayMetadata -> ContentPathMetadata
aschmahmann Feb 26, 2023
32eeec6
Merge branch 'main' into feat/gateway-refactor
aschmahmann Feb 27, 2023
9e32875
go mod tidy
aschmahmann Feb 27, 2023
7d145b5
make tests mostly pass, includes change to go-unixfs
aschmahmann Feb 28, 2023
ebbe85d
Merge branch 'main' into feat/gateway-refactor
aschmahmann Mar 1, 2023
8413c2b
use shared BlocksGatway
aschmahmann Mar 1, 2023
2b172e7
fixup most errors, expand definition of NotFound
aschmahmann Mar 1, 2023
461fca2
change error reporting to pass a test
aschmahmann Mar 1, 2023
8fca1ae
tmp: point at go-unixfs commit hash
aschmahmann Mar 1, 2023
2bba208
downgrade go-car+otel dependencies and tidy
aschmahmann Mar 1, 2023
2631a5c
fix: bump go-unixfs dep that was missed previously
aschmahmann Mar 1, 2023
6652355
bump go-unixfs to support cid metadata in symlinks
aschmahmann Mar 1, 2023
3f135ce
fix it so we don't cache /ipns/somedir/index.html
aschmahmann Mar 1, 2023
bbdc88f
feat(gateway): allow content-type for UnixFS data to be determined by…
aschmahmann Mar 3, 2023
f8b96e5
refactor(gateway): move public sentinel errors to the same file as th…
aschmahmann Mar 3, 2023
146bbff
docs(gateway): add clarification to ResolveMutable
aschmahmann Mar 3, 2023
707bec5
feat: fix errors and expose ErrServiceUnavailable instead of go-ipfs-…
aschmahmann Mar 3, 2023
382ceaa
fix more errors, assert index.html content type as text/html
aschmahmann Mar 3, 2023
526e9e0
cleanup some todos
aschmahmann Mar 3, 2023
ba19090
refactor dirEntryMetadata name
aschmahmann Mar 3, 2023
80bebc1
explicitly handle an impossible error
aschmahmann Mar 3, 2023
09dc72d
fixup some text
aschmahmann Mar 3, 2023
f4b3232
more 500s and removing todos
aschmahmann Mar 3, 2023
54b7644
docs: improve gateway API docs
aschmahmann Mar 3, 2023
42dfc9d
blocks-gateway: remove extraneous methods
aschmahmann Mar 5, 2023
b4a6bb7
blocks-gateway: return non-UnixFS codecs from Get, except for non-Uni…
aschmahmann Mar 5, 2023
61f233c
fix _redirects caching/mutability
aschmahmann Mar 5, 2023
0caed11
fix gateway car requests
aschmahmann Mar 5, 2023
8ff5626
switch multierr lib
aschmahmann Mar 5, 2023
7275da3
reorder some imports
aschmahmann Mar 5, 2023
7af0fc6
fix: set X-Ipfs-Roots for CARs
aschmahmann Mar 5, 2023
ac23a54
Merge branch 'main' into feat/gateway-refactor
aschmahmann Mar 6, 2023
5efb46f
fix: restore error handling lost in merge commit
aschmahmann Mar 6, 2023
ea590f1
feat: switch Get to be multiple functions and implement range requests
aschmahmann Mar 6, 2023
4a49a38
feat: add ResolvePath to gateway API
aschmahmann Mar 6, 2023
36c78c0
fix error shadowing
aschmahmann Mar 7, 2023
62c6100
chore: cleanup linter errors
aschmahmann Mar 7, 2023
8c1c4d0
change GetCAR return signature to make the linter happy
aschmahmann Mar 7, 2023
14e7ffb
fix: uint64 cannot be < 0
hacdias Mar 7, 2023
17f8d70
chore: go mod tidy
hacdias Mar 7, 2023
e7366ca
apply some suggestions by @lidel
hacdias Mar 9, 2023
cde04e5
Merge branch 'main' into feat/gateway-refactor
hacdias Mar 9, 2023
db8b79e
feat: use rawRecord value for ipns record etag
hacdias Mar 9, 2023
2411c38
refactor: call addUserHeaders and set X-Ipfs-Path earlier
hacdias Mar 9, 2023
e0a3b8b
docs: apply suggestions
hacdias Mar 9, 2023
2615467
docs: clean explanation in isErrNotFound
hacdias Mar 9, 2023
524f4fd
fix: panic on invalid ipns
hacdias Mar 10, 2023
c9bed98
improved errors, renamed variables, remove comments (lidel feedback)
hacdias Mar 10, 2023
2eb8fb6
wrap directory entries for fast listing 🌮
hacdias Mar 13, 2023
ae65e02
feat: add default dns resolvers
hacdias Mar 14, 2023
167a8a8
Merge branch 'main' into feat/gateway-refactor
hacdias Mar 15, 2023
cbf04b3
feat: switch Get to return a union of files.File and a directory list…
aschmahmann Mar 14, 2023
cd0193d
docs: change contract on GetRange to indicate the full file must stil…
aschmahmann Mar 14, 2023
5f7a833
fix: switch _redirects to properly use root path instead of a partial…
aschmahmann Mar 15, 2023
e928893
refactor: remove unused types
hacdias Mar 15, 2023
c3d7b6f
refactor: make unixfs dir handling clearer
hacdias Mar 16, 2023
a05f54c
docs: handleWebRequestErrors
lidel Mar 18, 2023
47d3e19
chore: rename to directoryMetadata
lidel Mar 18, 2023
3de5712
change errors: graph size -> DAG size
aschmahmann Mar 20, 2023
1c6d038
Merge branch 'main' into feat/gateway-refactor
hacdias Mar 21, 2023
784eed6
fix merge
hacdias Mar 21, 2023
1c4716f
fix: ensure basic ipld codecs are registered
hacdias Mar 21, 2023
925f76d
Merge branch 'main' into feat/gateway-refactor
aschmahmann Mar 27, 2023
b5beaa2
more boxo related fixups
aschmahmann Mar 27, 2023
2e4b7c3
more boxo related fixups
aschmahmann Mar 28, 2023
ffa3aad
cleanup variable name in example
aschmahmann Mar 28, 2023
360b031
Merge branch 'main' into feat/gateway-refactor
hacdias Mar 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added examples/gateway/car-file-gateway/libp2pio.car
Copy link
Member

@lidel lidel Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hacdias @aschmahmann afaik this file is not used anywhere, but increases repo size by +14MB. 🙈
Too late to force-push main to fix it, but can we at least remove it in separate PR so there is no confusing examples/gateway/car-file-gateway/ ?

If we need fixtures for tests, we should always put them in testdata/ directory, which has special meaning in go tooling

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lidel urgh, will do! Sorry missed that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary file not shown.
2 changes: 1 addition & 1 deletion examples/gateway/car/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {
}
defer f.Close()

gwAPI, err := common.NewBlocksGateway(blockService, nil)
gwAPI, err := gateway.NewBlocksGateway(blockService)
if err != nil {
log.Fatal(err)
}
Expand Down
3 changes: 2 additions & 1 deletion examples/gateway/car/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/ipfs/boxo/examples/gateway/common"
"github.com/ipfs/boxo/gateway"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/assert"
Expand All @@ -22,7 +23,7 @@ func newTestServer() (*httptest.Server, io.Closer, error) {
return nil, nil, err
}

gateway, err := common.NewBlocksGateway(blockService, nil)
gateway, err := gateway.NewBlocksGateway(blockService)
if err != nil {
_ = f.Close()
return nil, nil, err
Expand Down
231 changes: 1 addition & 230 deletions examples/gateway/common/blocks.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,12 @@
package common

import (
"context"
"errors"
"fmt"
"net/http"
gopath "path"

"github.com/ipfs/boxo/blockservice"
blockstore "github.com/ipfs/boxo/blockstore"
iface "github.com/ipfs/boxo/coreiface"
nsopts "github.com/ipfs/boxo/coreiface/options/namesys"
ifacepath "github.com/ipfs/boxo/coreiface/path"
bsfetcher "github.com/ipfs/boxo/fetcher/impl/blockservice"
"github.com/ipfs/boxo/files"
"github.com/ipfs/boxo/gateway"
"github.com/ipfs/boxo/ipld/merkledag"
"github.com/ipfs/boxo/ipld/unixfs"
ufile "github.com/ipfs/boxo/ipld/unixfs/file"
uio "github.com/ipfs/boxo/ipld/unixfs/io"
"github.com/ipfs/boxo/namesys"
"github.com/ipfs/boxo/namesys/resolve"
ipfspath "github.com/ipfs/boxo/path"
"github.com/ipfs/boxo/path/resolver"
"github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
format "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-unixfsnode"
dagpb "github.com/ipld/go-codec-dagpb"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
mc "github.com/multiformats/go-multicodec"
)

func NewBlocksHandler(gw *BlocksGateway, port int) http.Handler {
func NewBlocksHandler(gw gateway.IPFSBackend, port int) http.Handler {
headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

Expand All @@ -50,202 +20,3 @@ func NewBlocksHandler(gw *BlocksGateway, port int) http.Handler {
mux.Handle("/ipns/", gwHandler)
return mux
}

type BlocksGateway struct {
blockStore blockstore.Blockstore
blockService blockservice.BlockService
dagService format.DAGService
resolver resolver.Resolver

// Optional routing system to handle /ipns addresses.
namesys namesys.NameSystem
routing routing.ValueStore
}

func NewBlocksGateway(blockService blockservice.BlockService, routing routing.ValueStore) (*BlocksGateway, error) {
// Setup the DAG services, which use the CAR block store.
dagService := merkledag.NewDAGService(blockService)

// Setup the UnixFS resolver.
fetcherConfig := bsfetcher.NewFetcherConfig(blockService)
fetcherConfig.PrototypeChooser = dagpb.AddSupportToChooser(func(lnk ipld.Link, lnkCtx ipld.LinkContext) (ipld.NodePrototype, error) {
if tlnkNd, ok := lnkCtx.LinkNode.(schema.TypedLinkNode); ok {
return tlnkNd.LinkTargetNodePrototype(), nil
}
return basicnode.Prototype.Any, nil
})
fetcher := fetcherConfig.WithReifier(unixfsnode.Reify)
resolver := resolver.NewBasicResolver(fetcher)

// Setup a name system so that we are able to resolve /ipns links.
var (
ns namesys.NameSystem
err error
)
if routing != nil {
ns, err = namesys.NewNameSystem(routing)
if err != nil {
return nil, err
}
}

return &BlocksGateway{
blockStore: blockService.Blockstore(),
blockService: blockService,
dagService: dagService,
resolver: resolver,
routing: routing,
namesys: ns,
}, nil
}

func (api *BlocksGateway) GetUnixFsNode(ctx context.Context, p ifacepath.Resolved) (files.Node, error) {
nd, err := api.resolveNode(ctx, p)
if err != nil {
return nil, err
}

return ufile.NewUnixfsFile(ctx, api.dagService, nd)
}

func (api *BlocksGateway) LsUnixFsDir(ctx context.Context, p ifacepath.Resolved) (<-chan iface.DirEntry, error) {
node, err := api.resolveNode(ctx, p)
if err != nil {
return nil, err
}

dir, err := uio.NewDirectoryFromNode(api.dagService, node)
if err != nil {
return nil, err
}

out := make(chan iface.DirEntry, uio.DefaultShardWidth)

go func() {
defer close(out)
for l := range dir.EnumLinksAsync(ctx) {
select {
case out <- api.processLink(ctx, l):
case <-ctx.Done():
return
}
}
}()

return out, nil
}

func (api *BlocksGateway) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
return api.blockService.GetBlock(ctx, c)
}

func (api *BlocksGateway) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, error) {
if api.routing == nil {
return nil, routing.ErrNotSupported
}

// Fails fast if the CID is not an encoded Libp2p Key, avoids wasteful
// round trips to the remote routing provider.
if mc.Code(c.Type()) != mc.Libp2pKey {
return nil, errors.New("provided cid is not an encoded libp2p key")
}

// The value store expects the key itself to be encoded as a multihash.
id, err := peer.FromCid(c)
if err != nil {
return nil, err
}

return api.routing.GetValue(ctx, "/ipns/"+string(id))
}

func (api *BlocksGateway) GetDNSLinkRecord(ctx context.Context, hostname string) (ifacepath.Path, error) {
if api.namesys != nil {
p, err := api.namesys.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1))
if err == namesys.ErrResolveRecursion {
err = nil
}
return ifacepath.New(p.String()), err
}

return nil, errors.New("not implemented")
}

func (api *BlocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
return false
}

has, _ := api.blockStore.Has(ctx, rp.Cid())
return has
}

func (api *BlocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (ifacepath.Resolved, error) {
if _, ok := p.(ifacepath.Resolved); ok {
return p.(ifacepath.Resolved), nil
}

err := p.IsValid()
if err != nil {
return nil, err
}

ipath := ipfspath.Path(p.String())
if ipath.Segments()[0] == "ipns" {
ipath, err = resolve.ResolveIPNS(ctx, api.namesys, ipath)
if err != nil {
return nil, err
}
}

if ipath.Segments()[0] != "ipfs" {
return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
}

node, rest, err := api.resolver.ResolveToLastNode(ctx, ipath)
if err != nil {
return nil, err
}

root, err := cid.Parse(ipath.Segments()[1])
if err != nil {
return nil, err
}

return ifacepath.NewResolvedPath(ipath, node, root, gopath.Join(rest...)), nil
}

func (api *BlocksGateway) resolveNode(ctx context.Context, p ifacepath.Path) (format.Node, error) {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
return nil, err
}

node, err := api.dagService.Get(ctx, rp.Cid())
if err != nil {
return nil, fmt.Errorf("get node: %w", err)
}
return node, nil
}

func (api *BlocksGateway) processLink(ctx context.Context, result unixfs.LinkResult) iface.DirEntry {
if result.Err != nil {
return iface.DirEntry{Err: result.Err}
}

link := iface.DirEntry{
Name: result.Link.Name,
Cid: result.Link.Cid,
}

switch link.Cid.Type() {
case cid.Raw:
link.Type = iface.TFile
link.Size = result.Link.Size
case cid.DagProtobuf:
link.Size = result.Link.Size
}

return link
}
2 changes: 1 addition & 1 deletion examples/gateway/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func main() {
routing := newProxyRouting(*gatewayUrlPtr, nil)

// Creates the gateway with the block service and the routing.
gwAPI, err := common.NewBlocksGateway(blockService, routing)
gwAPI, err := gateway.NewBlocksGateway(blockService, gateway.WithValueStore(routing))
if err != nil {
log.Fatal(err)
}
Expand Down
5 changes: 3 additions & 2 deletions examples/gateway/proxy/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ipfs/boxo/blockservice"
"github.com/ipfs/boxo/examples/gateway/common"
offline "github.com/ipfs/boxo/exchange/offline"
"github.com/ipfs/boxo/gateway"
"github.com/ipfs/go-block-format"
"github.com/stretchr/testify/assert"
)
Expand All @@ -22,12 +23,12 @@ func newProxyGateway(t *testing.T, rs *httptest.Server) *httptest.Server {
blockService := blockservice.New(blockStore, offline.Exchange(blockStore))
routing := newProxyRouting(rs.URL, nil)

gateway, err := common.NewBlocksGateway(blockService, routing)
gw, err := gateway.NewBlocksGateway(blockService, gateway.WithValueStore(routing))
if err != nil {
t.Error(err)
}

handler := common.NewBlocksHandler(gateway, 0)
handler := common.NewBlocksHandler(gw, 0)
ts := httptest.NewServer(handler)
t.Cleanup(ts.Close)

Expand Down
7 changes: 4 additions & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ require (
github.com/ipfs/go-block-format v0.1.2
github.com/ipfs/go-cid v0.4.0
github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ipld-format v0.4.0
github.com/ipfs/go-unixfsnode v1.6.0
github.com/ipld/go-codec-dagpb v1.6.0
github.com/ipld/go-ipld-prime v0.20.0
github.com/libp2p/go-libp2p v0.26.3
github.com/libp2p/go-libp2p-routing-helpers v0.6.0
Expand Down Expand Up @@ -59,12 +56,15 @@ require (
github.com/ipfs/go-ipfs-redirects-file v0.1.1 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-ipld-cbor v0.0.6 // indirect
github.com/ipfs/go-ipld-format v0.4.0 // indirect
github.com/ipfs/go-ipld-legacy v0.1.1 // indirect
github.com/ipfs/go-ipns v0.3.0 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
github.com/ipfs/go-peertaskqueue v0.8.1 // indirect
github.com/ipfs/go-unixfsnode v1.6.0 // indirect
github.com/ipld/go-codec-dagpb v1.6.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
Expand All @@ -73,6 +73,7 @@ require (
github.com/koron/go-ssdp v0.0.3 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-doh-resolver v0.4.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.21.1 // indirect
Expand Down
3 changes: 3 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
github.com/libp2p/go-doh-resolver v0.4.0 h1:gUBa1f1XsPwtpE1du0O+nnZCUqtG7oYi7Bb+0S7FQqw=
github.com/libp2p/go-doh-resolver v0.4.0/go.mod h1:v1/jwsFusgsWIGX/c6vCRrnJ60x7bhTiq/fs2qt0cAg=
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=
github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM=
Expand Down Expand Up @@ -450,6 +452,7 @@ github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU=
github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs=
github.com/multiformats/go-multiaddr-dns v0.3.0/go.mod h1:mNzQ4eTGDg0ll1N9jKPOUogZPoJ30W8a7zk66FQPpdQ=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
Expand Down
13 changes: 12 additions & 1 deletion files/readerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@ type ReaderFile struct {
}

func NewBytesFile(b []byte) File {
return &ReaderFile{"", NewReaderFile(bytes.NewReader(b)), nil, int64(len(b))}
return &ReaderFile{"", bytesReaderCloser{bytes.NewReader(b)}, nil, int64(len(b))}
}

// TODO: Is this the best way to fix this bug?
// The bug is we want to be an io.ReadSeekCloser, but bytes.NewReader only gives a io.ReadSeeker and io.NopCloser
// effectively removes the io.Seeker ability from the passed in io.Reader
type bytesReaderCloser struct {
*bytes.Reader
}

func (b bytesReaderCloser) Close() error {
return nil
Comment on lines 20 to +32
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug and a small fix for it. I might be missing something though about why it was done this way.

}

func NewReaderFile(reader io.Reader) File {
Expand Down
Loading