diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index d834dc0d177..b289bb38253 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -41,6 +41,11 @@ func (api *CoreAPI) Key() coreiface.KeyAPI { return &KeyAPI{api, nil} } +//Object returns the ObjectAPI interface backed by the go-ipfs node +func (api *CoreAPI) Object() coreiface.ObjectAPI { + return &ObjectAPI{api, nil} +} + // ResolveNode resolves the path `p` using Unixfx resolver, gets and returns the // resolved Node. func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index 6e00d51ab43..ddcdc8db67f 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -64,6 +64,9 @@ type CoreAPI interface { // Key returns an implementation of Key API. Key() KeyAPI + // ObjectAPI returns an implementation of Object API + Object() ObjectAPI + // ResolvePath resolves the path using Unixfs resolver ResolvePath(context.Context, Path) (Path, error) @@ -191,27 +194,90 @@ type KeyAPI interface { Remove(ctx context.Context, name string) (Path, error) } -// type ObjectAPI interface { -// New() (cid.Cid, Object) -// Get(string) (Object, error) -// Links(string) ([]*Link, error) -// Data(string) (Reader, error) -// Stat(string) (ObjectStat, error) -// Put(Object) (cid.Cid, error) -// SetData(string, Reader) (cid.Cid, error) -// AppendData(string, Data) (cid.Cid, error) -// AddLink(string, string, string) (cid.Cid, error) -// RmLink(string, string) (cid.Cid, error) -// } - -// type ObjectStat struct { -// Cid cid.Cid -// NumLinks int -// BlockSize int -// LinksSize int -// DataSize int -// CumulativeSize int -// } +// 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) (Node, error) + + // WithType is an option for New which allows to change the type of created + // dag node. + // + // Supported types: + // * 'empty' - Empty node + // * 'unixfs-dir' - Empty UnixFS directory + WithType(string) options.ObjectNewOption + + // Put imports the data into merkledag + Put(context.Context, io.Reader, ...options.ObjectPutOption) (Path, error) + + // WithInputEnc is an option for Put which specifies the input encoding of the + // data. Default is "json". + // + // Supported encodings: + // * "protobuf" + // * "json" + WithInputEnc(e string) options.ObjectPutOption + + // WithDataType specifies the encoding of data field when using Josn or XML + // input encoding. + // + // Supported types: + // * "text" (default) + // * "base64" + WithDataType(t string) options.ObjectPutOption + + // Get returns the node for the path + Get(context.Context, Path) (Node, error) + + // Data returns reader for data of the node + Data(context.Context, Path) (io.Reader, error) + + // Links returns lint or links the node contains + Links(context.Context, Path) ([]*Link, error) + + // Stat returns information about the node + Stat(context.Context, 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, name string, child Path, opts ...options.ObjectAddLinkOption) (Path, error) + + // WithCreate is an option for AddLink which specifies whether create required + // directories for the child + WithCreate(create bool) options.ObjectAddLinkOption + + // RmLink removes a link from the node + RmLink(ctx context.Context, base Path, link string) (Path, error) + + // AppendData appends data to the node + AppendData(context.Context, Path, io.Reader) (Path, error) + + // SetData sets the data contained in the node + SetData(context.Context, Path, io.Reader) (Path, error) +} + +// 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 +} var ErrIsDir = errors.New("object is a directory") var ErrOffline = errors.New("can't resolve, ipfs node is offline") diff --git a/core/coreapi/interface/options/object.go b/core/coreapi/interface/options/object.go new file mode 100644 index 00000000000..9c8c9a9ddd1 --- /dev/null +++ b/core/coreapi/interface/options/object.go @@ -0,0 +1,91 @@ +package options + +type ObjectNewSettings struct { + Type string +} + +type ObjectPutSettings struct { + InputEnc string + DataType string +} + +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", + } + + 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 ObjectOptions struct{} + +func (api *ObjectOptions) WithType(t string) ObjectNewOption { + return func(settings *ObjectNewSettings) error { + settings.Type = t + return nil + } +} + +func (api *ObjectOptions) WithInputEnc(e string) ObjectPutOption { + return func(settings *ObjectPutSettings) error { + settings.InputEnc = e + return nil + } +} + +func (api *ObjectOptions) WithDataType(t string) ObjectPutOption { + return func(settings *ObjectPutSettings) error { + settings.DataType = t + return nil + } +} + +func (api *ObjectOptions) WithCreate(create bool) ObjectAddLinkOption { + return func(settings *ObjectAddLinkSettings) error { + settings.Create = create + return nil + } +} diff --git a/core/coreapi/object.go b/core/coreapi/object.go new file mode 100644 index 00000000000..772f90bf291 --- /dev/null +++ b/core/coreapi/object.go @@ -0,0 +1,325 @@ +package coreapi + +import ( + "bytes" + "context" + "io" + "io/ioutil" + + caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" + dagutils "github.com/ipfs/go-ipfs/merkledag/utils" + + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + dag "github.com/ipfs/go-ipfs/merkledag" + ft "github.com/ipfs/go-ipfs/unixfs" + + "encoding/base64" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + node "gx/ipfs/QmNwUEK7QbwSqyKBu3mMtToo8SUc6wQJ7gdZq4gGGJqfnf/go-ipld-format" + cid "gx/ipfs/QmeSrf6pzut73u6zLQkRFQ3ygt3k6XFT2kjdYP8Tnkwwyg/go-cid" +) + +const inputLimit = 2 << 20 + +type ObjectAPI struct { + *CoreAPI + *caopts.ObjectOptions +} + +type Link struct { + Name, Hash string + Size uint64 +} + +type Node struct { + Links []Link + Data string +} + +func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) (coreiface.Node, error) { + options, err := caopts.ObjectNewOptions(opts...) + if err != nil { + return nil, err + } + + var n node.Node + switch options.Type { + case "empty": + n = new(dag.ProtoNode) + case "unixfs-dir": + n = ft.EmptyDirNode() + } + + _, err = api.node.DAG.Add(n) + if err != nil { + return nil, err + } + return n, nil +} + +func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.ObjectPutOption) (coreiface.Path, error) { + options, err := caopts.ObjectPutOptions(opts...) + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(io.LimitReader(src, inputLimit+10)) + if err != nil { + return nil, err + } + + var dagnode *dag.ProtoNode + switch options.InputEnc { + case "json": + node := new(Node) + err = json.Unmarshal(data, node) + if err != nil { + return nil, err + } + + // check that we have data in the Node to add + // otherwise we will add the empty object without raising an error + if nodeEmpty(node) { + return nil, errors.New("no data or links in this node") + } + + dagnode, err = deserializeNode(node, options.DataType) + if err != nil { + return nil, err + } + + case "protobuf": + dagnode, err = dag.DecodeProtobuf(data) + + case "xml": + node := new(Node) + err = xml.Unmarshal(data, node) + if err != nil { + return nil, err + } + + // check that we have data in the Node to add + // otherwise we will add the empty object without raising an error + if nodeEmpty(node) { + return nil, errors.New("no data or links in this node") + } + + dagnode, err = deserializeNode(node, options.DataType) + if err != nil { + return nil, err + } + + default: + return nil, errors.New("unknown object encoding") + } + + if err != nil { + return nil, err + } + + _, err = api.node.DAG.Add(dagnode) + if err != nil { + return nil, err + } + + return ParseCid(dagnode.Cid()), nil +} + +func (api *ObjectAPI) Get(ctx context.Context, path coreiface.Path) (coreiface.Node, error) { + return api.core().ResolveNode(ctx, path) +} + +func (api *ObjectAPI) Data(ctx context.Context, path coreiface.Path) (io.Reader, error) { + nd, err := api.core().ResolveNode(ctx, path) + if err != nil { + return nil, err + } + + pbnd, ok := nd.(*dag.ProtoNode) + if !ok { + return nil, dag.ErrNotProtobuf + } + + return bytes.NewReader(pbnd.Data()), nil +} + +func (api *ObjectAPI) Links(ctx context.Context, path coreiface.Path) ([]*coreiface.Link, error) { + nd, err := api.core().ResolveNode(ctx, path) + if err != nil { + return nil, err + } + + links := nd.Links() + out := make([]*coreiface.Link, len(links)) + for n, l := range links { + out[n] = (*coreiface.Link)(l) + } + + return out, nil +} + +func (api *ObjectAPI) Stat(ctx context.Context, path coreiface.Path) (*coreiface.ObjectStat, error) { + nd, err := api.core().ResolveNode(ctx, path) + if err != nil { + return nil, err + } + + stat, err := nd.Stat() + if err != nil { + return nil, err + } + + out := &coreiface.ObjectStat{ + Cid: nd.Cid(), + NumLinks: stat.NumLinks, + BlockSize: stat.BlockSize, + LinksSize: stat.LinksSize, + DataSize: stat.DataSize, + CumulativeSize: stat.CumulativeSize, + } + + return out, nil +} + +func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.Path, error) { + options, err := caopts.ObjectAddLinkOptions(opts...) + if err != nil { + return nil, err + } + + baseNd, err := api.core().ResolveNode(ctx, base) + if err != nil { + return nil, err + } + + childNd, err := api.core().ResolveNode(ctx, child) + if err != nil { + return nil, err + } + + basePb, ok := baseNd.(*dag.ProtoNode) + if !ok { + return nil, dag.ErrNotProtobuf + } + + var createfunc func() *dag.ProtoNode + if options.Create { + createfunc = ft.EmptyDirNode + } + + e := dagutils.NewDagEditor(basePb, api.node.DAG) + + err = e.InsertNodeAtPath(ctx, name, childNd, createfunc) + if err != nil { + return nil, err + } + + nnode, err := e.Finalize(api.node.DAG) + if err != nil { + return nil, err + } + + return ParseCid(nnode.Cid()), nil +} + +func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.Path, error) { + baseNd, err := api.core().ResolveNode(ctx, base) + if err != nil { + return nil, err + } + + basePb, ok := baseNd.(*dag.ProtoNode) + if !ok { + return nil, dag.ErrNotProtobuf + } + + e := dagutils.NewDagEditor(basePb, api.node.DAG) + + err = e.RmLink(ctx, link) + if err != nil { + return nil, err + } + + nnode, err := e.Finalize(api.node.DAG) + if err != nil { + return nil, err + } + + return ParseCid(nnode.Cid()), nil +} + +func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) { + return api.patchData(ctx, path, r, true) +} + +func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) { + return api.patchData(ctx, path, r, false) +} + +func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.Path, error) { + nd, err := api.core().ResolveNode(ctx, path) + if err != nil { + return nil, err + } + + pbnd, ok := nd.(*dag.ProtoNode) + if !ok { + return nil, dag.ErrNotProtobuf + } + + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + if appendData { + data = append(pbnd.Data(), data...) + } + pbnd.SetData(data) + + _, err = api.node.DAG.Add(pbnd) + if err != nil { + return nil, err + } + + return ParseCid(pbnd.Cid()), nil +} + +func (api *ObjectAPI) core() coreiface.CoreAPI { + return api.CoreAPI +} + +func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) { + dagnode := new(dag.ProtoNode) + switch dataFieldEncoding { + case "text": + dagnode.SetData([]byte(nd.Data)) + case "base64": + data, _ := base64.StdEncoding.DecodeString(nd.Data) + dagnode.SetData(data) + default: + return nil, fmt.Errorf("Unkown data field encoding") + } + + dagnode.SetLinks(make([]*node.Link, len(nd.Links))) + for i, link := range nd.Links { + c, err := cid.Decode(link.Hash) + if err != nil { + return nil, err + } + dagnode.Links()[i] = &node.Link{ + Name: link.Name, + Size: link.Size, + Cid: c, + } + } + + return dagnode, nil +} + +func nodeEmpty(node *Node) bool { + return node.Data == "" && len(node.Links) == 0 +} diff --git a/core/coreapi/object_test.go b/core/coreapi/object_test.go new file mode 100644 index 00000000000..1ff90d32de6 --- /dev/null +++ b/core/coreapi/object_test.go @@ -0,0 +1,385 @@ +package coreapi_test + +import ( + "bytes" + "context" + "encoding/hex" + "io/ioutil" + "strings" + "testing" +) + +func TestNew(t *testing.T) { + ctx := context.Background() + _, api, err := 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, api.Object().WithType("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 TestObjectPut(t *testing.T) { + ctx := context.Background() + _, api, err := 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"}`), api.Object().WithDataType("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), api.Object().WithInputEnc("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 TestObjectGet(t *testing.T) { + ctx := context.Background() + _, api, err := 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 TestObjectData(t *testing.T) { + ctx := context.Background() + _, api, err := 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 := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err) + } + + if string(data) != "foo" { + t.Fatal("got non-matching data") + } +} + +func TestObjectLinks(t *testing.T) { + ctx := context.Background() + _, api, err := 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 TestObjectStat(t *testing.T) { + ctx := context.Background() + _, api, err := 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 TestObjectAddLink(t *testing.T) { + ctx := context.Background() + _, api, err := 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 TestObjectAddLinkCreate(t *testing.T) { + ctx := context.Background() + _, api, err := 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/d", p2) + if err == nil { + t.Fatal("expected an error") + } + if err.Error() != "no link by that name" { + t.Fatal("unexpected error: %s", err.Error()) + } + + p3, err = api.Object().AddLink(ctx, p2, "abc/d", p2, api.Object().WithCreate(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 TestObjectRmLink(t *testing.T) { + ctx := context.Background() + _, api, err := 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 TestObjectAddData(t *testing.T) { + ctx := context.Background() + _, api, err := 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 := ioutil.ReadAll(r) + + if string(data) != "foobar" { + t.Error("unexpected data") + } +} + +func TestObjectSetData(t *testing.T) { + ctx := context.Background() + _, api, err := 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 := ioutil.ReadAll(r) + + if string(data) != "bar" { + t.Error("unexpected data") + } +} diff --git a/core/coredag/dagtransl.go b/core/coredag/dagtransl.go index b20afef942b..903467d33aa 100644 --- a/core/coredag/dagtransl.go +++ b/core/coredag/dagtransl.go @@ -18,9 +18,10 @@ type InputEncParsers map[string]FormatParsers // DefaultInputEncParsers is InputEncParser that is used everywhere var DefaultInputEncParsers = InputEncParsers{ - "json": defaultJSONParsers, - "raw": defaultRawParsers, - "cbor": defaultCborParsers, + "json": defaultJSONParsers, + "raw": defaultRawParsers, + "cbor": defaultCborParsers, + "protobuf": defaultProtobufParsers, } var defaultJSONParsers = FormatParsers{ @@ -46,6 +47,11 @@ var defaultCborParsers = FormatParsers{ "dag-cbor": cborRawParser, } +var defaultProtobufParsers = FormatParsers{ + "protobuf": dagpbRawParser, + "dag-pb": dagpbRawParser, +} + // ParseInputs uses DefaultInputEncParsers to parse io.Reader described by // input encoding and format to an instance of ipld Node func ParseInputs(ienc, format string, r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {