Commit 60ab4b24 authored by hannahhoward's avatar hannahhoward

feat(metadata): extract metadata to package

Extract metadata encoding to a package and add a decoding feature
parent b2a44bb4
......@@ -30,6 +30,18 @@ func NewIPLDBridge() IPLDBridge {
return &ipldBridge{}
}
func (rb *ipldBridge) ExtractData(node ipld.Node, buildFn func(SimpleNode) interface{}) (interface{}, error) {
var value interface{}
err := fluent.Recover(func() {
simpleNode := fluent.WrapNode(node)
value = buildFn(simpleNode)
})
if err != nil {
return nil, err
}
return value, nil
}
func (rb *ipldBridge) BuildNode(buildFn func(NodeBuilder) ipld.Node) (ipld.Node, error) {
var node ipld.Node
err := fluent.Recover(func() {
......
......@@ -37,9 +37,20 @@ type ListBuilder = fluent.ListBuilder
// MapBuilder is an alias from ipld fluent, in case it's moved
type MapBuilder = fluent.MapBuilder
// SimpleNode is an alias from ipld fluent, to refer to its non error based
// node struct
type SimpleNode = fluent.Node
// IPLDBridge is an interface for making calls to IPLD, which can be
// replaced with alternative implementations
type IPLDBridge interface {
// ExtractData provides an efficient mechanism for assembling nodes w/ fluent
// interface
ExtractData(ipld.Node, func(SimpleNode) interface{}) (interface{}, error)
// BuildNode provides an efficient mechanism for assembling nodes w/ fluent
// interface
BuildNode(func(NodeBuilder) ipld.Node) (ipld.Node, error)
// ValidateSelectorSpec verifies if a node matches the selector spec.
......
package metadata
import (
"github.com/ipfs/go-graphsync/ipldbridge"
"github.com/ipld/go-ipld-prime"
)
// Metadata is information about metadata contained in a response, which can be
// serialized back and forth to bytes
type Metadata map[ipld.Link]bool
// DecodeMetadata assembles metadata from a raw byte array, first deserializing
// as a node and then assembling into a metadata struct.
func DecodeMetadata(data []byte, ipldBridge ipldbridge.IPLDBridge) (Metadata, error) {
node, err := ipldBridge.DecodeNode(data)
if err != nil {
return nil, err
}
decodedData, err := ipldBridge.ExtractData(node, func(simpleNode ipldbridge.SimpleNode) interface{} {
iterator := simpleNode.ListIterator()
metadata := make(Metadata)
for !iterator.Done() {
_, item := iterator.Next()
link := item.TraverseField("link").AsLink()
blockPresent := item.TraverseField("blockPresent").AsBool()
metadata[link] = blockPresent
}
return metadata
})
if err != nil {
return nil, err
}
return decodedData.(Metadata), err
}
// EncodeMetadata encodes metadata to an IPLD node then serializes to raw bytes
func EncodeMetadata(entries Metadata, ipldBridge ipldbridge.IPLDBridge) ([]byte, error) {
node, err := ipldBridge.BuildNode(func(nb ipldbridge.NodeBuilder) ipld.Node {
return nb.CreateList(func(lb ipldbridge.ListBuilder, nb ipldbridge.NodeBuilder) {
for link, blockPresent := range entries {
lb.Append(
nb.CreateMap(func(mb ipldbridge.MapBuilder, knb ipldbridge.NodeBuilder, vnb ipldbridge.NodeBuilder) {
mb.Insert(knb.CreateString("link"), vnb.CreateLink(link))
mb.Insert(knb.CreateString("blockPresent"), vnb.CreateBool(blockPresent))
}),
)
}
})
})
if err != nil {
return nil, err
}
return ipldBridge.EncodeNode(node)
}
package metadata
import (
"math/rand"
"reflect"
"testing"
"github.com/ipfs/go-graphsync/testbridge"
"github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipfs/go-graphsync/testutil"
)
func TestDecodeEncodeMetadata(t *testing.T) {
cids := testutil.GenerateCids(10)
initialMetadata := make(Metadata)
for _, k := range cids {
link := cidlink.Link{Cid: k}
blockPresent := rand.Int31()%2 == 0
initialMetadata[link] = blockPresent
}
bridge := testbridge.NewMockIPLDBridge()
encoded, err := EncodeMetadata(initialMetadata, bridge)
if err != nil {
t.Fatal("Error encoding")
}
decodedMetadata, err := DecodeMetadata(encoded, bridge)
if err != nil {
t.Fatal("Error decoding")
}
if !reflect.DeepEqual(initialMetadata, decodedMetadata) {
t.Fatal("Metadata changed during encoding and decoding")
}
}
......@@ -4,6 +4,7 @@ import (
"github.com/ipfs/go-block-format"
"github.com/ipfs/go-graphsync/ipldbridge"
gsmsg "github.com/ipfs/go-graphsync/message"
"github.com/ipfs/go-graphsync/metadata"
"github.com/ipld/go-ipld-prime"
)
......@@ -13,14 +14,14 @@ import (
type ResponseBuilder struct {
outgoingBlocks []blocks.Block
completedResponses map[gsmsg.GraphSyncRequestID]gsmsg.GraphSyncResponseStatusCode
outgoingResponses map[gsmsg.GraphSyncRequestID]map[ipld.Link]bool
outgoingResponses map[gsmsg.GraphSyncRequestID]metadata.Metadata
}
// New generates a new ResponseBuilder.
func New() *ResponseBuilder {
return &ResponseBuilder{
completedResponses: make(map[gsmsg.GraphSyncRequestID]gsmsg.GraphSyncResponseStatusCode),
outgoingResponses: make(map[gsmsg.GraphSyncRequestID]map[ipld.Link]bool),
outgoingResponses: make(map[gsmsg.GraphSyncRequestID]metadata.Metadata),
}
}
......@@ -61,7 +62,7 @@ func (rb *ResponseBuilder) Empty() bool {
func (rb *ResponseBuilder) Build(ipldBridge ipldbridge.IPLDBridge) ([]gsmsg.GraphSyncResponse, []blocks.Block, error) {
responses := make([]gsmsg.GraphSyncResponse, 0, len(rb.outgoingResponses))
for requestID, linkMap := range rb.outgoingResponses {
extra, err := makeEncodedData(linkMap, ipldBridge)
extra, err := metadata.EncodeMetadata(linkMap, ipldBridge)
if err != nil {
return nil, nil, err
}
......@@ -71,25 +72,6 @@ func (rb *ResponseBuilder) Build(ipldBridge ipldbridge.IPLDBridge) ([]gsmsg.Grap
return responses, rb.outgoingBlocks, nil
}
func makeEncodedData(entries map[ipld.Link]bool, ipldBridge ipldbridge.IPLDBridge) ([]byte, error) {
node, err := ipldBridge.BuildNode(func(nb ipldbridge.NodeBuilder) ipld.Node {
return nb.CreateList(func(lb ipldbridge.ListBuilder, nb ipldbridge.NodeBuilder) {
for link, blockPresent := range entries {
lb.Append(
nb.CreateMap(func(mb ipldbridge.MapBuilder, knb ipldbridge.NodeBuilder, vnb ipldbridge.NodeBuilder) {
mb.Insert(knb.CreateString("link"), vnb.CreateLink(link))
mb.Insert(knb.CreateString("blockPresent"), vnb.CreateBool(blockPresent))
}),
)
}
})
})
if err != nil {
return nil, err
}
return ipldBridge.EncodeNode(node)
}
func responseCode(status gsmsg.GraphSyncResponseStatusCode, isComplete bool) gsmsg.GraphSyncResponseStatusCode {
if !isComplete {
return gsmsg.PartialResponse
......
......@@ -3,11 +3,11 @@ package responsebuilder
import (
"fmt"
"math/rand"
"reflect"
"testing"
"github.com/ipld/go-ipld-prime/fluent"
gsmsg "github.com/ipfs/go-graphsync/message"
"github.com/ipfs/go-graphsync/metadata"
"github.com/ipfs/go-graphsync/testbridge"
"github.com/ipfs/go-graphsync/testutil"
"github.com/ipld/go-ipld-prime"
......@@ -62,40 +62,38 @@ func TestMessageBuilding(t *testing.T) {
t.Fatal("did not generate completed partial response")
}
response1Metadata, err := ipldBridge.DecodeNode(response1.Extra())
if err != nil {
t.Fatal("unable to read metadata from response")
}
analyzeMetadata(t, response1Metadata, map[ipld.Link]bool{
response1Metadata, err := metadata.DecodeMetadata(response1.Extra(), ipldBridge)
if err != nil || !reflect.DeepEqual(response1Metadata, metadata.Metadata{
links[0]: true,
links[1]: false,
links[2]: true,
})
}) {
t.Fatal("Metadata did not match expected")
}
response2, err := findResponseForRequestID(responses, requestID2)
if err != nil || response2.Status() != gsmsg.RequestCompletedFull {
t.Fatal("did not generate completed partial response")
}
response2Metadata, err := ipldBridge.DecodeNode(response2.Extra())
if err != nil {
t.Fatal("unable to read metadata from response")
}
analyzeMetadata(t, response2Metadata, map[ipld.Link]bool{
response2Metadata, err := metadata.DecodeMetadata(response2.Extra(), ipldBridge)
if err != nil || !reflect.DeepEqual(response2Metadata, metadata.Metadata{
links[1]: true,
links[2]: true,
})
}) {
t.Fatal("Metadata did not match expected")
}
response3, err := findResponseForRequestID(responses, requestID3)
if err != nil || response3.Status() != gsmsg.PartialResponse {
t.Fatal("did not generate completed partial response")
}
response3Metadata, err := ipldBridge.DecodeNode(response3.Extra())
if err != nil {
t.Fatal("unable to read metadata from response")
}
analyzeMetadata(t, response3Metadata, map[ipld.Link]bool{
response3Metadata, err := metadata.DecodeMetadata(response3.Extra(), ipldBridge)
if err != nil || !reflect.DeepEqual(response3Metadata, metadata.Metadata{
links[0]: true,
links[1]: true,
})
}) {
t.Fatal("Metadata did not match expected")
}
response4, err := findResponseForRequestID(responses, requestID4)
if err != nil || response4.Status() != gsmsg.RequestCompletedFull {
......@@ -121,24 +119,3 @@ func findResponseForRequestID(responses []gsmsg.GraphSyncResponse, requestID gsm
}
return gsmsg.GraphSyncResponse{}, fmt.Errorf("Response Not Found")
}
func analyzeMetadata(t *testing.T, metadata ipld.Node, expectedMetadata map[ipld.Link]bool) {
if metadata.Length() != len(expectedMetadata) {
t.Fatal("Wrong amount of metadata on first response")
}
err := fluent.Recover(func() {
safeMetadata := fluent.WrapNode(metadata)
for i := 0; i < len(expectedMetadata); i++ {
metadatum := safeMetadata.TraverseIndex(i)
link := metadatum.TraverseField("link").AsLink()
blockPresent := metadatum.TraverseField("blockPresent").AsBool()
expectedBlockPresent, ok := expectedMetadata[link]
if !ok || expectedBlockPresent != blockPresent {
t.Fatal("Metadata did not match expected")
}
}
})
if err != nil {
t.Fatal(err)
}
}
......@@ -24,6 +24,21 @@ type mockIPLDBridge struct {
func NewMockIPLDBridge() ipldbridge.IPLDBridge {
return &mockIPLDBridge{}
}
func (mb *mockIPLDBridge) ExtractData(
node ipld.Node,
buildFn func(ipldbridge.SimpleNode) interface{}) (interface{}, error) {
var value interface{}
err := fluent.Recover(func() {
simpleNode := fluent.WrapNode(node)
value = buildFn(simpleNode)
})
if err != nil {
return nil, err
}
return value, nil
}
func (mb *mockIPLDBridge) BuildNode(buildFn func(ipldbridge.NodeBuilder) ipld.Node) (ipld.Node, error) {
var node ipld.Node
err := fluent.Recover(func() {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment