mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
feat: Über Migration (and Boxo rename)
Include rename from: github.com/ipfs/go-libipfs => github.com/ipfs/boxo This migration was reverted: ./blocks => github.com/ipfs/go-block-format Migrated repos: - github.com/ipfs/interface-go-ipfs-core => ./coreiface - github.com/ipfs/go-pinning-service-http-client => ./pinning/remote/client - github.com/ipfs/go-path => ./path - github.com/ipfs/go-namesys => ./namesys - github.com/ipfs/go-mfs => ./mfs - github.com/ipfs/go-ipfs-provider => ./provider - github.com/ipfs/go-ipfs-pinner => ./pinning/pinner - github.com/ipfs/go-ipfs-keystore => ./keystore - github.com/ipfs/go-filestore => ./filestore - github.com/ipfs/go-ipns => ./ipns - github.com/ipfs/go-blockservice => ./blockservice - github.com/ipfs/go-ipfs-chunker => ./chunker - github.com/ipfs/go-fetcher => ./fetcher - github.com/ipfs/go-ipfs-blockstore => ./blockstore - github.com/ipfs/go-ipfs-posinfo => ./filestore/posinfo - github.com/ipfs/go-ipfs-util => ./util - github.com/ipfs/go-ipfs-ds-help => ./datastore/dshelp - github.com/ipfs/go-verifcid => ./verifcid - github.com/ipfs/go-ipfs-exchange-offline => ./exchange/offline - github.com/ipfs/go-ipfs-routing => ./routing - github.com/ipfs/go-ipfs-exchange-interface => ./exchange - github.com/ipfs/go-unixfs => ./ipld/unixfs - github.com/ipfs/go-merkledag => ./ipld/merkledag - github.com/ipld/go-car => ./ipld/car Fixes #215 Updates #202 This commit was moved from ipfs/boxo@038bdd291d
This commit is contained in:
commit
8c2ae9cf22
38
core/coreiface/block.go
Normal file
38
core/coreiface/block.go
Normal file
@ -0,0 +1,38 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
// BlockStat contains information about a block
|
||||
type BlockStat interface {
|
||||
// Size is the size of a block
|
||||
Size() int
|
||||
|
||||
// Path returns path to the block
|
||||
Path() path.Resolved
|
||||
}
|
||||
|
||||
// BlockAPI specifies the interface to the block layer
|
||||
type BlockAPI interface {
|
||||
// Put imports raw block data, hashing it using specified settings.
|
||||
Put(context.Context, io.Reader, ...options.BlockPutOption) (BlockStat, error)
|
||||
|
||||
// Get attempts to resolve the path and return a reader for data in the block
|
||||
Get(context.Context, path.Path) (io.Reader, error)
|
||||
|
||||
// Rm removes the block specified by the path from local blockstore.
|
||||
// By default an error will be returned if the block can't be found locally.
|
||||
//
|
||||
// NOTE: If the specified block is pinned it won't be removed and no error
|
||||
// will be returned
|
||||
Rm(context.Context, path.Path, ...options.BlockRmOption) error
|
||||
|
||||
// Stat returns information on
|
||||
Stat(context.Context, path.Path) (BlockStat, error)
|
||||
}
|
||||
60
core/coreiface/coreapi.go
Normal file
60
core/coreiface/coreapi.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Package iface defines IPFS Core API which is a set of interfaces used to
|
||||
// interact with IPFS nodes.
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
// CoreAPI defines an unified interface to IPFS for Go programs
|
||||
type CoreAPI interface {
|
||||
// Unixfs returns an implementation of Unixfs API
|
||||
Unixfs() UnixfsAPI
|
||||
|
||||
// Block returns an implementation of Block API
|
||||
Block() BlockAPI
|
||||
|
||||
// Dag returns an implementation of Dag API
|
||||
Dag() APIDagService
|
||||
|
||||
// Name returns an implementation of Name API
|
||||
Name() NameAPI
|
||||
|
||||
// Key returns an implementation of Key API
|
||||
Key() KeyAPI
|
||||
|
||||
// Pin returns an implementation of Pin API
|
||||
Pin() PinAPI
|
||||
|
||||
// Object returns an implementation of Object API
|
||||
Object() ObjectAPI
|
||||
|
||||
// Dht returns an implementation of Dht API
|
||||
Dht() DhtAPI
|
||||
|
||||
// Swarm returns an implementation of Swarm API
|
||||
Swarm() SwarmAPI
|
||||
|
||||
// PubSub returns an implementation of PubSub API
|
||||
PubSub() PubSubAPI
|
||||
|
||||
// Routing returns an implementation of Routing API
|
||||
Routing() RoutingAPI
|
||||
|
||||
// ResolvePath resolves the path using Unixfs resolver
|
||||
ResolvePath(context.Context, path.Path) (path.Resolved, error)
|
||||
|
||||
// ResolveNode resolves the path (if not resolved already) using Unixfs
|
||||
// resolver, gets and returns the resolved Node
|
||||
ResolveNode(context.Context, path.Path) (ipld.Node, error)
|
||||
|
||||
// WithOptions creates new instance of CoreAPI based on this instance with
|
||||
// a set of options applied
|
||||
WithOptions(...options.ApiOption) (CoreAPI, error)
|
||||
}
|
||||
13
core/coreiface/dag.go
Normal file
13
core/coreiface/dag.go
Normal file
@ -0,0 +1,13 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
// APIDagService extends ipld.DAGService
|
||||
type APIDagService interface {
|
||||
ipld.DAGService
|
||||
|
||||
// Pinning returns special NodeAdder which recursively pins added nodes
|
||||
Pinning() ipld.NodeAdder
|
||||
}
|
||||
27
core/coreiface/dht.go
Normal file
27
core/coreiface/dht.go
Normal file
@ -0,0 +1,27 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
// DhtAPI specifies the interface to the DHT
|
||||
// Note: This API will likely get deprecated in near future, see
|
||||
// https://github.com/ipfs/interface-ipfs-core/issues/249 for more context.
|
||||
type DhtAPI interface {
|
||||
// FindPeer queries the DHT for all of the multiaddresses associated with a
|
||||
// Peer ID
|
||||
FindPeer(context.Context, peer.ID) (peer.AddrInfo, error)
|
||||
|
||||
// FindProviders finds peers in the DHT who can provide a specific value
|
||||
// given a key.
|
||||
FindProviders(context.Context, path.Path, ...options.DhtFindProvidersOption) (<-chan peer.AddrInfo, error)
|
||||
|
||||
// Provide announces to the network that you are providing given values
|
||||
Provide(context.Context, path.Path, ...options.DhtProvideOption) error
|
||||
}
|
||||
10
core/coreiface/errors.go
Normal file
10
core/coreiface/errors.go
Normal file
@ -0,0 +1,10 @@
|
||||
package iface
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrIsDir = errors.New("this dag node is a directory")
|
||||
ErrNotFile = errors.New("this dag node is not a regular file")
|
||||
ErrOffline = errors.New("this action must be run in online mode, try running 'ipfs daemon' first")
|
||||
ErrNotSupported = errors.New("operation not supported")
|
||||
)
|
||||
19
core/coreiface/idfmt.go
Normal file
19
core/coreiface/idfmt.go
Normal file
@ -0,0 +1,19 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
)
|
||||
|
||||
func FormatKeyID(id peer.ID) string {
|
||||
if s, err := peer.ToCid(id).StringOfBase(mbase.Base36); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// FormatKey formats the given IPNS key in a canonical way.
|
||||
func FormatKey(key Key) string {
|
||||
return FormatKeyID(key.ID())
|
||||
}
|
||||
43
core/coreiface/key.go
Normal file
43
core/coreiface/key.go
Normal file
@ -0,0 +1,43 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
// Key specifies the interface to Keys in KeyAPI Keystore
|
||||
type Key interface {
|
||||
// Key returns key name
|
||||
Name() string
|
||||
|
||||
// Path returns key path
|
||||
Path() path.Path
|
||||
|
||||
// ID returns key PeerID
|
||||
ID() peer.ID
|
||||
}
|
||||
|
||||
// KeyAPI specifies the interface to Keystore
|
||||
type KeyAPI interface {
|
||||
// Generate generates new key, stores it in the keystore under the specified
|
||||
// name and returns a base58 encoded multihash of it's public key
|
||||
Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (Key, error)
|
||||
|
||||
// Rename renames oldName key to newName. Returns the key and whether another
|
||||
// key was overwritten, or an error
|
||||
Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (Key, bool, error)
|
||||
|
||||
// List lists keys stored in keystore
|
||||
List(ctx context.Context) ([]Key, error)
|
||||
|
||||
// Self returns the 'main' node key
|
||||
Self(ctx context.Context) (Key, error)
|
||||
|
||||
// Remove removes keys from keystore. Returns ipns path of the removed key
|
||||
Remove(ctx context.Context, name string) (Key, error)
|
||||
}
|
||||
48
core/coreiface/name.go
Normal file
48
core/coreiface/name.go
Normal file
@ -0,0 +1,48 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
var ErrResolveFailed = errors.New("could not resolve name")
|
||||
|
||||
// IpnsEntry specifies the interface to IpnsEntries
|
||||
type IpnsEntry interface {
|
||||
// Name returns IpnsEntry name
|
||||
Name() string
|
||||
// Value returns IpnsEntry value
|
||||
Value() path.Path
|
||||
}
|
||||
|
||||
type IpnsResult struct {
|
||||
path.Path
|
||||
Err error
|
||||
}
|
||||
|
||||
// NameAPI specifies the interface to IPNS.
|
||||
//
|
||||
// IPNS is a PKI namespace, where names are the hashes of public keys, and the
|
||||
// private key enables publishing new (signed) values. In both publish and
|
||||
// resolve, the default name used is the node's own PeerID, which is the hash of
|
||||
// its public key.
|
||||
//
|
||||
// You can use .Key API to list and generate more names and their respective keys.
|
||||
type NameAPI interface {
|
||||
// Publish announces new IPNS name
|
||||
Publish(ctx context.Context, path path.Path, opts ...options.NamePublishOption) (IpnsEntry, error)
|
||||
|
||||
// Resolve attempts to resolve the newest version of the specified name
|
||||
Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (path.Path, error)
|
||||
|
||||
// Search is a version of Resolve which outputs paths as they are discovered,
|
||||
// reducing the time to first entry
|
||||
//
|
||||
// Note: by default, all paths read from the channel are considered unsafe,
|
||||
// except the latest (last path in channel read buffer).
|
||||
Search(ctx context.Context, name string, opts ...options.NameResolveOption) (<-chan IpnsResult, error)
|
||||
}
|
||||
108
core/coreiface/object.go
Normal file
108
core/coreiface/object.go
Normal file
@ -0,0 +1,108 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
// ObjectStat provides information about dag nodes
|
||||
type ObjectStat struct {
|
||||
// Cid is the CID of the node
|
||||
Cid cid.Cid
|
||||
|
||||
// NumLinks is number of links the node contains
|
||||
NumLinks int
|
||||
|
||||
// BlockSize is size of the raw serialized node
|
||||
BlockSize int
|
||||
|
||||
// LinksSize is size of the links block section
|
||||
LinksSize int
|
||||
|
||||
// DataSize is the size of data block section
|
||||
DataSize int
|
||||
|
||||
// CumulativeSize is size of the tree (BlockSize + link sizes)
|
||||
CumulativeSize int
|
||||
}
|
||||
|
||||
// ChangeType denotes type of change in ObjectChange
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
// DiffAdd is set when a link was added to the graph
|
||||
DiffAdd ChangeType = iota
|
||||
|
||||
// DiffRemove is set when a link was removed from the graph
|
||||
DiffRemove
|
||||
|
||||
// DiffMod is set when a link was changed in the graph
|
||||
DiffMod
|
||||
)
|
||||
|
||||
// ObjectChange represents a change ia a graph
|
||||
type ObjectChange struct {
|
||||
// Type of the change, either:
|
||||
// * DiffAdd - Added a link
|
||||
// * DiffRemove - Removed a link
|
||||
// * DiffMod - Modified a link
|
||||
Type ChangeType
|
||||
|
||||
// Path to the changed link
|
||||
Path string
|
||||
|
||||
// Before holds the link path before the change. Note that when a link is
|
||||
// added, this will be nil.
|
||||
Before path.Resolved
|
||||
|
||||
// After holds the link path after the change. Note that when a link is
|
||||
// removed, this will be nil.
|
||||
After path.Resolved
|
||||
}
|
||||
|
||||
// ObjectAPI specifies the interface to MerkleDAG and contains useful utilities
|
||||
// for manipulating MerkleDAG data structures.
|
||||
type ObjectAPI interface {
|
||||
// New creates new, empty (by default) dag-node.
|
||||
New(context.Context, ...options.ObjectNewOption) (ipld.Node, error)
|
||||
|
||||
// Put imports the data into merkledag
|
||||
Put(context.Context, io.Reader, ...options.ObjectPutOption) (path.Resolved, error)
|
||||
|
||||
// Get returns the node for the path
|
||||
Get(context.Context, path.Path) (ipld.Node, error)
|
||||
|
||||
// Data returns reader for data of the node
|
||||
Data(context.Context, path.Path) (io.Reader, error)
|
||||
|
||||
// Links returns lint or links the node contains
|
||||
Links(context.Context, path.Path) ([]*ipld.Link, error)
|
||||
|
||||
// Stat returns information about the node
|
||||
Stat(context.Context, path.Path) (*ObjectStat, error)
|
||||
|
||||
// AddLink adds a link under the specified path. child path can point to a
|
||||
// subdirectory within the patent which must be present (can be overridden
|
||||
// with WithCreate option).
|
||||
AddLink(ctx context.Context, base path.Path, name string, child path.Path, opts ...options.ObjectAddLinkOption) (path.Resolved, error)
|
||||
|
||||
// RmLink removes a link from the node
|
||||
RmLink(ctx context.Context, base path.Path, link string) (path.Resolved, error)
|
||||
|
||||
// AppendData appends data to the node
|
||||
AppendData(context.Context, path.Path, io.Reader) (path.Resolved, error)
|
||||
|
||||
// SetData sets the data contained in the node
|
||||
SetData(context.Context, path.Path, io.Reader) (path.Resolved, error)
|
||||
|
||||
// Diff returns a set of changes needed to transform the first object into the
|
||||
// second.
|
||||
Diff(context.Context, path.Path, path.Path) ([]ObjectChange, error)
|
||||
}
|
||||
164
core/coreiface/options/block.go
Normal file
164
core/coreiface/options/block.go
Normal file
@ -0,0 +1,164 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
mc "github.com/multiformats/go-multicodec"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
type BlockPutSettings struct {
|
||||
CidPrefix cid.Prefix
|
||||
Pin bool
|
||||
}
|
||||
|
||||
type BlockRmSettings struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
type BlockPutOption func(*BlockPutSettings) error
|
||||
type BlockRmOption func(*BlockRmSettings) error
|
||||
|
||||
func BlockPutOptions(opts ...BlockPutOption) (*BlockPutSettings, error) {
|
||||
var cidPrefix cid.Prefix
|
||||
|
||||
// Baseline is CIDv1 raw sha2-255-32 (can be tweaked later via opts)
|
||||
cidPrefix.Version = 1
|
||||
cidPrefix.Codec = uint64(mc.Raw)
|
||||
cidPrefix.MhType = mh.SHA2_256
|
||||
cidPrefix.MhLength = -1 // -1 means len is to be calculated during mh.Sum()
|
||||
|
||||
options := &BlockPutSettings{
|
||||
CidPrefix: cidPrefix,
|
||||
Pin: false,
|
||||
}
|
||||
|
||||
// Apply any overrides
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func BlockRmOptions(opts ...BlockRmOption) (*BlockRmSettings, error) {
|
||||
options := &BlockRmSettings{
|
||||
Force: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type blockOpts struct{}
|
||||
|
||||
var Block blockOpts
|
||||
|
||||
// CidCodec is the modern option for Block.Put which specifies the multicodec to use
|
||||
// in the CID returned by the Block.Put operation.
|
||||
// It uses correct codes from go-multicodec and replaces the old Format now with CIDv1 as the default.
|
||||
func (blockOpts) CidCodec(codecName string) BlockPutOption {
|
||||
return func(settings *BlockPutSettings) error {
|
||||
if codecName == "" {
|
||||
return nil
|
||||
}
|
||||
code, err := codeFromName(codecName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.CidPrefix.Codec = uint64(code)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Map string to code from go-multicodec
|
||||
func codeFromName(codecName string) (mc.Code, error) {
|
||||
var cidCodec mc.Code
|
||||
err := cidCodec.Set(codecName)
|
||||
return cidCodec, err
|
||||
}
|
||||
|
||||
// Format is a legacy option for Block.Put which specifies the multicodec to
|
||||
// use to serialize the object.
|
||||
// Provided for backward-compatibility only. Use CidCodec instead.
|
||||
func (blockOpts) Format(format string) BlockPutOption {
|
||||
return func(settings *BlockPutSettings) error {
|
||||
if format == "" {
|
||||
return nil
|
||||
}
|
||||
// Opt-in CIDv0 support for backward-compatibility
|
||||
if format == "v0" {
|
||||
settings.CidPrefix.Version = 0
|
||||
}
|
||||
|
||||
// Fixup a legacy (invalid) names for dag-pb (0x70)
|
||||
if format == "v0" || format == "protobuf" {
|
||||
format = "dag-pb"
|
||||
}
|
||||
|
||||
// Fixup invalid name for dag-cbor (0x71)
|
||||
if format == "cbor" {
|
||||
format = "dag-cbor"
|
||||
}
|
||||
|
||||
// Set code based on name passed as "format"
|
||||
code, err := codeFromName(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.CidPrefix.Codec = uint64(code)
|
||||
|
||||
// If CIDv0, ensure all parameters are compatible
|
||||
// (in theory go-cid would validate this anyway, but we want to provide better errors)
|
||||
pref := settings.CidPrefix
|
||||
if pref.Version == 0 {
|
||||
if pref.Codec != uint64(mc.DagPb) {
|
||||
return fmt.Errorf("only dag-pb is allowed with CIDv0")
|
||||
}
|
||||
if pref.MhType != mh.SHA2_256 || (pref.MhLength != -1 && pref.MhLength != 32) {
|
||||
return fmt.Errorf("only sha2-255-32 is allowed with CIDv0")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Hash is an option for Block.Put which specifies the multihash settings to use
|
||||
// when hashing the object. Default is mh.SHA2_256 (0x12).
|
||||
// If mhLen is set to -1, default length for the hash will be used
|
||||
func (blockOpts) Hash(mhType uint64, mhLen int) BlockPutOption {
|
||||
return func(settings *BlockPutSettings) error {
|
||||
settings.CidPrefix.MhType = mhType
|
||||
settings.CidPrefix.MhLength = mhLen
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pin is an option for Block.Put which specifies whether to (recursively) pin
|
||||
// added blocks
|
||||
func (blockOpts) Pin(pin bool) BlockPutOption {
|
||||
return func(settings *BlockPutSettings) error {
|
||||
settings.Pin = pin
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Force is an option for Block.Rm which, when set to true, will ignore
|
||||
// non-existing blocks
|
||||
func (blockOpts) Force(force bool) BlockRmOption {
|
||||
return func(settings *BlockRmSettings) error {
|
||||
settings.Force = force
|
||||
return nil
|
||||
}
|
||||
}
|
||||
62
core/coreiface/options/dht.go
Normal file
62
core/coreiface/options/dht.go
Normal file
@ -0,0 +1,62 @@
|
||||
package options
|
||||
|
||||
type DhtProvideSettings struct {
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type DhtFindProvidersSettings struct {
|
||||
NumProviders int
|
||||
}
|
||||
|
||||
type DhtProvideOption func(*DhtProvideSettings) error
|
||||
type DhtFindProvidersOption func(*DhtFindProvidersSettings) error
|
||||
|
||||
func DhtProvideOptions(opts ...DhtProvideOption) (*DhtProvideSettings, error) {
|
||||
options := &DhtProvideSettings{
|
||||
Recursive: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func DhtFindProvidersOptions(opts ...DhtFindProvidersOption) (*DhtFindProvidersSettings, error) {
|
||||
options := &DhtFindProvidersSettings{
|
||||
NumProviders: 20,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type dhtOpts struct{}
|
||||
|
||||
var Dht dhtOpts
|
||||
|
||||
// Recursive is an option for Dht.Provide which specifies whether to provide
|
||||
// the given path recursively
|
||||
func (dhtOpts) Recursive(recursive bool) DhtProvideOption {
|
||||
return func(settings *DhtProvideSettings) error {
|
||||
settings.Recursive = recursive
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NumProviders is an option for Dht.FindProviders which specifies the
|
||||
// number of peers to look for. Default is 20
|
||||
func (dhtOpts) NumProviders(numProviders int) DhtFindProvidersOption {
|
||||
return func(settings *DhtFindProvidersSettings) error {
|
||||
settings.NumProviders = numProviders
|
||||
return nil
|
||||
}
|
||||
}
|
||||
47
core/coreiface/options/global.go
Normal file
47
core/coreiface/options/global.go
Normal file
@ -0,0 +1,47 @@
|
||||
package options
|
||||
|
||||
type ApiSettings struct {
|
||||
Offline bool
|
||||
FetchBlocks bool
|
||||
}
|
||||
|
||||
type ApiOption func(*ApiSettings) error
|
||||
|
||||
func ApiOptions(opts ...ApiOption) (*ApiSettings, error) {
|
||||
options := &ApiSettings{
|
||||
Offline: false,
|
||||
FetchBlocks: true,
|
||||
}
|
||||
|
||||
return ApiOptionsTo(options, opts...)
|
||||
}
|
||||
|
||||
func ApiOptionsTo(options *ApiSettings, opts ...ApiOption) (*ApiSettings, error) {
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type apiOpts struct{}
|
||||
|
||||
var Api apiOpts
|
||||
|
||||
func (apiOpts) Offline(offline bool) ApiOption {
|
||||
return func(settings *ApiSettings) error {
|
||||
settings.Offline = offline
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// FetchBlocks when set to false prevents api from fetching blocks from the
|
||||
// network while allowing other services such as IPNS to still be online
|
||||
func (apiOpts) FetchBlocks(fetch bool) ApiOption {
|
||||
return func(settings *ApiSettings) error {
|
||||
settings.FetchBlocks = fetch
|
||||
return nil
|
||||
}
|
||||
}
|
||||
87
core/coreiface/options/key.go
Normal file
87
core/coreiface/options/key.go
Normal file
@ -0,0 +1,87 @@
|
||||
package options
|
||||
|
||||
const (
|
||||
RSAKey = "rsa"
|
||||
Ed25519Key = "ed25519"
|
||||
|
||||
DefaultRSALen = 2048
|
||||
)
|
||||
|
||||
type KeyGenerateSettings struct {
|
||||
Algorithm string
|
||||
Size int
|
||||
}
|
||||
|
||||
type KeyRenameSettings struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
type KeyGenerateOption func(*KeyGenerateSettings) error
|
||||
type KeyRenameOption func(*KeyRenameSettings) error
|
||||
|
||||
func KeyGenerateOptions(opts ...KeyGenerateOption) (*KeyGenerateSettings, error) {
|
||||
options := &KeyGenerateSettings{
|
||||
Algorithm: RSAKey,
|
||||
Size: -1,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func KeyRenameOptions(opts ...KeyRenameOption) (*KeyRenameSettings, error) {
|
||||
options := &KeyRenameSettings{
|
||||
Force: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type keyOpts struct{}
|
||||
|
||||
var Key keyOpts
|
||||
|
||||
// Type is an option for Key.Generate which specifies which algorithm
|
||||
// should be used for the key. Default is options.RSAKey
|
||||
//
|
||||
// Supported key types:
|
||||
// * options.RSAKey
|
||||
// * options.Ed25519Key
|
||||
func (keyOpts) Type(algorithm string) KeyGenerateOption {
|
||||
return func(settings *KeyGenerateSettings) error {
|
||||
settings.Algorithm = algorithm
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Size is an option for Key.Generate which specifies the size of the key to
|
||||
// generated. Default is -1
|
||||
//
|
||||
// value of -1 means 'use default size for key type':
|
||||
// - 2048 for RSA
|
||||
func (keyOpts) Size(size int) KeyGenerateOption {
|
||||
return func(settings *KeyGenerateSettings) error {
|
||||
settings.Size = size
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Force is an option for Key.Rename which specifies whether to allow to
|
||||
// replace existing keys.
|
||||
func (keyOpts) Force(force bool) KeyRenameOption {
|
||||
return func(settings *KeyRenameSettings) error {
|
||||
settings.Force = force
|
||||
return nil
|
||||
}
|
||||
}
|
||||
121
core/coreiface/options/name.go
Normal file
121
core/coreiface/options/name.go
Normal file
@ -0,0 +1,121 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
ropts "github.com/ipfs/boxo/coreiface/options/namesys"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultNameValidTime = 24 * time.Hour
|
||||
)
|
||||
|
||||
type NamePublishSettings struct {
|
||||
ValidTime time.Duration
|
||||
Key string
|
||||
|
||||
TTL *time.Duration
|
||||
|
||||
AllowOffline bool
|
||||
}
|
||||
|
||||
type NameResolveSettings struct {
|
||||
Cache bool
|
||||
|
||||
ResolveOpts []ropts.ResolveOpt
|
||||
}
|
||||
|
||||
type NamePublishOption func(*NamePublishSettings) error
|
||||
type NameResolveOption func(*NameResolveSettings) error
|
||||
|
||||
func NamePublishOptions(opts ...NamePublishOption) (*NamePublishSettings, error) {
|
||||
options := &NamePublishSettings{
|
||||
ValidTime: DefaultNameValidTime,
|
||||
Key: "self",
|
||||
|
||||
AllowOffline: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func NameResolveOptions(opts ...NameResolveOption) (*NameResolveSettings, error) {
|
||||
options := &NameResolveSettings{
|
||||
Cache: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type nameOpts struct{}
|
||||
|
||||
var Name nameOpts
|
||||
|
||||
// ValidTime is an option for Name.Publish which specifies for how long the
|
||||
// entry will remain valid. Default value is 24h
|
||||
func (nameOpts) ValidTime(validTime time.Duration) NamePublishOption {
|
||||
return func(settings *NamePublishSettings) error {
|
||||
settings.ValidTime = validTime
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Key is an option for Name.Publish which specifies the key to use for
|
||||
// publishing. Default value is "self" which is the node's own PeerID.
|
||||
// The key parameter must be either PeerID or keystore key alias.
|
||||
//
|
||||
// You can use KeyAPI to list and generate more names and their respective keys.
|
||||
func (nameOpts) Key(key string) NamePublishOption {
|
||||
return func(settings *NamePublishSettings) error {
|
||||
settings.Key = key
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowOffline is an option for Name.Publish which specifies whether to allow
|
||||
// publishing when the node is offline. Default value is false
|
||||
func (nameOpts) AllowOffline(allow bool) NamePublishOption {
|
||||
return func(settings *NamePublishSettings) error {
|
||||
settings.AllowOffline = allow
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TTL is an option for Name.Publish which specifies the time duration the
|
||||
// published record should be cached for (caution: experimental).
|
||||
func (nameOpts) TTL(ttl time.Duration) NamePublishOption {
|
||||
return func(settings *NamePublishSettings) error {
|
||||
settings.TTL = &ttl
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Cache is an option for Name.Resolve which specifies if cache should be used.
|
||||
// Default value is true
|
||||
func (nameOpts) Cache(cache bool) NameResolveOption {
|
||||
return func(settings *NameResolveSettings) error {
|
||||
settings.Cache = cache
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (nameOpts) ResolveOption(opt ropts.ResolveOpt) NameResolveOption {
|
||||
return func(settings *NameResolveSettings) error {
|
||||
settings.ResolveOpts = append(settings.ResolveOpts, opt)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
123
core/coreiface/options/namesys/opts.go
Normal file
123
core/coreiface/options/namesys/opts.go
Normal file
@ -0,0 +1,123 @@
|
||||
package nsopts
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultDepthLimit is the default depth limit used by Resolve.
|
||||
DefaultDepthLimit = 32
|
||||
|
||||
// UnlimitedDepth allows infinite recursion in Resolve. You
|
||||
// probably don't want to use this, but it's here if you absolutely
|
||||
// trust resolution to eventually complete and can't put an upper
|
||||
// limit on how many steps it will take.
|
||||
UnlimitedDepth = 0
|
||||
|
||||
// DefaultIPNSRecordTTL specifies the time that the record can be cached
|
||||
// before checking if its validity again.
|
||||
DefaultIPNSRecordTTL = time.Minute
|
||||
|
||||
// DefaultIPNSRecordEOL specifies the time that the network will cache IPNS
|
||||
// records after being published. Records should be re-published before this
|
||||
// interval expires. We use the same default expiration as the DHT.
|
||||
DefaultIPNSRecordEOL = 48 * time.Hour
|
||||
)
|
||||
|
||||
// ResolveOpts specifies options for resolving an IPNS path
|
||||
type ResolveOpts struct {
|
||||
// Recursion depth limit
|
||||
Depth uint
|
||||
// The number of IPNS records to retrieve from the DHT
|
||||
// (the best record is selected from this set)
|
||||
DhtRecordCount uint
|
||||
// The amount of time to wait for DHT records to be fetched
|
||||
// and verified. A zero value indicates that there is no explicit
|
||||
// timeout (although there is an implicit timeout due to dial
|
||||
// timeouts within the DHT)
|
||||
DhtTimeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultResolveOpts returns the default options for resolving
|
||||
// an IPNS path
|
||||
func DefaultResolveOpts() ResolveOpts {
|
||||
return ResolveOpts{
|
||||
Depth: DefaultDepthLimit,
|
||||
DhtRecordCount: 16,
|
||||
DhtTimeout: time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveOpt is used to set an option
|
||||
type ResolveOpt func(*ResolveOpts)
|
||||
|
||||
// Depth is the recursion depth limit
|
||||
func Depth(depth uint) ResolveOpt {
|
||||
return func(o *ResolveOpts) {
|
||||
o.Depth = depth
|
||||
}
|
||||
}
|
||||
|
||||
// DhtRecordCount is the number of IPNS records to retrieve from the DHT
|
||||
func DhtRecordCount(count uint) ResolveOpt {
|
||||
return func(o *ResolveOpts) {
|
||||
o.DhtRecordCount = count
|
||||
}
|
||||
}
|
||||
|
||||
// DhtTimeout is the amount of time to wait for DHT records to be fetched
|
||||
// and verified. A zero value indicates that there is no explicit timeout
|
||||
func DhtTimeout(timeout time.Duration) ResolveOpt {
|
||||
return func(o *ResolveOpts) {
|
||||
o.DhtTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessOpts converts an array of ResolveOpt into a ResolveOpts object
|
||||
func ProcessOpts(opts []ResolveOpt) ResolveOpts {
|
||||
rsopts := DefaultResolveOpts()
|
||||
for _, option := range opts {
|
||||
option(&rsopts)
|
||||
}
|
||||
return rsopts
|
||||
}
|
||||
|
||||
// PublishOptions specifies options for publishing an IPNS record.
|
||||
type PublishOptions struct {
|
||||
EOL time.Time
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
// DefaultPublishOptions returns the default options for publishing an IPNS record.
|
||||
func DefaultPublishOptions() PublishOptions {
|
||||
return PublishOptions{
|
||||
EOL: time.Now().Add(DefaultIPNSRecordEOL),
|
||||
TTL: DefaultIPNSRecordTTL,
|
||||
}
|
||||
}
|
||||
|
||||
// PublishOption is used to set an option for PublishOpts.
|
||||
type PublishOption func(*PublishOptions)
|
||||
|
||||
// PublishWithEOL sets an EOL.
|
||||
func PublishWithEOL(eol time.Time) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.EOL = eol
|
||||
}
|
||||
}
|
||||
|
||||
// PublishWithEOL sets a TTL.
|
||||
func PublishWithTTL(ttl time.Duration) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.TTL = ttl
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessPublishOptions converts an array of PublishOpt into a PublishOpts object.
|
||||
func ProcessPublishOptions(opts []PublishOption) PublishOptions {
|
||||
rsopts := DefaultPublishOptions()
|
||||
for _, option := range opts {
|
||||
option(&rsopts)
|
||||
}
|
||||
return rsopts
|
||||
}
|
||||
124
core/coreiface/options/object.go
Normal file
124
core/coreiface/options/object.go
Normal file
@ -0,0 +1,124 @@
|
||||
package options
|
||||
|
||||
type ObjectNewSettings struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
type ObjectPutSettings struct {
|
||||
InputEnc string
|
||||
DataType string
|
||||
Pin bool
|
||||
}
|
||||
|
||||
type ObjectAddLinkSettings struct {
|
||||
Create bool
|
||||
}
|
||||
|
||||
type ObjectNewOption func(*ObjectNewSettings) error
|
||||
type ObjectPutOption func(*ObjectPutSettings) error
|
||||
type ObjectAddLinkOption func(*ObjectAddLinkSettings) error
|
||||
|
||||
func ObjectNewOptions(opts ...ObjectNewOption) (*ObjectNewSettings, error) {
|
||||
options := &ObjectNewSettings{
|
||||
Type: "empty",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func ObjectPutOptions(opts ...ObjectPutOption) (*ObjectPutSettings, error) {
|
||||
options := &ObjectPutSettings{
|
||||
InputEnc: "json",
|
||||
DataType: "text",
|
||||
Pin: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func ObjectAddLinkOptions(opts ...ObjectAddLinkOption) (*ObjectAddLinkSettings, error) {
|
||||
options := &ObjectAddLinkSettings{
|
||||
Create: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type objectOpts struct{}
|
||||
|
||||
var Object objectOpts
|
||||
|
||||
// Type is an option for Object.New which allows to change the type of created
|
||||
// dag node.
|
||||
//
|
||||
// Supported types:
|
||||
// * 'empty' - Empty node
|
||||
// * 'unixfs-dir' - Empty UnixFS directory
|
||||
func (objectOpts) Type(t string) ObjectNewOption {
|
||||
return func(settings *ObjectNewSettings) error {
|
||||
settings.Type = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// InputEnc is an option for Object.Put which specifies the input encoding of the
|
||||
// data. Default is "json".
|
||||
//
|
||||
// Supported encodings:
|
||||
// * "protobuf"
|
||||
// * "json"
|
||||
func (objectOpts) InputEnc(e string) ObjectPutOption {
|
||||
return func(settings *ObjectPutSettings) error {
|
||||
settings.InputEnc = e
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DataType is an option for Object.Put which specifies the encoding of data
|
||||
// field when using Json or XML input encoding.
|
||||
//
|
||||
// Supported types:
|
||||
// * "text" (default)
|
||||
// * "base64"
|
||||
func (objectOpts) DataType(t string) ObjectPutOption {
|
||||
return func(settings *ObjectPutSettings) error {
|
||||
settings.DataType = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pin is an option for Object.Put which specifies whether to pin the added
|
||||
// objects, default is false
|
||||
func (objectOpts) Pin(pin bool) ObjectPutOption {
|
||||
return func(settings *ObjectPutSettings) error {
|
||||
settings.Pin = pin
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create is an option for Object.AddLink which specifies whether create required
|
||||
// directories for the child
|
||||
func (objectOpts) Create(create bool) ObjectAddLinkOption {
|
||||
return func(settings *ObjectAddLinkSettings) error {
|
||||
settings.Create = create
|
||||
return nil
|
||||
}
|
||||
}
|
||||
283
core/coreiface/options/pin.go
Normal file
283
core/coreiface/options/pin.go
Normal file
@ -0,0 +1,283 @@
|
||||
package options
|
||||
|
||||
import "fmt"
|
||||
|
||||
// PinAddSettings represent the settings for PinAPI.Add
|
||||
type PinAddSettings struct {
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
// PinLsSettings represent the settings for PinAPI.Ls
|
||||
type PinLsSettings struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
// PinIsPinnedSettings represent the settings for PinAPI.IsPinned
|
||||
type PinIsPinnedSettings struct {
|
||||
WithType string
|
||||
}
|
||||
|
||||
// PinRmSettings represents the settings for PinAPI.Rm
|
||||
type PinRmSettings struct {
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
// PinUpdateSettings represent the settings for PinAPI.Update
|
||||
type PinUpdateSettings struct {
|
||||
Unpin bool
|
||||
}
|
||||
|
||||
// PinAddOption is the signature of an option for PinAPI.Add
|
||||
type PinAddOption func(*PinAddSettings) error
|
||||
|
||||
// PinLsOption is the signature of an option for PinAPI.Ls
|
||||
type PinLsOption func(*PinLsSettings) error
|
||||
|
||||
// PinIsPinnedOption is the signature of an option for PinAPI.IsPinned
|
||||
type PinIsPinnedOption func(*PinIsPinnedSettings) error
|
||||
|
||||
// PinRmOption is the signature of an option for PinAPI.Rm
|
||||
type PinRmOption func(*PinRmSettings) error
|
||||
|
||||
// PinUpdateOption is the signature of an option for PinAPI.Update
|
||||
type PinUpdateOption func(*PinUpdateSettings) error
|
||||
|
||||
// PinAddOptions compile a series of PinAddOption into a ready to use
|
||||
// PinAddSettings and set the default values.
|
||||
func PinAddOptions(opts ...PinAddOption) (*PinAddSettings, error) {
|
||||
options := &PinAddSettings{
|
||||
Recursive: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// PinLsOptions compile a series of PinLsOption into a ready to use
|
||||
// PinLsSettings and set the default values.
|
||||
func PinLsOptions(opts ...PinLsOption) (*PinLsSettings, error) {
|
||||
options := &PinLsSettings{
|
||||
Type: "all",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// PinIsPinnedOptions compile a series of PinIsPinnedOption into a ready to use
|
||||
// PinIsPinnedSettings and set the default values.
|
||||
func PinIsPinnedOptions(opts ...PinIsPinnedOption) (*PinIsPinnedSettings, error) {
|
||||
options := &PinIsPinnedSettings{
|
||||
WithType: "all",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// PinRmOptions compile a series of PinRmOption into a ready to use
|
||||
// PinRmSettings and set the default values.
|
||||
func PinRmOptions(opts ...PinRmOption) (*PinRmSettings, error) {
|
||||
options := &PinRmSettings{
|
||||
Recursive: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// PinUpdateOptions compile a series of PinUpdateOption into a ready to use
|
||||
// PinUpdateSettings and set the default values.
|
||||
func PinUpdateOptions(opts ...PinUpdateOption) (*PinUpdateSettings, error) {
|
||||
options := &PinUpdateSettings{
|
||||
Unpin: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type pinOpts struct {
|
||||
Ls pinLsOpts
|
||||
IsPinned pinIsPinnedOpts
|
||||
}
|
||||
|
||||
// Pin provide an access to all the options for the Pin API.
|
||||
var Pin pinOpts
|
||||
|
||||
type pinLsOpts struct{}
|
||||
|
||||
// All is an option for Pin.Ls which will make it return all pins. It is
|
||||
// the default
|
||||
func (pinLsOpts) All() PinLsOption {
|
||||
return Pin.Ls.pinType("all")
|
||||
}
|
||||
|
||||
// Recursive is an option for Pin.Ls which will make it only return recursive
|
||||
// pins
|
||||
func (pinLsOpts) Recursive() PinLsOption {
|
||||
return Pin.Ls.pinType("recursive")
|
||||
}
|
||||
|
||||
// Direct is an option for Pin.Ls which will make it only return direct (non
|
||||
// recursive) pins
|
||||
func (pinLsOpts) Direct() PinLsOption {
|
||||
return Pin.Ls.pinType("direct")
|
||||
}
|
||||
|
||||
// Indirect is an option for Pin.Ls which will make it only return indirect pins
|
||||
// (objects referenced by other recursively pinned objects)
|
||||
func (pinLsOpts) Indirect() PinLsOption {
|
||||
return Pin.Ls.pinType("indirect")
|
||||
}
|
||||
|
||||
// Type is an option for Pin.Ls which will make it only return pins of the given
|
||||
// type.
|
||||
//
|
||||
// Supported values:
|
||||
// - "direct" - directly pinned objects
|
||||
// - "recursive" - roots of recursive pins
|
||||
// - "indirect" - indirectly pinned objects (referenced by recursively pinned
|
||||
// objects)
|
||||
// - "all" - all pinned objects (default)
|
||||
func (pinLsOpts) Type(typeStr string) (PinLsOption, error) {
|
||||
switch typeStr {
|
||||
case "all", "direct", "indirect", "recursive":
|
||||
return Pin.Ls.pinType(typeStr), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr)
|
||||
}
|
||||
}
|
||||
|
||||
// pinType is an option for Pin.Ls which allows to specify which pin types should
|
||||
// be returned
|
||||
//
|
||||
// Supported values:
|
||||
// - "direct" - directly pinned objects
|
||||
// - "recursive" - roots of recursive pins
|
||||
// - "indirect" - indirectly pinned objects (referenced by recursively pinned
|
||||
// objects)
|
||||
// - "all" - all pinned objects (default)
|
||||
func (pinLsOpts) pinType(t string) PinLsOption {
|
||||
return func(settings *PinLsSettings) error {
|
||||
settings.Type = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type pinIsPinnedOpts struct{}
|
||||
|
||||
// All is an option for Pin.IsPinned which will make it search in all type of pins.
|
||||
// It is the default
|
||||
func (pinIsPinnedOpts) All() PinIsPinnedOption {
|
||||
return Pin.IsPinned.pinType("all")
|
||||
}
|
||||
|
||||
// Recursive is an option for Pin.IsPinned which will make it only search in
|
||||
// recursive pins
|
||||
func (pinIsPinnedOpts) Recursive() PinIsPinnedOption {
|
||||
return Pin.IsPinned.pinType("recursive")
|
||||
}
|
||||
|
||||
// Direct is an option for Pin.IsPinned which will make it only search in direct
|
||||
// (non recursive) pins
|
||||
func (pinIsPinnedOpts) Direct() PinIsPinnedOption {
|
||||
return Pin.IsPinned.pinType("direct")
|
||||
}
|
||||
|
||||
// Indirect is an option for Pin.IsPinned which will make it only search indirect
|
||||
// pins (objects referenced by other recursively pinned objects)
|
||||
func (pinIsPinnedOpts) Indirect() PinIsPinnedOption {
|
||||
return Pin.IsPinned.pinType("indirect")
|
||||
}
|
||||
|
||||
// Type is an option for Pin.IsPinned which will make it only search pins of the given
|
||||
// type.
|
||||
//
|
||||
// Supported values:
|
||||
// - "direct" - directly pinned objects
|
||||
// - "recursive" - roots of recursive pins
|
||||
// - "indirect" - indirectly pinned objects (referenced by recursively pinned
|
||||
// objects)
|
||||
// - "all" - all pinned objects (default)
|
||||
func (pinIsPinnedOpts) Type(typeStr string) (PinIsPinnedOption, error) {
|
||||
switch typeStr {
|
||||
case "all", "direct", "indirect", "recursive":
|
||||
return Pin.IsPinned.pinType(typeStr), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr)
|
||||
}
|
||||
}
|
||||
|
||||
// pinType is an option for Pin.IsPinned which allows to specify which pin type the given
|
||||
// pin is expected to be, speeding up the research.
|
||||
//
|
||||
// Supported values:
|
||||
// - "direct" - directly pinned objects
|
||||
// - "recursive" - roots of recursive pins
|
||||
// - "indirect" - indirectly pinned objects (referenced by recursively pinned
|
||||
// objects)
|
||||
// - "all" - all pinned objects (default)
|
||||
func (pinIsPinnedOpts) pinType(t string) PinIsPinnedOption {
|
||||
return func(settings *PinIsPinnedSettings) error {
|
||||
settings.WithType = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive is an option for Pin.Add which specifies whether to pin an entire
|
||||
// object tree or just one object. Default: true
|
||||
func (pinOpts) Recursive(recursive bool) PinAddOption {
|
||||
return func(settings *PinAddSettings) error {
|
||||
settings.Recursive = recursive
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RmRecursive is an option for Pin.Rm which specifies whether to recursively
|
||||
// unpin the object linked to by the specified object(s). This does not remove
|
||||
// indirect pins referenced by other recursive pins.
|
||||
func (pinOpts) RmRecursive(recursive bool) PinRmOption {
|
||||
return func(settings *PinRmSettings) error {
|
||||
settings.Recursive = recursive
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Unpin is an option for Pin.Update which specifies whether to remove the old pin.
|
||||
// Default is true.
|
||||
func (pinOpts) Unpin(unpin bool) PinUpdateOption {
|
||||
return func(settings *PinUpdateSettings) error {
|
||||
settings.Unpin = unpin
|
||||
return nil
|
||||
}
|
||||
}
|
||||
58
core/coreiface/options/pubsub.go
Normal file
58
core/coreiface/options/pubsub.go
Normal file
@ -0,0 +1,58 @@
|
||||
package options
|
||||
|
||||
type PubSubPeersSettings struct {
|
||||
Topic string
|
||||
}
|
||||
|
||||
type PubSubSubscribeSettings struct {
|
||||
Discover bool
|
||||
}
|
||||
|
||||
type PubSubPeersOption func(*PubSubPeersSettings) error
|
||||
type PubSubSubscribeOption func(*PubSubSubscribeSettings) error
|
||||
|
||||
func PubSubPeersOptions(opts ...PubSubPeersOption) (*PubSubPeersSettings, error) {
|
||||
options := &PubSubPeersSettings{
|
||||
Topic: "",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func PubSubSubscribeOptions(opts ...PubSubSubscribeOption) (*PubSubSubscribeSettings, error) {
|
||||
options := &PubSubSubscribeSettings{
|
||||
Discover: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type pubsubOpts struct{}
|
||||
|
||||
var PubSub pubsubOpts
|
||||
|
||||
func (pubsubOpts) Topic(topic string) PubSubPeersOption {
|
||||
return func(settings *PubSubPeersSettings) error {
|
||||
settings.Topic = topic
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (pubsubOpts) Discover(discover bool) PubSubSubscribeOption {
|
||||
return func(settings *PubSubSubscribeSettings) error {
|
||||
settings.Discover = discover
|
||||
return nil
|
||||
}
|
||||
}
|
||||
293
core/coreiface/options/unixfs.go
Normal file
293
core/coreiface/options/unixfs.go
Normal file
@ -0,0 +1,293 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
dag "github.com/ipfs/boxo/ipld/merkledag"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
type Layout int
|
||||
|
||||
const (
|
||||
BalancedLayout Layout = iota
|
||||
TrickleLayout
|
||||
)
|
||||
|
||||
type UnixfsAddSettings struct {
|
||||
CidVersion int
|
||||
MhType uint64
|
||||
|
||||
Inline bool
|
||||
InlineLimit int
|
||||
RawLeaves bool
|
||||
RawLeavesSet bool
|
||||
|
||||
Chunker string
|
||||
Layout Layout
|
||||
|
||||
Pin bool
|
||||
OnlyHash bool
|
||||
FsCache bool
|
||||
NoCopy bool
|
||||
|
||||
Events chan<- interface{}
|
||||
Silent bool
|
||||
Progress bool
|
||||
}
|
||||
|
||||
type UnixfsLsSettings struct {
|
||||
ResolveChildren bool
|
||||
UseCumulativeSize bool
|
||||
}
|
||||
|
||||
type UnixfsAddOption func(*UnixfsAddSettings) error
|
||||
type UnixfsLsOption func(*UnixfsLsSettings) error
|
||||
|
||||
func UnixfsAddOptions(opts ...UnixfsAddOption) (*UnixfsAddSettings, cid.Prefix, error) {
|
||||
options := &UnixfsAddSettings{
|
||||
CidVersion: -1,
|
||||
MhType: mh.SHA2_256,
|
||||
|
||||
Inline: false,
|
||||
InlineLimit: 32,
|
||||
RawLeaves: false,
|
||||
RawLeavesSet: false,
|
||||
|
||||
Chunker: "size-262144",
|
||||
Layout: BalancedLayout,
|
||||
|
||||
Pin: false,
|
||||
OnlyHash: false,
|
||||
FsCache: false,
|
||||
NoCopy: false,
|
||||
|
||||
Events: nil,
|
||||
Silent: false,
|
||||
Progress: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, cid.Prefix{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// nocopy -> rawblocks
|
||||
if options.NoCopy && !options.RawLeaves {
|
||||
// fixed?
|
||||
if options.RawLeavesSet {
|
||||
return nil, cid.Prefix{}, fmt.Errorf("nocopy option requires '--raw-leaves' to be enabled as well")
|
||||
}
|
||||
|
||||
// No, satisfy mandatory constraint.
|
||||
options.RawLeaves = true
|
||||
}
|
||||
|
||||
// (hash != "sha2-256") -> CIDv1
|
||||
if options.MhType != mh.SHA2_256 {
|
||||
switch options.CidVersion {
|
||||
case 0:
|
||||
return nil, cid.Prefix{}, errors.New("CIDv0 only supports sha2-256")
|
||||
case 1, -1:
|
||||
options.CidVersion = 1
|
||||
default:
|
||||
return nil, cid.Prefix{}, fmt.Errorf("unknown CID version: %d", options.CidVersion)
|
||||
}
|
||||
} else {
|
||||
if options.CidVersion < 0 {
|
||||
// Default to CIDv0
|
||||
options.CidVersion = 0
|
||||
}
|
||||
}
|
||||
|
||||
// cidV1 -> raw blocks (by default)
|
||||
if options.CidVersion > 0 && !options.RawLeavesSet {
|
||||
options.RawLeaves = true
|
||||
}
|
||||
|
||||
prefix, err := dag.PrefixForCidVersion(options.CidVersion)
|
||||
if err != nil {
|
||||
return nil, cid.Prefix{}, err
|
||||
}
|
||||
|
||||
prefix.MhType = options.MhType
|
||||
prefix.MhLength = -1
|
||||
|
||||
return options, prefix, nil
|
||||
}
|
||||
|
||||
func UnixfsLsOptions(opts ...UnixfsLsOption) (*UnixfsLsSettings, error) {
|
||||
options := &UnixfsLsSettings{
|
||||
ResolveChildren: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
type unixfsOpts struct{}
|
||||
|
||||
var Unixfs unixfsOpts
|
||||
|
||||
// CidVersion specifies which CID version to use. Defaults to 0 unless an option
|
||||
// that depends on CIDv1 is passed.
|
||||
func (unixfsOpts) CidVersion(version int) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.CidVersion = version
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Hash function to use. Implies CIDv1 if not set to sha2-256 (default).
|
||||
//
|
||||
// Table of functions is declared in https://github.com/multiformats/go-multihash/blob/master/multihash.go
|
||||
func (unixfsOpts) Hash(mhtype uint64) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.MhType = mhtype
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RawLeaves specifies whether to use raw blocks for leaves (data nodes with no
|
||||
// links) instead of wrapping them with unixfs structures.
|
||||
func (unixfsOpts) RawLeaves(enable bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.RawLeaves = enable
|
||||
settings.RawLeavesSet = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Inline tells the adder to inline small blocks into CIDs
|
||||
func (unixfsOpts) Inline(enable bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Inline = enable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// InlineLimit sets the amount of bytes below which blocks will be encoded
|
||||
// directly into CID instead of being stored and addressed by it's hash.
|
||||
// Specifying this option won't enable block inlining. For that use `Inline`
|
||||
// option. Default: 32 bytes
|
||||
//
|
||||
// Note that while there is no hard limit on the number of bytes, it should be
|
||||
// kept at a reasonably low value, such as 64; implementations may choose to
|
||||
// reject anything larger.
|
||||
func (unixfsOpts) InlineLimit(limit int) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.InlineLimit = limit
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Chunker specifies settings for the chunking algorithm to use.
|
||||
//
|
||||
// Default: size-262144, formats:
|
||||
// size-[bytes] - Simple chunker splitting data into blocks of n bytes
|
||||
// rabin-[min]-[avg]-[max] - Rabin chunker
|
||||
func (unixfsOpts) Chunker(chunker string) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Chunker = chunker
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Layout tells the adder how to balance data between leaves.
|
||||
// options.BalancedLayout is the default, it's optimized for static seekable
|
||||
// files.
|
||||
// options.TrickleLayout is optimized for streaming data,
|
||||
func (unixfsOpts) Layout(layout Layout) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Layout = layout
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pin tells the adder to pin the file root recursively after adding
|
||||
func (unixfsOpts) Pin(pin bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Pin = pin
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// HashOnly will make the adder calculate data hash without storing it in the
|
||||
// blockstore or announcing it to the network
|
||||
func (unixfsOpts) HashOnly(hashOnly bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.OnlyHash = hashOnly
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Events specifies channel which will be used to report events about ongoing
|
||||
// Add operation.
|
||||
//
|
||||
// Note that if this channel blocks it may slowdown the adder
|
||||
func (unixfsOpts) Events(sink chan<- interface{}) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Events = sink
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Silent reduces event output
|
||||
func (unixfsOpts) Silent(silent bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Silent = silent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Progress tells the adder whether to enable progress events
|
||||
func (unixfsOpts) Progress(enable bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.Progress = enable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// FsCache tells the adder to check the filestore for pre-existing blocks
|
||||
//
|
||||
// Experimental
|
||||
func (unixfsOpts) FsCache(enable bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.FsCache = enable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NoCopy tells the adder to add the files using filestore. Implies RawLeaves.
|
||||
//
|
||||
// Experimental
|
||||
func (unixfsOpts) Nocopy(enable bool) UnixfsAddOption {
|
||||
return func(settings *UnixfsAddSettings) error {
|
||||
settings.NoCopy = enable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (unixfsOpts) ResolveChildren(resolve bool) UnixfsLsOption {
|
||||
return func(settings *UnixfsLsSettings) error {
|
||||
settings.ResolveChildren = resolve
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (unixfsOpts) UseCumulativeSize(use bool) UnixfsLsOption {
|
||||
return func(settings *UnixfsLsSettings) error {
|
||||
settings.UseCumulativeSize = use
|
||||
return nil
|
||||
}
|
||||
}
|
||||
199
core/coreiface/path/path.go
Normal file
199
core/coreiface/path/path.go
Normal file
@ -0,0 +1,199 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ipfspath "github.com/ipfs/boxo/path"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
// Path is a generic wrapper for paths used in the API. A path can be resolved
|
||||
// to a CID using one of Resolve functions in the API.
|
||||
//
|
||||
// Paths must be prefixed with a valid prefix:
|
||||
//
|
||||
// * /ipfs - Immutable unixfs path (files)
|
||||
// * /ipld - Immutable ipld path (data)
|
||||
// * /ipns - Mutable names. Usually resolves to one of the immutable paths
|
||||
// TODO: /local (MFS)
|
||||
type Path interface {
|
||||
// String returns the path as a string.
|
||||
String() string
|
||||
|
||||
// Namespace returns the first component of the path.
|
||||
//
|
||||
// For example path "/ipfs/QmHash", calling Namespace() will return "ipfs"
|
||||
//
|
||||
// Calling this method on invalid paths (IsValid() != nil) will result in
|
||||
// empty string
|
||||
Namespace() string
|
||||
|
||||
// Mutable returns false if the data pointed to by this path in guaranteed
|
||||
// to not change.
|
||||
//
|
||||
// Note that resolved mutable path can be immutable.
|
||||
Mutable() bool
|
||||
|
||||
// IsValid checks if this path is a valid ipfs Path, returning nil iff it is
|
||||
// valid
|
||||
IsValid() error
|
||||
}
|
||||
|
||||
// Resolved is a path which was resolved to the last resolvable node.
|
||||
// ResolvedPaths are guaranteed to return nil from `IsValid`
|
||||
type Resolved interface {
|
||||
// Cid returns the CID of the node referenced by the path. Remainder of the
|
||||
// path is guaranteed to be within the node.
|
||||
//
|
||||
// Examples:
|
||||
// If you have 3 linked objects: QmRoot -> A -> B:
|
||||
//
|
||||
// cidB := {"foo": {"bar": 42 }}
|
||||
// cidA := {"B": {"/": cidB }}
|
||||
// cidRoot := {"A": {"/": cidA }}
|
||||
//
|
||||
// And resolve paths:
|
||||
//
|
||||
// * "/ipfs/${cidRoot}"
|
||||
// * Calling Cid() will return `cidRoot`
|
||||
// * Calling Root() will return `cidRoot`
|
||||
// * Calling Remainder() will return ``
|
||||
//
|
||||
// * "/ipfs/${cidRoot}/A"
|
||||
// * Calling Cid() will return `cidA`
|
||||
// * Calling Root() will return `cidRoot`
|
||||
// * Calling Remainder() will return ``
|
||||
//
|
||||
// * "/ipfs/${cidRoot}/A/B/foo"
|
||||
// * Calling Cid() will return `cidB`
|
||||
// * Calling Root() will return `cidRoot`
|
||||
// * Calling Remainder() will return `foo`
|
||||
//
|
||||
// * "/ipfs/${cidRoot}/A/B/foo/bar"
|
||||
// * Calling Cid() will return `cidB`
|
||||
// * Calling Root() will return `cidRoot`
|
||||
// * Calling Remainder() will return `foo/bar`
|
||||
Cid() cid.Cid
|
||||
|
||||
// Root returns the CID of the root object of the path
|
||||
//
|
||||
// Example:
|
||||
// If you have 3 linked objects: QmRoot -> A -> B, and resolve path
|
||||
// "/ipfs/QmRoot/A/B", the Root method will return the CID of object QmRoot
|
||||
//
|
||||
// For more examples see the documentation of Cid() method
|
||||
Root() cid.Cid
|
||||
|
||||
// Remainder returns unresolved part of the path
|
||||
//
|
||||
// Example:
|
||||
// If you have 2 linked objects: QmRoot -> A, where A is a CBOR node
|
||||
// containing the following data:
|
||||
//
|
||||
// {"foo": {"bar": 42 }}
|
||||
//
|
||||
// When resolving "/ipld/QmRoot/A/foo/bar", Remainder will return "foo/bar"
|
||||
//
|
||||
// For more examples see the documentation of Cid() method
|
||||
Remainder() string
|
||||
|
||||
Path
|
||||
}
|
||||
|
||||
// path implements coreiface.Path
|
||||
type path struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// resolvedPath implements coreiface.resolvedPath
|
||||
type resolvedPath struct {
|
||||
path
|
||||
cid cid.Cid
|
||||
root cid.Cid
|
||||
remainder string
|
||||
}
|
||||
|
||||
// Join appends provided segments to the base path
|
||||
func Join(base Path, a ...string) Path {
|
||||
s := strings.Join(append([]string{base.String()}, a...), "/")
|
||||
return &path{path: s}
|
||||
}
|
||||
|
||||
// IpfsPath creates new /ipfs path from the provided CID
|
||||
func IpfsPath(c cid.Cid) Resolved {
|
||||
return &resolvedPath{
|
||||
path: path{"/ipfs/" + c.String()},
|
||||
cid: c,
|
||||
root: c,
|
||||
remainder: "",
|
||||
}
|
||||
}
|
||||
|
||||
// IpldPath creates new /ipld path from the provided CID
|
||||
func IpldPath(c cid.Cid) Resolved {
|
||||
return &resolvedPath{
|
||||
path: path{"/ipld/" + c.String()},
|
||||
cid: c,
|
||||
root: c,
|
||||
remainder: "",
|
||||
}
|
||||
}
|
||||
|
||||
// New parses string path to a Path
|
||||
func New(p string) Path {
|
||||
if pp, err := ipfspath.ParsePath(p); err == nil {
|
||||
p = pp.String()
|
||||
}
|
||||
|
||||
return &path{path: p}
|
||||
}
|
||||
|
||||
// NewResolvedPath creates new Resolved path. This function performs no checks
|
||||
// and is intended to be used by resolver implementations. Incorrect inputs may
|
||||
// cause panics. Handle with care.
|
||||
func NewResolvedPath(ipath ipfspath.Path, c cid.Cid, root cid.Cid, remainder string) Resolved {
|
||||
return &resolvedPath{
|
||||
path: path{ipath.String()},
|
||||
cid: c,
|
||||
root: root,
|
||||
remainder: remainder,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *path) String() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
func (p *path) Namespace() string {
|
||||
ip, err := ipfspath.ParsePath(p.path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(ip.Segments()) < 1 {
|
||||
panic("path without namespace") // this shouldn't happen under any scenario
|
||||
}
|
||||
return ip.Segments()[0]
|
||||
}
|
||||
|
||||
func (p *path) Mutable() bool {
|
||||
// TODO: MFS: check for /local
|
||||
return p.Namespace() == "ipns"
|
||||
}
|
||||
|
||||
func (p *path) IsValid() error {
|
||||
_, err := ipfspath.ParsePath(p.path)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *resolvedPath) Cid() cid.Cid {
|
||||
return p.cid
|
||||
}
|
||||
|
||||
func (p *resolvedPath) Root() cid.Cid {
|
||||
return p.root
|
||||
}
|
||||
|
||||
func (p *resolvedPath) Remainder() string {
|
||||
return p.remainder
|
||||
}
|
||||
63
core/coreiface/pin.go
Normal file
63
core/coreiface/pin.go
Normal file
@ -0,0 +1,63 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
// Pin holds information about pinned resource
|
||||
type Pin interface {
|
||||
// Path to the pinned object
|
||||
Path() path.Resolved
|
||||
|
||||
// Type of the pin
|
||||
Type() string
|
||||
|
||||
// if not nil, an error happened. Everything else should be ignored.
|
||||
Err() error
|
||||
}
|
||||
|
||||
// PinStatus holds information about pin health
|
||||
type PinStatus interface {
|
||||
// Ok indicates whether the pin has been verified to be correct
|
||||
Ok() bool
|
||||
|
||||
// BadNodes returns any bad (usually missing) nodes from the pin
|
||||
BadNodes() []BadPinNode
|
||||
}
|
||||
|
||||
// BadPinNode is a node that has been marked as bad by Pin.Verify
|
||||
type BadPinNode interface {
|
||||
// Path is the path of the node
|
||||
Path() path.Resolved
|
||||
|
||||
// Err is the reason why the node has been marked as bad
|
||||
Err() error
|
||||
}
|
||||
|
||||
// PinAPI specifies the interface to pining
|
||||
type PinAPI interface {
|
||||
// Add creates new pin, be default recursive - pinning the whole referenced
|
||||
// tree
|
||||
Add(context.Context, path.Path, ...options.PinAddOption) error
|
||||
|
||||
// Ls returns list of pinned objects on this node
|
||||
Ls(context.Context, ...options.PinLsOption) (<-chan Pin, error)
|
||||
|
||||
// IsPinned returns whether or not the given cid is pinned
|
||||
// and an explanation of why its pinned
|
||||
IsPinned(context.Context, path.Path, ...options.PinIsPinnedOption) (string, bool, error)
|
||||
|
||||
// Rm removes pin for object specified by the path
|
||||
Rm(context.Context, path.Path, ...options.PinRmOption) error
|
||||
|
||||
// Update changes one pin to another, skipping checks for matching paths in
|
||||
// the old tree
|
||||
Update(ctx context.Context, from path.Path, to path.Path, opts ...options.PinUpdateOption) error
|
||||
|
||||
// Verify verifies the integrity of pinned objects
|
||||
Verify(context.Context) (<-chan PinStatus, error)
|
||||
}
|
||||
48
core/coreiface/pubsub.go
Normal file
48
core/coreiface/pubsub.go
Normal file
@ -0,0 +1,48 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
// PubSubSubscription is an active PubSub subscription
|
||||
type PubSubSubscription interface {
|
||||
io.Closer
|
||||
|
||||
// Next return the next incoming message
|
||||
Next(context.Context) (PubSubMessage, error)
|
||||
}
|
||||
|
||||
// PubSubMessage is a single PubSub message
|
||||
type PubSubMessage interface {
|
||||
// From returns id of a peer from which the message has arrived
|
||||
From() peer.ID
|
||||
|
||||
// Data returns the message body
|
||||
Data() []byte
|
||||
|
||||
// Seq returns message identifier
|
||||
Seq() []byte
|
||||
|
||||
// Topics returns list of topics this message was set to
|
||||
Topics() []string
|
||||
}
|
||||
|
||||
// PubSubAPI specifies the interface to PubSub
|
||||
type PubSubAPI interface {
|
||||
// Ls lists subscribed topics by name
|
||||
Ls(context.Context) ([]string, error)
|
||||
|
||||
// Peers list peers we are currently pubsubbing with
|
||||
Peers(context.Context, ...options.PubSubPeersOption) ([]peer.ID, error)
|
||||
|
||||
// Publish a message to a given pubsub topic
|
||||
Publish(context.Context, string, []byte) error
|
||||
|
||||
// Subscribe to messages on a given topic
|
||||
Subscribe(context.Context, string, ...options.PubSubSubscribeOption) (PubSubSubscription, error)
|
||||
}
|
||||
14
core/coreiface/routing.go
Normal file
14
core/coreiface/routing.go
Normal file
@ -0,0 +1,14 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// RoutingAPI specifies the interface to the routing layer.
|
||||
type RoutingAPI interface {
|
||||
// Get retrieves the best value for a given key
|
||||
Get(context.Context, string) ([]byte, error)
|
||||
|
||||
// Put sets a value for a given key
|
||||
Put(ctx context.Context, key string, value []byte) error
|
||||
}
|
||||
57
core/coreiface/swarm.go
Normal file
57
core/coreiface/swarm.go
Normal file
@ -0,0 +1,57 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotConnected = errors.New("not connected")
|
||||
ErrConnNotFound = errors.New("conn not found")
|
||||
)
|
||||
|
||||
// ConnectionInfo contains information about a peer
|
||||
type ConnectionInfo interface {
|
||||
// ID returns PeerID
|
||||
ID() peer.ID
|
||||
|
||||
// Address returns the multiaddress via which we are connected with the peer
|
||||
Address() ma.Multiaddr
|
||||
|
||||
// Direction returns which way the connection was established
|
||||
Direction() network.Direction
|
||||
|
||||
// Latency returns last known round trip time to the peer
|
||||
Latency() (time.Duration, error)
|
||||
|
||||
// Streams returns list of streams established with the peer
|
||||
Streams() ([]protocol.ID, error)
|
||||
}
|
||||
|
||||
// SwarmAPI specifies the interface to libp2p swarm
|
||||
type SwarmAPI interface {
|
||||
// Connect to a given peer
|
||||
Connect(context.Context, peer.AddrInfo) error
|
||||
|
||||
// Disconnect from a given address
|
||||
Disconnect(context.Context, ma.Multiaddr) error
|
||||
|
||||
// Peers returns the list of peers we are connected to
|
||||
Peers(context.Context) ([]ConnectionInfo, error)
|
||||
|
||||
// KnownAddrs returns the list of all addresses this node is aware of
|
||||
KnownAddrs(context.Context) (map[peer.ID][]ma.Multiaddr, error)
|
||||
|
||||
// LocalAddrs returns the list of announced listening addresses
|
||||
LocalAddrs(context.Context) ([]ma.Multiaddr, error)
|
||||
|
||||
// ListenAddrs returns the list of all listening addresses
|
||||
ListenAddrs(context.Context) ([]ma.Multiaddr, error)
|
||||
}
|
||||
97
core/coreiface/tests/api.go
Normal file
97
core/coreiface/tests/api.go
Normal file
@ -0,0 +1,97 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
coreiface "github.com/ipfs/boxo/coreiface"
|
||||
)
|
||||
|
||||
var errAPINotImplemented = errors.New("api not implemented")
|
||||
|
||||
func (tp *TestSuite) makeAPI(ctx context.Context) (coreiface.CoreAPI, error) {
|
||||
api, err := tp.MakeAPISwarm(ctx, false, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api[0], nil
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
// Make creates n nodes. fullIdentity set to false can be ignored
|
||||
MakeAPISwarm(ctx context.Context, fullIdentity bool, n int) ([]coreiface.CoreAPI, error)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) MakeAPISwarm(ctx context.Context, fullIdentity bool, n int) ([]coreiface.CoreAPI, error) {
|
||||
if tp.apis != nil {
|
||||
tp.apis <- 1
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
tp.apis <- -1
|
||||
}()
|
||||
}
|
||||
|
||||
return tp.Provider.MakeAPISwarm(ctx, fullIdentity, n)
|
||||
}
|
||||
|
||||
type TestSuite struct {
|
||||
Provider
|
||||
|
||||
apis chan int
|
||||
}
|
||||
|
||||
func TestApi(p Provider) func(t *testing.T) {
|
||||
running := 1
|
||||
apis := make(chan int)
|
||||
zeroRunning := make(chan struct{})
|
||||
go func() {
|
||||
for i := range apis {
|
||||
running += i
|
||||
if running < 1 {
|
||||
close(zeroRunning)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
tp := &TestSuite{Provider: p, apis: apis}
|
||||
|
||||
return func(t *testing.T) {
|
||||
t.Run("Block", tp.TestBlock)
|
||||
t.Run("Dag", tp.TestDag)
|
||||
t.Run("Dht", tp.TestDht)
|
||||
t.Run("Key", tp.TestKey)
|
||||
t.Run("Name", tp.TestName)
|
||||
t.Run("Object", tp.TestObject)
|
||||
t.Run("Path", tp.TestPath)
|
||||
t.Run("Pin", tp.TestPin)
|
||||
t.Run("PubSub", tp.TestPubSub)
|
||||
t.Run("Routing", tp.TestRouting)
|
||||
t.Run("Unixfs", tp.TestUnixfs)
|
||||
|
||||
apis <- -1
|
||||
t.Run("TestsCancelCtx", func(t *testing.T) {
|
||||
select {
|
||||
case <-zeroRunning:
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("%d test swarms(s) not closed", running)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) hasApi(t *testing.T, tf func(coreiface.CoreAPI) error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tf(api); err != nil {
|
||||
t.Fatal(api)
|
||||
}
|
||||
}
|
||||
354
core/coreiface/tests/block.go
Normal file
354
core/coreiface/tests/block.go
Normal file
@ -0,0 +1,354 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
coreiface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
var (
|
||||
pbCidV0 = "QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN" // dag-pb
|
||||
pbCid = "bafybeiffndsajwhk3lwjewwdxqntmjm4b5wxaaanokonsggenkbw6slwk4" // dag-pb
|
||||
rawCid = "bafkreiffndsajwhk3lwjewwdxqntmjm4b5wxaaanokonsggenkbw6slwk4" // raw bytes
|
||||
cborCid = "bafyreicnga62zhxnmnlt6ymq5hcbsg7gdhqdu6z4ehu3wpjhvqnflfy6nm" // dag-cbor
|
||||
cborKCid = "bafyr2qgsohbwdlk7ajmmbb4lhoytmest4wdbe5xnexfvtxeatuyqqmwv3fgxp3pmhpc27gwey2cct56gloqefoqwcf3yqiqzsaqb7p4jefhcw" // dag-cbor keccak-512
|
||||
)
|
||||
|
||||
// dag-pb
|
||||
func pbBlock() io.Reader {
|
||||
return bytes.NewReader([]byte{10, 12, 8, 2, 18, 6, 104, 101, 108, 108, 111, 10, 24, 6})
|
||||
}
|
||||
|
||||
// dag-cbor
|
||||
func cborBlock() io.Reader {
|
||||
return bytes.NewReader([]byte{101, 72, 101, 108, 108, 111})
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlock(t *testing.T) {
|
||||
tp.hasApi(t, func(api coreiface.CoreAPI) error {
|
||||
if api.Block() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestBlockPut (get raw CIDv1)", tp.TestBlockPut)
|
||||
t.Run("TestBlockPutCidCodec: dag-pb", tp.TestBlockPutCidCodecDagPb)
|
||||
t.Run("TestBlockPutCidCodec: dag-cbor", tp.TestBlockPutCidCodecDagCbor)
|
||||
t.Run("TestBlockPutFormat (legacy): cbor → dag-cbor", tp.TestBlockPutFormatDagCbor)
|
||||
t.Run("TestBlockPutFormat (legacy): protobuf → dag-pb", tp.TestBlockPutFormatDagPb)
|
||||
t.Run("TestBlockPutFormat (legacy): v0 → CIDv0", tp.TestBlockPutFormatV0)
|
||||
t.Run("TestBlockPutHash", tp.TestBlockPutHash)
|
||||
t.Run("TestBlockGet", tp.TestBlockGet)
|
||||
t.Run("TestBlockRm", tp.TestBlockRm)
|
||||
t.Run("TestBlockStat", tp.TestBlockStat)
|
||||
t.Run("TestBlockPin", tp.TestBlockPin)
|
||||
}
|
||||
|
||||
// when no opts are passed, produced CID has 'raw' codec
|
||||
func (tp *TestSuite) TestBlockPut(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, pbBlock())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != rawCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
// Format is deprecated, it used invalid codec names.
|
||||
// Confirm 'cbor' gets fixed to 'dag-cbor'
|
||||
func (tp *TestSuite) TestBlockPutFormatDagCbor(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, cborBlock(), opt.Block.Format("cbor"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != cborCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
// Format is deprecated, it used invalid codec names.
|
||||
// Confirm 'protobuf' got fixed to 'dag-pb'
|
||||
func (tp *TestSuite) TestBlockPutFormatDagPb(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, pbBlock(), opt.Block.Format("protobuf"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != pbCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
// Format is deprecated, it used invalid codec names.
|
||||
// Confirm fake codec 'v0' got fixed to CIDv0 (with implicit dag-pb codec)
|
||||
func (tp *TestSuite) TestBlockPutFormatV0(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, pbBlock(), opt.Block.Format("v0"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != pbCidV0 {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockPutCidCodecDagCbor(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, cborBlock(), opt.Block.CidCodec("dag-cbor"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != cborCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockPutCidCodecDagPb(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, pbBlock(), opt.Block.CidCodec("dag-pb"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != pbCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockPutHash(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(
|
||||
ctx,
|
||||
cborBlock(),
|
||||
opt.Block.Hash(mh.KECCAK_512, -1),
|
||||
opt.Block.CidCodec("dag-cbor"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Path().Cid().String() != cborKCid {
|
||||
t.Errorf("got wrong cid: %s", res.Path().Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockGet(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), opt.Block.Format("raw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := api.Block().Get(ctx, res.Path())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(d) != "Hello" {
|
||||
t.Error("didn't get correct data back")
|
||||
}
|
||||
|
||||
p := path.New("/ipfs/" + res.Path().Cid().String())
|
||||
|
||||
rp, err := api.ResolvePath(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rp.Cid().String() != res.Path().Cid().String() {
|
||||
t.Error("paths didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockRm(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), opt.Block.Format("raw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := api.Block().Get(ctx, res.Path())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(d) != "Hello" {
|
||||
t.Error("didn't get correct data back")
|
||||
}
|
||||
|
||||
err = api.Block().Rm(ctx, res.Path())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Block().Get(ctx, res.Path())
|
||||
if err == nil {
|
||||
t.Fatal("expected err to exist")
|
||||
}
|
||||
if !ipld.IsNotFound(err) {
|
||||
t.Errorf("unexpected error; %s", err.Error())
|
||||
}
|
||||
|
||||
err = api.Block().Rm(ctx, res.Path())
|
||||
if err == nil {
|
||||
t.Fatal("expected err to exist")
|
||||
}
|
||||
if !ipld.IsNotFound(err) {
|
||||
t.Errorf("unexpected error; %s", err.Error())
|
||||
}
|
||||
|
||||
err = api.Block().Rm(ctx, res.Path(), opt.Block.Force(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockStat(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), opt.Block.Format("raw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat, err := api.Block().Stat(ctx, res.Path())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if stat.Path().String() != res.Path().String() {
|
||||
t.Error("paths don't match")
|
||||
}
|
||||
|
||||
if stat.Size() != len("Hello") {
|
||||
t.Error("length doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBlockPin(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Block().Put(ctx, strings.NewReader(`Hello`), opt.Block.Format("raw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pins, err := api.Pin().Ls(ctx); err != nil || len(pins) != 0 {
|
||||
t.Fatal("expected 0 pins")
|
||||
}
|
||||
|
||||
res, err := api.Block().Put(
|
||||
ctx,
|
||||
strings.NewReader(`Hello`),
|
||||
opt.Block.Pin(true),
|
||||
opt.Block.Format("raw"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pins, err := accPins(api.Pin().Ls(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(pins) != 1 {
|
||||
t.Fatal("expected 1 pin")
|
||||
}
|
||||
if pins[0].Type() != "recursive" {
|
||||
t.Error("expected a recursive pin")
|
||||
}
|
||||
if pins[0].Path().String() != res.Path().String() {
|
||||
t.Error("pin path didn't match")
|
||||
}
|
||||
}
|
||||
200
core/coreiface/tests/dag.go
Normal file
200
core/coreiface/tests/dag.go
Normal file
@ -0,0 +1,200 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
gopath "path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
coreiface "github.com/ipfs/boxo/coreiface"
|
||||
|
||||
ipldcbor "github.com/ipfs/go-ipld-cbor"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestDag(t *testing.T) {
|
||||
tp.hasApi(t, func(api coreiface.CoreAPI) error {
|
||||
if api.Dag() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestPut", tp.TestPut)
|
||||
t.Run("TestPutWithHash", tp.TestPutWithHash)
|
||||
t.Run("TestPath", tp.TestDagPath)
|
||||
t.Run("TestTree", tp.TestTree)
|
||||
t.Run("TestBatch", tp.TestBatch)
|
||||
}
|
||||
|
||||
var (
|
||||
treeExpected = map[string]struct{}{
|
||||
"a": {},
|
||||
"b": {},
|
||||
"c": {},
|
||||
"c/d": {},
|
||||
"c/e": {},
|
||||
}
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestPut(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`"Hello"`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Dag().Add(ctx, nd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nd.Cid().String() != "bafyreicnga62zhxnmnlt6ymq5hcbsg7gdhqdu6z4ehu3wpjhvqnflfy6nm" {
|
||||
t.Errorf("got wrong cid: %s", nd.Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPutWithHash(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`"Hello"`), mh.SHA3_256, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Dag().Add(ctx, nd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nd.Cid().String() != "bafyrmifu7haikttpqqgc5ewvmp76z3z4ebp7h2ph4memw7dq4nt6btmxny" {
|
||||
t.Errorf("got wrong cid: %s", nd.Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestDagPath(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snd, err := ipldcbor.FromJSON(strings.NewReader(`"foo"`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Dag().Add(ctx, snd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+snd.Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Dag().Add(ctx, nd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := path.New(gopath.Join(nd.Cid().String(), "lnk"))
|
||||
|
||||
rp, err := api.ResolvePath(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ndd, err := api.Dag().Get(ctx, rp.Cid())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ndd.Cid().String() != snd.Cid().String() {
|
||||
t.Errorf("got unexpected cid %s, expected %s", ndd.Cid().String(), snd.Cid().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestTree(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"a": 123, "b": "foo", "c": {"d": 321, "e": 111}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Dag().Add(ctx, nd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := api.Dag().Get(ctx, nd.Cid())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lst := res.Tree("", -1)
|
||||
if len(lst) != len(treeExpected) {
|
||||
t.Errorf("tree length of %d doesn't match expected %d", len(lst), len(treeExpected))
|
||||
}
|
||||
|
||||
for _, ent := range lst {
|
||||
if _, ok := treeExpected[ent]; !ok {
|
||||
t.Errorf("unexpected tree entry %s", ent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBatch(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`"Hello"`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nd.Cid().String() != "bafyreicnga62zhxnmnlt6ymq5hcbsg7gdhqdu6z4ehu3wpjhvqnflfy6nm" {
|
||||
t.Errorf("got wrong cid: %s", nd.Cid().String())
|
||||
}
|
||||
|
||||
_, err = api.Dag().Get(ctx, nd.Cid())
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().AddMany(ctx, []ipld.Node{nd}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Dag().Get(ctx, nd.Cid())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
166
core/coreiface/tests/dht.go
Normal file
166
core/coreiface/tests/dht.go
Normal file
@ -0,0 +1,166 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestDht(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.Dht() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestDhtFindPeer", tp.TestDhtFindPeer)
|
||||
t.Run("TestDhtFindProviders", tp.TestDhtFindProviders)
|
||||
t.Run("TestDhtProvide", tp.TestDhtProvide)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestDhtFindPeer(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self0, err := apis[0].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
laddrs0, err := apis[0].Swarm().LocalAddrs(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(laddrs0) != 1 {
|
||||
t.Fatal("unexpected number of local addrs")
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
pi, err := apis[2].Dht().FindPeer(ctx, self0.ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pi.Addrs[0].String() != laddrs0[0].String() {
|
||||
t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String())
|
||||
}
|
||||
|
||||
self2, err := apis[2].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pi, err = apis[1].Dht().FindPeer(ctx, self2.ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
laddrs2, err := apis[2].Swarm().LocalAddrs(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(laddrs2) != 1 {
|
||||
t.Fatal("unexpected number of local addrs")
|
||||
}
|
||||
|
||||
if pi.Addrs[0].String() != laddrs2[0].String() {
|
||||
t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestDhtFindProviders(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err := addTestObject(ctx, apis[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
out, err := apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider := <-out
|
||||
|
||||
self0, err := apis[0].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if provider.ID.String() != self0.ID().String() {
|
||||
t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestDhtProvide(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
off0, err := apis[0].WithOptions(options.Api.Offline(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s, err := off0.Block().Put(ctx, &io.LimitedReader{R: rnd, N: 4092})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := s.Path()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
out, err := apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, ok := <-out
|
||||
|
||||
if ok {
|
||||
t.Fatal("did not expect to find any providers")
|
||||
}
|
||||
|
||||
self0, err := apis[0].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = apis[0].Dht().Provide(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err = apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider := <-out
|
||||
|
||||
if provider.ID.String() != self0.ID().String() {
|
||||
t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String())
|
||||
}
|
||||
}
|
||||
538
core/coreiface/tests/key.go
Normal file
538
core/coreiface/tests/key.go
Normal file
@ -0,0 +1,538 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
"github.com/ipfs/go-cid"
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestKey(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.Key() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestListSelf", tp.TestListSelf)
|
||||
t.Run("TestRenameSelf", tp.TestRenameSelf)
|
||||
t.Run("TestRemoveSelf", tp.TestRemoveSelf)
|
||||
t.Run("TestGenerate", tp.TestGenerate)
|
||||
t.Run("TestGenerateSize", tp.TestGenerateSize)
|
||||
t.Run("TestGenerateType", tp.TestGenerateType)
|
||||
t.Run("TestGenerateExisting", tp.TestGenerateExisting)
|
||||
t.Run("TestList", tp.TestList)
|
||||
t.Run("TestRename", tp.TestRename)
|
||||
t.Run("TestRenameToSelf", tp.TestRenameToSelf)
|
||||
t.Run("TestRenameToSelfForce", tp.TestRenameToSelfForce)
|
||||
t.Run("TestRenameOverwriteNoForce", tp.TestRenameOverwriteNoForce)
|
||||
t.Run("TestRenameOverwrite", tp.TestRenameOverwrite)
|
||||
t.Run("TestRenameSameNameNoForce", tp.TestRenameSameNameNoForce)
|
||||
t.Run("TestRenameSameName", tp.TestRenameSameName)
|
||||
t.Run("TestRemove", tp.TestRemove)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestListSelf(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keys, err := api.Key().List(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list keys: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(keys) != 1 {
|
||||
t.Fatalf("there should be 1 key (self), got %d", len(keys))
|
||||
return
|
||||
}
|
||||
|
||||
if keys[0].Name() != "self" {
|
||||
t.Errorf("expected the key to be called 'self', got '%s'", keys[0].Name())
|
||||
}
|
||||
|
||||
if keys[0].Path().String() != "/ipns/"+iface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected the key to have path '/ipns/%s', got '%s'", iface.FormatKeyID(self.ID()), keys[0].Path().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameSelf(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = api.Key().Rename(ctx, "self", "foo")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot rename key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = api.Key().Rename(ctx, "self", "foo", opt.Key.Force(true))
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot rename key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRemoveSelf(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Key().Remove(ctx, "self")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot remove key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot remove key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestGenerate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if k.Name() != "foo" {
|
||||
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||
}
|
||||
|
||||
verifyIPNSPath(t, k.Path().String())
|
||||
}
|
||||
|
||||
func verifyIPNSPath(t *testing.T, p string) bool {
|
||||
t.Helper()
|
||||
if !strings.HasPrefix(p, "/ipns/") {
|
||||
t.Errorf("path %q does not look like an IPNS path", p)
|
||||
return false
|
||||
}
|
||||
k := p[len("/ipns/"):]
|
||||
c, err := cid.Decode(k)
|
||||
if err != nil {
|
||||
t.Errorf("failed to decode IPNS key %q (%v)", k, err)
|
||||
return false
|
||||
}
|
||||
b36, err := c.StringOfBase(mbase.Base36)
|
||||
if err != nil {
|
||||
t.Fatalf("cid cannot format itself in b36")
|
||||
return false
|
||||
}
|
||||
if b36 != k {
|
||||
t.Errorf("IPNS key is not base36")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestGenerateSize(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := api.Key().Generate(ctx, "foo", opt.Key.Size(2048))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if k.Name() != "foo" {
|
||||
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||
}
|
||||
|
||||
verifyIPNSPath(t, k.Path().String())
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestGenerateType(t *testing.T) {
|
||||
t.Skip("disabled until libp2p/specs#111 is fixed")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := api.Key().Generate(ctx, "bar", opt.Key.Type(opt.Ed25519Key))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if k.Name() != "bar" {
|
||||
t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
|
||||
}
|
||||
|
||||
// Expected to be an inlined identity hash.
|
||||
if !strings.HasPrefix(k.Path().String(), "/ipns/12") {
|
||||
t.Errorf("expected the key to be prefixed with '/ipns/12', got '%s'", k.Path().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestGenerateExisting(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "key with name 'foo' already exists") {
|
||||
t.Fatalf("expected error 'key with name 'foo' already exists', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "self")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot create key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot create key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestList(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
l, err := api.Key().List(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(l) != 2 {
|
||||
t.Fatalf("expected to get 2 keys, got %d", len(l))
|
||||
return
|
||||
}
|
||||
|
||||
if l[0].Name() != "self" {
|
||||
t.Fatalf("expected key 0 to be called 'self', got '%s'", l[0].Name())
|
||||
return
|
||||
}
|
||||
|
||||
if l[1].Name() != "foo" {
|
||||
t.Fatalf("expected key 1 to be called 'foo', got '%s'", l[1].Name())
|
||||
return
|
||||
}
|
||||
|
||||
verifyIPNSPath(t, l[0].Path().String())
|
||||
verifyIPNSPath(t, l[1].Path().String())
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRename(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
k, overwrote, err := api.Key().Rename(ctx, "foo", "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if overwrote {
|
||||
t.Error("overwrote should be false")
|
||||
}
|
||||
|
||||
if k.Name() != "bar" {
|
||||
t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameToSelf(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = api.Key().Rename(ctx, "foo", "self")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot overwrite key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameToSelfForce(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = api.Key().Rename(ctx, "foo", "self", opt.Key.Force(true))
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "cannot overwrite key with name 'self'") {
|
||||
t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameOverwriteNoForce(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = api.Key().Rename(ctx, "foo", "bar")
|
||||
if err == nil {
|
||||
t.Error("expected error to not be nil")
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "key by that name already exists, refusing to overwrite") {
|
||||
t.Fatalf("expected error 'key by that name already exists, refusing to overwrite', got '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameOverwrite(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kfoo, err := api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
k, overwrote, err := api.Key().Rename(ctx, "foo", "bar", opt.Key.Force(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !overwrote {
|
||||
t.Error("overwrote should be true")
|
||||
}
|
||||
|
||||
if k.Name() != "bar" {
|
||||
t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
|
||||
}
|
||||
|
||||
if k.Path().String() != kfoo.Path().String() {
|
||||
t.Errorf("k and kfoo should have equal paths, '%s'!='%s'", k.Path().String(), kfoo.Path().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameSameNameNoForce(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
k, overwrote, err := api.Key().Rename(ctx, "foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if overwrote {
|
||||
t.Error("overwrote should be false")
|
||||
}
|
||||
|
||||
if k.Name() != "foo" {
|
||||
t.Errorf("returned key should be called 'foo', got '%s'", k.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRenameSameName(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
k, overwrote, err := api.Key().Rename(ctx, "foo", "foo", opt.Key.Force(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if overwrote {
|
||||
t.Error("overwrote should be false")
|
||||
}
|
||||
|
||||
if k.Name() != "foo" {
|
||||
t.Errorf("returned key should be called 'foo', got '%s'", k.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRemove(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
k, err := api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
l, err := api.Key().List(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(l) != 2 {
|
||||
t.Fatalf("expected to get 2 keys, got %d", len(l))
|
||||
return
|
||||
}
|
||||
|
||||
p, err := api.Key().Remove(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if k.Path().String() != p.Path().String() {
|
||||
t.Errorf("k and p should have equal paths, '%s'!='%s'", k.Path().String(), p.Path().String())
|
||||
}
|
||||
|
||||
l, err = api.Key().List(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(l) != 1 {
|
||||
t.Fatalf("expected to get 1 key, got %d", len(l))
|
||||
return
|
||||
}
|
||||
|
||||
if l[0].Name() != "self" {
|
||||
t.Errorf("expected the key to be called 'self', got '%s'", l[0].Name())
|
||||
}
|
||||
}
|
||||
274
core/coreiface/tests/name.go
Normal file
274
core/coreiface/tests/name.go
Normal file
@ -0,0 +1,274 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
gopath "path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
|
||||
coreiface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestName(t *testing.T) {
|
||||
tp.hasApi(t, func(api coreiface.CoreAPI) error {
|
||||
if api.Name() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestPublishResolve", tp.TestPublishResolve)
|
||||
t.Run("TestBasicPublishResolveKey", tp.TestBasicPublishResolveKey)
|
||||
t.Run("TestBasicPublishResolveTimeout", tp.TestBasicPublishResolveTimeout)
|
||||
}
|
||||
|
||||
var rnd = rand.New(rand.NewSource(0x62796532303137))
|
||||
|
||||
func addTestObject(ctx context.Context, api coreiface.CoreAPI) (path.Path, error) {
|
||||
return api.Unixfs().Add(ctx, files.NewReaderFile(&io.LimitedReader{R: rnd, N: 4092}))
|
||||
}
|
||||
|
||||
func appendPath(p path.Path, sub string) path.Path {
|
||||
return path.New(gopath.Join(p.String(), sub))
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPublishResolve(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
init := func() (coreiface.CoreAPI, path.Path) {
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil
|
||||
}
|
||||
api := apis[0]
|
||||
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil
|
||||
}
|
||||
return api, p
|
||||
}
|
||||
run := func(t *testing.T, ropts []opt.NameResolveOption) {
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("publishPath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, appendPath(p, "/test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name(), ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("revolvePath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name()+"/test", ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/test" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/test")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("publishRevolvePath", func(t *testing.T) {
|
||||
api, p := init()
|
||||
e, err := api.Name().Publish(ctx, appendPath(p, "/a"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String()+"/a" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name()+"/b", ropts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String()+"/a/b" {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()+"/a/b")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
run(t, []opt.NameResolveOption{})
|
||||
})
|
||||
|
||||
t.Run("nocache", func(t *testing.T) {
|
||||
run(t, []opt.NameResolveOption{opt.Name.Cache(false)})
|
||||
})
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBasicPublishResolveKey(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api := apis[0]
|
||||
|
||||
k, err := api.Key().Generate(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e, err := api.Name().Publish(ctx, p, opt.Name.Key(k.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKey(k) {
|
||||
t.Errorf("expected e.Name to equal %s, got '%s'", e.Name(), coreiface.FormatKey(k))
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
resPath, err := api.Name().Resolve(ctx, e.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resPath.String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBasicPublishResolveTimeout(t *testing.T) {
|
||||
t.Skip("ValidTime doesn't appear to work at this time resolution")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api := apis[0]
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e, err := api.Name().Publish(ctx, p, opt.Name.ValidTime(time.Millisecond*100))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
self, err := api.Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e.Name() != coreiface.FormatKeyID(self.ID()) {
|
||||
t.Errorf("expected e.Name to equal '%s', got '%s'", coreiface.FormatKeyID(self.ID()), e.Name())
|
||||
}
|
||||
|
||||
if e.Value().String() != p.String() {
|
||||
t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String())
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
_, err = api.Name().Resolve(ctx, e.Name())
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: When swarm api is created, add multinode tests
|
||||
467
core/coreiface/tests/object.go
Normal file
467
core/coreiface/tests/object.go
Normal file
@ -0,0 +1,467 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestObject(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.Object() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestNew", tp.TestNew)
|
||||
t.Run("TestObjectPut", tp.TestObjectPut)
|
||||
t.Run("TestObjectGet", tp.TestObjectGet)
|
||||
t.Run("TestObjectData", tp.TestObjectData)
|
||||
t.Run("TestObjectLinks", tp.TestObjectLinks)
|
||||
t.Run("TestObjectStat", tp.TestObjectStat)
|
||||
t.Run("TestObjectAddLink", tp.TestObjectAddLink)
|
||||
t.Run("TestObjectAddLinkCreate", tp.TestObjectAddLinkCreate)
|
||||
t.Run("TestObjectRmLink", tp.TestObjectRmLink)
|
||||
t.Run("TestObjectAddData", tp.TestObjectAddData)
|
||||
t.Run("TestObjectSetData", tp.TestObjectSetData)
|
||||
t.Run("TestDiffTest", tp.TestDiffTest)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestNew(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
emptyNode, err := api.Object().New(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dirNode, err := api.Object().New(ctx, opt.Object.Type("unixfs-dir"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if emptyNode.String() != "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" {
|
||||
t.Errorf("Unexpected emptyNode path: %s", emptyNode.String())
|
||||
}
|
||||
|
||||
if dirNode.String() != "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" {
|
||||
t.Errorf("Unexpected dirNode path: %s", dirNode.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectPut(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"YmFy"}`), opt.Object.DataType("base64")) //bar
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pbBytes, err := hex.DecodeString("0a0362617a")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p3, err := api.Object().Put(ctx, bytes.NewReader(pbBytes), opt.Object.InputEnc("protobuf"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p1.String() != "/ipfs/QmQeGyS87nyijii7kFt1zbe4n2PsXTFimzsdxyE9qh9TST" {
|
||||
t.Errorf("unexpected path: %s", p1.String())
|
||||
}
|
||||
|
||||
if p2.String() != "/ipfs/QmNeYRbCibmaMMK6Du6ChfServcLqFvLJF76PzzF76SPrZ" {
|
||||
t.Errorf("unexpected path: %s", p2.String())
|
||||
}
|
||||
|
||||
if p3.String() != "/ipfs/QmZreR7M2t7bFXAdb1V5FtQhjk4t36GnrvueLJowJbQM9m" {
|
||||
t.Errorf("unexpected path: %s", p3.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectGet(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd, err := api.Object().Get(ctx, p1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(nd.RawData()[len(nd.RawData())-3:]) != "foo" {
|
||||
t.Fatal("got non-matching data")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := api.Object().Data(ctx, p1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(data) != "foo" {
|
||||
t.Fatal("got non-matching data")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectLinks(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`"}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
links, err := api.Object().Links(ctx, p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(links) != 1 {
|
||||
t.Errorf("unexpected number of links: %d", len(links))
|
||||
}
|
||||
|
||||
if links[0].Cid.String() != p1.Cid().String() {
|
||||
t.Fatal("cids didn't batch")
|
||||
}
|
||||
|
||||
if links[0].Name != "bar" {
|
||||
t.Fatal("unexpected link name")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectStat(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat, err := api.Object().Stat(ctx, p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if stat.Cid.String() != p2.Cid().String() {
|
||||
t.Error("unexpected stat.Cid")
|
||||
}
|
||||
|
||||
if stat.NumLinks != 1 {
|
||||
t.Errorf("unexpected stat.NumLinks")
|
||||
}
|
||||
|
||||
if stat.BlockSize != 51 {
|
||||
t.Error("unexpected stat.BlockSize")
|
||||
}
|
||||
|
||||
if stat.LinksSize != 47 {
|
||||
t.Errorf("unexpected stat.LinksSize: %d", stat.LinksSize)
|
||||
}
|
||||
|
||||
if stat.DataSize != 4 {
|
||||
t.Error("unexpected stat.DataSize")
|
||||
}
|
||||
|
||||
if stat.CumulativeSize != 54 {
|
||||
t.Error("unexpected stat.DataSize")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectAddLink(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p3, err := api.Object().AddLink(ctx, p2, "abc", p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
links, err := api.Object().Links(ctx, p3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(links) != 2 {
|
||||
t.Errorf("unexpected number of links: %d", len(links))
|
||||
}
|
||||
|
||||
if links[0].Name != "abc" {
|
||||
t.Errorf("unexpected link 0 name: %s", links[0].Name)
|
||||
}
|
||||
|
||||
if links[1].Name != "bar" {
|
||||
t.Errorf("unexpected link 1 name: %s", links[1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectAddLinkCreate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.Object().AddLink(ctx, p2, "abc/d", p2)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no link by that name") {
|
||||
t.Fatalf("unexpected error: %s", err.Error())
|
||||
}
|
||||
|
||||
p3, err := api.Object().AddLink(ctx, p2, "abc/d", p2, opt.Object.Create(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
links, err := api.Object().Links(ctx, p3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(links) != 2 {
|
||||
t.Errorf("unexpected number of links: %d", len(links))
|
||||
}
|
||||
|
||||
if links[0].Name != "abc" {
|
||||
t.Errorf("unexpected link 0 name: %s", links[0].Name)
|
||||
}
|
||||
|
||||
if links[1].Name != "bar" {
|
||||
t.Errorf("unexpected link 1 name: %s", links[1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectRmLink(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bazz", "Links":[{"Name":"bar", "Hash":"`+p1.Cid().String()+`", "Size":3}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p3, err := api.Object().RmLink(ctx, p2, "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
links, err := api.Object().Links(ctx, p3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(links) != 0 {
|
||||
t.Errorf("unexpected number of links: %d", len(links))
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectAddData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().AppendData(ctx, p1, strings.NewReader("bar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := api.Object().Data(ctx, p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(data) != "foobar" {
|
||||
t.Error("unexpected data")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestObjectSetData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().SetData(ctx, p1, strings.NewReader("bar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := api.Object().Data(ctx, p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(data) != "bar" {
|
||||
t.Error("unexpected data")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestDiffTest(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bar"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := api.Object().Diff(ctx, p1, p2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatal("unexpected changes len")
|
||||
}
|
||||
|
||||
if changes[0].Type != iface.DiffMod {
|
||||
t.Fatal("unexpected change type")
|
||||
}
|
||||
|
||||
if changes[0].Before.String() != p1.String() {
|
||||
t.Fatal("unexpected before path")
|
||||
}
|
||||
|
||||
if changes[0].After.String() != p2.String() {
|
||||
t.Fatal("unexpected before path")
|
||||
}
|
||||
}
|
||||
197
core/coreiface/tests/path.go
Normal file
197
core/coreiface/tests/path.go
Normal file
@ -0,0 +1,197 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
|
||||
ipldcbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestPath(t *testing.T) {
|
||||
t.Run("TestMutablePath", tp.TestMutablePath)
|
||||
t.Run("TestPathRemainder", tp.TestPathRemainder)
|
||||
t.Run("TestEmptyPathRemainder", tp.TestEmptyPathRemainder)
|
||||
t.Run("TestInvalidPathRemainder", tp.TestInvalidPathRemainder)
|
||||
t.Run("TestPathRoot", tp.TestPathRoot)
|
||||
t.Run("TestPathJoin", tp.TestPathJoin)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestMutablePath(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blk, err := api.Block().Put(ctx, strings.NewReader(`foo`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if blk.Path().Mutable() {
|
||||
t.Error("expected /ipld path to be immutable")
|
||||
}
|
||||
|
||||
// get self /ipns path
|
||||
|
||||
if api.Key() == nil {
|
||||
t.Fatal(".Key not implemented")
|
||||
}
|
||||
|
||||
keys, err := api.Key().List(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !keys[0].Path().Mutable() {
|
||||
t.Error("expected self /ipns path to be mutable")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPathRemainder(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if api.Dag() == nil {
|
||||
t.Fatal(".Dag not implemented")
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"foo": {"bar": "baz"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().Add(ctx, nd); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rp1, err := api.ResolvePath(ctx, path.New(nd.String()+"/foo/bar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rp1.Remainder() != "foo/bar" {
|
||||
t.Error("expected to get path remainder")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestEmptyPathRemainder(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if api.Dag() == nil {
|
||||
t.Fatal(".Dag not implemented")
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"foo": {"bar": "baz"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().Add(ctx, nd); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rp1, err := api.ResolvePath(ctx, path.New(nd.Cid().String()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rp1.Remainder() != "" {
|
||||
t.Error("expected the resolved path to not have a remainder")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestInvalidPathRemainder(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if api.Dag() == nil {
|
||||
t.Fatal(".Dag not implemented")
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"foo": {"bar": "baz"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().Add(ctx, nd); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = api.ResolvePath(ctx, path.New("/ipld/"+nd.Cid().String()+"/bar/baz"))
|
||||
if err == nil || !strings.Contains(err.Error(), `no link named "bar"`) {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPathRoot(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if api.Block() == nil {
|
||||
t.Fatal(".Block not implemented")
|
||||
}
|
||||
|
||||
blk, err := api.Block().Put(ctx, strings.NewReader(`foo`), options.Block.Format("raw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if api.Dag() == nil {
|
||||
t.Fatal(".Dag not implemented")
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.FromJSON(strings.NewReader(`{"foo": {"/": "`+blk.Path().Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().Add(ctx, nd); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rp, err := api.ResolvePath(ctx, path.New("/ipld/"+nd.Cid().String()+"/foo"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rp.Root().String() != nd.Cid().String() {
|
||||
t.Error("unexpected path root")
|
||||
}
|
||||
|
||||
if rp.Cid().String() != blk.Path().Cid().String() {
|
||||
t.Error("unexpected path cid")
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPathJoin(t *testing.T) {
|
||||
p1 := path.New("/ipfs/QmYNmQKp6SuaVrpgWRsPTgCQCnpxUYGq76YEKBXuj2N4H6/bar/baz")
|
||||
|
||||
if path.Join(p1, "foo").String() != "/ipfs/QmYNmQKp6SuaVrpgWRsPTgCQCnpxUYGq76YEKBXuj2N4H6/bar/baz/foo" {
|
||||
t.Error("unexpected path")
|
||||
}
|
||||
}
|
||||
601
core/coreiface/tests/pin.go
Normal file
601
core/coreiface/tests/pin.go
Normal file
@ -0,0 +1,601 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
opt "github.com/ipfs/boxo/coreiface/options"
|
||||
"github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
ipldcbor "github.com/ipfs/go-ipld-cbor"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestPin(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.Pin() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestPinAdd", tp.TestPinAdd)
|
||||
t.Run("TestPinSimple", tp.TestPinSimple)
|
||||
t.Run("TestPinRecursive", tp.TestPinRecursive)
|
||||
t.Run("TestPinLsIndirect", tp.TestPinLsIndirect)
|
||||
t.Run("TestPinLsPrecedence", tp.TestPinLsPrecedence)
|
||||
t.Run("TestPinIsPinned", tp.TestPinIsPinned)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinAdd(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err := api.Unixfs().Add(ctx, strFile("foo")())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinSimple(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err := api.Unixfs().Add(ctx, strFile("foo")())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err := accPins(api.Pin().Ls(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
|
||||
if list[0].Path().Cid().String() != p.Cid().String() {
|
||||
t.Error("paths don't match")
|
||||
}
|
||||
|
||||
if list[0].Type() != "recursive" {
|
||||
t.Error("unexpected pin type")
|
||||
}
|
||||
|
||||
assertIsPinned(t, ctx, api, p, "recursive")
|
||||
|
||||
err = api.Pin().Rm(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 0 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinRecursive(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p0, err := api.Unixfs().Add(ctx, strFile("foo")())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p1, err := api.Unixfs().Add(ctx, strFile("bar")())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd2, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+p0.Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nd3, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+p1.Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().AddMany(ctx, []ipld.Node{nd2, nd3}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(nd2.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(nd3.Cid()), opt.Pin.Recursive(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err := accPins(api.Pin().Ls(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 3 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Direct()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
|
||||
if list[0].Path().String() != path.IpldPath(nd3.Cid()).String() {
|
||||
t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.IpfsPath(nd3.Cid()).String())
|
||||
}
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Recursive()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
|
||||
if list[0].Path().String() != path.IpldPath(nd2.Cid()).String() {
|
||||
t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.IpldPath(nd2.Cid()).String())
|
||||
}
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Indirect()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Errorf("unexpected pin list len: %d", len(list))
|
||||
}
|
||||
|
||||
if list[0].Path().Cid().String() != p0.Cid().String() {
|
||||
t.Errorf("unexpected path, %s != %s", list[0].Path().Cid().String(), p0.Cid().String())
|
||||
}
|
||||
|
||||
res, err := api.Pin().Verify(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n := 0
|
||||
for r := range res {
|
||||
if !r.Ok() {
|
||||
t.Error("expected pin to be ok")
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n != 1 {
|
||||
t.Errorf("unexpected verify result count: %d", n)
|
||||
}
|
||||
|
||||
//TODO: figure out a way to test verify without touching IpfsNode
|
||||
/*
|
||||
err = api.Block().Rm(ctx, p0, opt.Block.Force(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err = api.Pin().Verify(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n = 0
|
||||
for r := range res {
|
||||
if r.Ok() {
|
||||
t.Error("expected pin to not be ok")
|
||||
}
|
||||
|
||||
if len(r.BadNodes()) != 1 {
|
||||
t.Fatalf("unexpected badNodes len")
|
||||
}
|
||||
|
||||
if r.BadNodes()[0].Path().Cid().String() != p0.Cid().String() {
|
||||
t.Error("unexpected badNode path")
|
||||
}
|
||||
|
||||
if r.BadNodes()[0].Err().Error() != "merkledag: not found" {
|
||||
t.Errorf("unexpected badNode error: %s", r.BadNodes()[0].Err().Error())
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n != 1 {
|
||||
t.Errorf("unexpected verify result count: %d", n)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// TestPinLsIndirect verifies that indirect nodes are listed by pin ls even if a parent node is directly pinned
|
||||
func (tp *TestSuite) TestPinLsIndirect(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "foo")
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()), opt.Pin.Recursive(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinTypes(t, ctx, api, []cidContainer{grandparent}, []cidContainer{parent}, []cidContainer{leaf})
|
||||
}
|
||||
|
||||
// TestPinLsPrecedence verifies the precedence of pins (recursive > direct > indirect)
|
||||
func (tp *TestSuite) TestPinLsPrecedence(t *testing.T) {
|
||||
// Testing precedence of recursive, direct and indirect pins
|
||||
// Results should be recursive > indirect, direct > indirect, and recursive > direct
|
||||
|
||||
t.Run("TestPinLsPredenceRecursiveIndirect", tp.TestPinLsPredenceRecursiveIndirect)
|
||||
t.Run("TestPinLsPrecedenceDirectIndirect", tp.TestPinLsPrecedenceDirectIndirect)
|
||||
t.Run("TestPinLsPrecedenceRecursiveDirect", tp.TestPinLsPrecedenceRecursiveDirect)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinLsPredenceRecursiveIndirect(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test recursive > indirect
|
||||
leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "recursive > indirect")
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinTypes(t, ctx, api, []cidContainer{grandparent, parent}, []cidContainer{}, []cidContainer{leaf})
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinLsPrecedenceDirectIndirect(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test direct > indirect
|
||||
leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "direct > indirect")
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()), opt.Pin.Recursive(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinTypes(t, ctx, api, []cidContainer{grandparent}, []cidContainer{parent}, []cidContainer{leaf})
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinLsPrecedenceRecursiveDirect(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test recursive > direct
|
||||
leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "recursive + direct = error")
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()), opt.Pin.Recursive(false))
|
||||
if err == nil {
|
||||
t.Fatal("expected error directly pinning a recursively pinned node")
|
||||
}
|
||||
|
||||
assertPinTypes(t, ctx, api, []cidContainer{parent}, []cidContainer{}, []cidContainer{leaf})
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()), opt.Pin.Recursive(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinTypes(t, ctx, api, []cidContainer{grandparent, parent}, []cidContainer{}, []cidContainer{leaf})
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestPinIsPinned(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
api, err := tp.makeAPI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "foofoo")
|
||||
|
||||
assertNotPinned(t, ctx, api, path.IpldPath(grandparent.Cid()))
|
||||
assertNotPinned(t, ctx, api, path.IpldPath(parent.Cid()))
|
||||
assertNotPinned(t, ctx, api, path.IpldPath(leaf.Cid()))
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()), opt.Pin.Recursive(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertNotPinned(t, ctx, api, path.IpldPath(grandparent.Cid()))
|
||||
assertIsPinned(t, ctx, api, path.IpldPath(parent.Cid()), "recursive")
|
||||
assertIsPinned(t, ctx, api, path.IpldPath(leaf.Cid()), "indirect")
|
||||
|
||||
err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()), opt.Pin.Recursive(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertIsPinned(t, ctx, api, path.IpldPath(grandparent.Cid()), "direct")
|
||||
assertIsPinned(t, ctx, api, path.IpldPath(parent.Cid()), "recursive")
|
||||
assertIsPinned(t, ctx, api, path.IpldPath(leaf.Cid()), "indirect")
|
||||
}
|
||||
|
||||
type cidContainer interface {
|
||||
Cid() cid.Cid
|
||||
}
|
||||
|
||||
func getThreeChainedNodes(t *testing.T, ctx context.Context, api iface.CoreAPI, leafData string) (cidContainer, cidContainer, cidContainer) {
|
||||
leaf, err := api.Unixfs().Add(ctx, strFile(leafData)())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parent, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+leaf.Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
grandparent, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+parent.Cid().String()+`"}}`), math.MaxUint64, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := api.Dag().AddMany(ctx, []ipld.Node{parent, grandparent}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return leaf, parent, grandparent
|
||||
}
|
||||
|
||||
func assertPinTypes(t *testing.T, ctx context.Context, api iface.CoreAPI, recusive, direct, indirect []cidContainer) {
|
||||
assertPinLsAllConsistency(t, ctx, api)
|
||||
|
||||
list, err := accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Recursive()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinCids(t, list, recusive...)
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Direct()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinCids(t, list, direct...)
|
||||
|
||||
list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Indirect()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertPinCids(t, list, indirect...)
|
||||
}
|
||||
|
||||
// assertPinCids verifies that the pins match the expected cids
|
||||
func assertPinCids(t *testing.T, pins []iface.Pin, cids ...cidContainer) {
|
||||
t.Helper()
|
||||
|
||||
if expected, actual := len(cids), len(pins); expected != actual {
|
||||
t.Fatalf("expected pin list to have len %d, was %d", expected, actual)
|
||||
}
|
||||
|
||||
cSet := cid.NewSet()
|
||||
for _, c := range cids {
|
||||
cSet.Add(c.Cid())
|
||||
}
|
||||
|
||||
valid := true
|
||||
for _, p := range pins {
|
||||
c := p.Path().Cid()
|
||||
if cSet.Has(c) {
|
||||
cSet.Remove(c)
|
||||
} else {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
valid = valid && cSet.Len() == 0
|
||||
|
||||
if !valid {
|
||||
pinStrs := make([]string, len(pins))
|
||||
for i, p := range pins {
|
||||
pinStrs[i] = p.Path().Cid().String()
|
||||
}
|
||||
pathStrs := make([]string, len(cids))
|
||||
for i, c := range cids {
|
||||
pathStrs[i] = c.Cid().String()
|
||||
}
|
||||
t.Fatalf("expected: %s \nactual: %s", strings.Join(pathStrs, ", "), strings.Join(pinStrs, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
// assertPinLsAllConsistency verifies that listing all pins gives the same result as listing the pin types individually
|
||||
func assertPinLsAllConsistency(t *testing.T, ctx context.Context, api iface.CoreAPI) {
|
||||
t.Helper()
|
||||
allPins, err := accPins(api.Pin().Ls(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
type pinTypeProps struct {
|
||||
*cid.Set
|
||||
opt.PinLsOption
|
||||
}
|
||||
|
||||
all, recursive, direct, indirect := cid.NewSet(), cid.NewSet(), cid.NewSet(), cid.NewSet()
|
||||
typeMap := map[string]*pinTypeProps{
|
||||
"recursive": {recursive, opt.Pin.Ls.Recursive()},
|
||||
"direct": {direct, opt.Pin.Ls.Direct()},
|
||||
"indirect": {indirect, opt.Pin.Ls.Indirect()},
|
||||
}
|
||||
|
||||
for _, p := range allPins {
|
||||
if !all.Visit(p.Path().Cid()) {
|
||||
t.Fatalf("pin ls returned the same cid multiple times")
|
||||
}
|
||||
|
||||
typeStr := p.Type()
|
||||
if typeSet, ok := typeMap[p.Type()]; ok {
|
||||
typeSet.Add(p.Path().Cid())
|
||||
} else {
|
||||
t.Fatalf("unknown pin type: %s", typeStr)
|
||||
}
|
||||
}
|
||||
|
||||
for typeStr, pinProps := range typeMap {
|
||||
pins, err := accPins(api.Pin().Ls(ctx, pinProps.PinLsOption))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected, actual := len(pins), pinProps.Set.Len(); expected != actual {
|
||||
t.Fatalf("pin ls all has %d pins of type %s, but pin ls for the type has %d", expected, typeStr, actual)
|
||||
}
|
||||
|
||||
for _, p := range pins {
|
||||
if pinType := p.Type(); pinType != typeStr {
|
||||
t.Fatalf("returned wrong pin type: expected %s, got %s", typeStr, pinType)
|
||||
}
|
||||
|
||||
if c := p.Path().Cid(); !pinProps.Has(c) {
|
||||
t.Fatalf("%s expected to be in pin ls all as type %s", c.String(), typeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertIsPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path, typeStr string) {
|
||||
t.Helper()
|
||||
withType, err := opt.Pin.IsPinned.Type(typeStr)
|
||||
if err != nil {
|
||||
t.Fatal("unhandled pin type")
|
||||
}
|
||||
|
||||
whyPinned, pinned, err := api.Pin().IsPinned(ctx, p, withType)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pinned {
|
||||
t.Fatalf("%s expected to be pinned with type %s", p, typeStr)
|
||||
}
|
||||
|
||||
switch typeStr {
|
||||
case "recursive", "direct":
|
||||
if typeStr != whyPinned {
|
||||
t.Fatalf("reason for pinning expected to be %s for %s, got %s", typeStr, p, whyPinned)
|
||||
}
|
||||
case "indirect":
|
||||
if whyPinned == "" {
|
||||
t.Fatalf("expected to have a pin reason for %s", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path) {
|
||||
t.Helper()
|
||||
|
||||
_, pinned, err := api.Pin().IsPinned(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pinned {
|
||||
t.Fatalf("%s expected to not be pinned", p)
|
||||
}
|
||||
}
|
||||
|
||||
func accPins(pins <-chan iface.Pin, err error) ([]iface.Pin, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []iface.Pin
|
||||
|
||||
for pin := range pins {
|
||||
if pin.Err() != nil {
|
||||
return nil, pin.Err()
|
||||
}
|
||||
result = append(result, pin)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
136
core/coreiface/tests/pubsub.go
Normal file
136
core/coreiface/tests/pubsub.go
Normal file
@ -0,0 +1,136 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestPubSub(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.PubSub() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestBasicPubSub", tp.TestBasicPubSub)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestBasicPubSub(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sub, err := apis[0].PubSub().Subscribe(ctx, "testch")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
err := apis[1].PubSub().Publish(ctx, "testch", []byte("hello world"))
|
||||
switch err {
|
||||
case nil:
|
||||
case context.Canceled:
|
||||
return
|
||||
default:
|
||||
t.Error(err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the sender to finish before we return.
|
||||
// Otherwise, we can get random errors as publish fails.
|
||||
defer func() {
|
||||
cancel()
|
||||
<-done
|
||||
}()
|
||||
|
||||
m, err := sub.Next(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(m.Data()) != "hello world" {
|
||||
t.Errorf("got invalid data: %s", string(m.Data()))
|
||||
}
|
||||
|
||||
self1, err := apis[1].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if m.From() != self1.ID() {
|
||||
t.Errorf("m.From didn't match")
|
||||
}
|
||||
|
||||
peers, err := apis[1].PubSub().Peers(ctx, options.PubSub.Topic("testch"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(peers) != 1 {
|
||||
t.Fatalf("got incorrect number of peers: %d", len(peers))
|
||||
}
|
||||
|
||||
self0, err := apis[0].Key().Self(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if peers[0] != self0.ID() {
|
||||
t.Errorf("peer didn't match")
|
||||
}
|
||||
|
||||
peers, err = apis[1].PubSub().Peers(ctx, options.PubSub.Topic("nottestch"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(peers) != 0 {
|
||||
t.Fatalf("got incorrect number of peers: %d", len(peers))
|
||||
}
|
||||
|
||||
topics, err := apis[0].PubSub().Ls(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(topics) != 1 {
|
||||
t.Fatalf("got incorrect number of topics: %d", len(peers))
|
||||
}
|
||||
|
||||
if topics[0] != "testch" {
|
||||
t.Errorf("topic didn't match")
|
||||
}
|
||||
|
||||
topics, err = apis[1].PubSub().Ls(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(topics) != 0 {
|
||||
t.Fatalf("got incorrect number of topics: %d", len(peers))
|
||||
}
|
||||
}
|
||||
92
core/coreiface/tests/routing.go
Normal file
92
core/coreiface/tests/routing.go
Normal file
@ -0,0 +1,92 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
iface "github.com/ipfs/boxo/coreiface"
|
||||
ipns_pb "github.com/ipfs/boxo/ipns/pb"
|
||||
)
|
||||
|
||||
func (tp *TestSuite) TestRouting(t *testing.T) {
|
||||
tp.hasApi(t, func(api iface.CoreAPI) error {
|
||||
if api.Routing() == nil {
|
||||
return errAPINotImplemented
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("TestRoutingGet", tp.TestRoutingGet)
|
||||
t.Run("TestRoutingPut", tp.TestRoutingPut)
|
||||
}
|
||||
|
||||
func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI) iface.IpnsEntry {
|
||||
p, err := addTestObject(ctx, api)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entry, err := api.Name().Publish(ctx, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
return entry
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRoutingGet(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Node 1: publishes an IPNS name
|
||||
ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
|
||||
// Node 2: retrieves the best value for the IPNS name.
|
||||
data, err := apis[1].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Checks if values match.
|
||||
var entry ipns_pb.IpnsEntry
|
||||
err = proto.Unmarshal(data, &entry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(entry.GetValue()) != ipnsEntry.Value().String() {
|
||||
t.Fatalf("routing key has wrong value, expected %s, got %s", ipnsEntry.Value().String(), string(entry.GetValue()))
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestSuite) TestRoutingPut(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
apis, err := tp.MakeAPISwarm(ctx, true, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create and publish IPNS entry.
|
||||
ipnsEntry := tp.testRoutingPublishKey(t, ctx, apis[0])
|
||||
|
||||
// Get valid routing value.
|
||||
data, err := apis[0].Routing().Get(ctx, "/ipns/"+ipnsEntry.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Put routing value.
|
||||
err = apis[1].Routing().Put(ctx, "/ipns/"+ipnsEntry.Name(), data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
1080
core/coreiface/tests/unixfs.go
Normal file
1080
core/coreiface/tests/unixfs.go
Normal file
File diff suppressed because it is too large
Load Diff
80
core/coreiface/unixfs.go
Normal file
80
core/coreiface/unixfs.go
Normal file
@ -0,0 +1,80 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/boxo/coreiface/options"
|
||||
path "github.com/ipfs/boxo/coreiface/path"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
type AddEvent struct {
|
||||
Name string
|
||||
Path path.Resolved `json:",omitempty"`
|
||||
Bytes int64 `json:",omitempty"`
|
||||
Size string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// FileType is an enum of possible UnixFS file types.
|
||||
type FileType int32
|
||||
|
||||
const (
|
||||
// TUnknown means the file type isn't known (e.g., it hasn't been
|
||||
// resolved).
|
||||
TUnknown FileType = iota
|
||||
// TFile is a regular file.
|
||||
TFile
|
||||
// TDirectory is a directory.
|
||||
TDirectory
|
||||
// TSymlink is a symlink.
|
||||
TSymlink
|
||||
)
|
||||
|
||||
func (t FileType) String() string {
|
||||
switch t {
|
||||
case TUnknown:
|
||||
return "unknown"
|
||||
case TFile:
|
||||
return "file"
|
||||
case TDirectory:
|
||||
return "directory"
|
||||
case TSymlink:
|
||||
return "symlink"
|
||||
default:
|
||||
return "<unknown file type>"
|
||||
}
|
||||
}
|
||||
|
||||
// DirEntry is a directory entry returned by `Ls`.
|
||||
type DirEntry struct {
|
||||
Name string
|
||||
Cid cid.Cid
|
||||
|
||||
// Only filled when asked to resolve the directory entry.
|
||||
Size uint64 // The size of the file in bytes (or the size of the symlink).
|
||||
Type FileType // The type of the file.
|
||||
Target string // The symlink target (if a symlink).
|
||||
|
||||
Err error
|
||||
}
|
||||
|
||||
// UnixfsAPI is the basic interface to immutable files in IPFS
|
||||
// NOTE: This API is heavily WIP, things are guaranteed to break frequently
|
||||
type UnixfsAPI interface {
|
||||
// Add imports the data from the reader into merkledag file
|
||||
//
|
||||
// TODO: a long useful comment on how to use this for many different scenarios
|
||||
Add(context.Context, files.Node, ...options.UnixfsAddOption) (path.Resolved, error)
|
||||
|
||||
// Get returns a read-only handle to a file tree referenced by a path
|
||||
//
|
||||
// Note that some implementations of this API may apply the specified context
|
||||
// to operations performed on the returned file
|
||||
Get(context.Context, path.Path) (files.Node, error)
|
||||
|
||||
// Ls returns the list of links in a directory. Links aren't guaranteed to be
|
||||
// returned in order
|
||||
Ls(context.Context, path.Path, ...options.UnixfsLsOption) (<-chan DirEntry, error)
|
||||
}
|
||||
20
core/coreiface/util.go
Normal file
20
core/coreiface/util.go
Normal file
@ -0,0 +1,20 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reader interface {
|
||||
ReadSeekCloser
|
||||
Size() uint64
|
||||
CtxReadFull(context.Context, []byte) (int, error)
|
||||
}
|
||||
|
||||
// A ReadSeekCloser implements interfaces to read, copy, seek and close.
|
||||
type ReadSeekCloser interface {
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Closer
|
||||
io.WriterTo
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user