Skip to content
This repository has been archived by the owner on Aug 9, 2018. It is now read-only.

Commit

Permalink
Merge pull request #7 from mildred/mildred-ipld
Browse files Browse the repository at this point in the history
Implement stripping LD directives from IPLD Node, and transforming IPLD to JSON-LD
  • Loading branch information
jbenet committed Oct 2, 2015
2 parents b9f1655 + b5a26b3 commit 376a7ed
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 47 deletions.
23 changes: 9 additions & 14 deletions ipld.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const (
CtxKey = "@context" // the JSON-LD style context

CodecKey = "@codec" // used to determine which multicodec to use
LinkType = "mlink" // a merkle-link type.
HashKey = "hash" // multihash in an mlink
LinkKey = "mlink" // key for merkle-links
)

// Node is an IPLD node. effectively, it is equivalent to a JSON-LD object.
Expand Down Expand Up @@ -98,16 +97,16 @@ func (l Link) Type() string {
return s
}

// HashStr returns the string value of l["hash"],
// HashStr returns the string value of l["mlink"],
// which is the value we use to store hashes.
func (l Link) HashStr() string {
s, _ := l[HashKey].(string)
func (l Link) LinkStr() string {
s, _ := l[LinkKey].(string)
return s
}

// Hash returns the multihash value of the link.
func (l Link) Hash() (mh.Multihash, error) {
s := l.HashStr()
s := l.LinkStr()
if s == "" {
return nil, errors.New("no hash in link")
}
Expand Down Expand Up @@ -162,25 +161,21 @@ func Links(n Node) map[string]Link {
// checks whether a value is a link. for now we assume that all links
// follow:
//
// { "@type" : "mlink", "hash": "<multihash>" }
// { "mlink": "<multihash>" }
func IsLink(v interface{}) bool {
vn, ok := v.(Node)
if !ok {
return false
}

ts, ok := vn[TypeKey].(string)
if !ok {
return false
}

return ts == LinkType
_, ok = vn[LinkKey].(string)
return ok;
}

// returns the link value of an object. for now we assume that all links
// follow:
//
// { "@type" : "mlink", "hash": "<multihash>" }
// { "mlink": "<multihash>" }
func LinkCast(v interface{}) (l Link, ok bool) {
if !IsLink(v) {
return
Expand Down
70 changes: 38 additions & 32 deletions ipld_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type TC struct {
src Node
links map[string]Link
links map[string]string
typ string
ctx interface{}
}
Expand All @@ -25,66 +25,72 @@ func mmh(b58 string) mh.Multihash {

func init() {
testCases = append(testCases, TC{
Node{
src: Node{
"foo": "bar",
"bar": []int{1, 2, 3},
"baz": Node{
"@type": "mlink",
"hash": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"test": Node {
// This is not a link because mlink is not a string but a Node
"mlink": Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
},
},
map[string]Link{
"baz": {"@type": "mlink", "hash": ("QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo")},
links: map[string]string{
"baz": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"test/mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"",
nil,
})

testCases = append(testCases, TC{
Node{
"foo": "bar",
"@type": "commit",
typ: "",
ctx: nil,
}, TC{
src: Node{
"@context": "/ipfs/QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo/mdag",
"baz": Node{
"@type": "mlink",
"hash": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"bazz": Node{
"@type": "mlink",
"hash": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"bar": Node{
"@type": "mlinkoo",
"hash": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPb",
},
"bar2": Node{
"foo": Node{
"@type": "mlink",
"hash": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"@bar": Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPa",
},
"\\@foo": Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPa",
},
},
},
map[string]Link{
"baz": {"@type": "mlink", "hash": ("QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo")},
"bazz": {"@type": "mlink", "hash": ("QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo")},
"bar2/foo": {"@type": "mlink", "hash": ("QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo")},
links: map[string]string{
"baz": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"bazz": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
"bar": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPb",
"bar2/@foo": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPa",
},
"",
"/ipfs/QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo/mdag",
typ: "",
ctx: "/ipfs/QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo/mdag",
})
}

func TestParsing(t *testing.T) {
for tci, tc := range testCases {
t.Logf("===== Test case #%d =====", tci)
doc := tc.src

// check links
links := doc.Links()
t.Log(links)
t.Logf("links: %#v", links)
if len(links) != len(tc.links) {
t.Errorf("links do not match, not the same number of links, expected %d, got %d", len(tc.links), len(links))
}
for k, l1 := range tc.links {
l2 := links[k]
if !l1.Equal(l2) {
t.Errorf("links do not match. %d/%s %s != %s", tci, k, l1, l2)
if l1 != l2["mlink"] {
t.Errorf("links do not match. %d/%#v %#v != %#v[mlink]", tci, k, l1, l2)
}
}
}
Expand Down
116 changes: 116 additions & 0 deletions jsonld/ipld_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package jsonld

import (
"testing"
"reflect"

ipld "github.com/ipfs/go-ipld"
)

type TC struct {
src ipld.Node
jsonld ipld.Node
}

var testCases []TC

func init() {
testCases = append(testCases, TC{
src: ipld.Node{
"foo": "bar",
"bar": []int{1, 2, 3},
"baz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
},
jsonld: ipld.Node{
"foo": "bar",
"bar": []int{1, 2, 3},
"baz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
},
}, TC{
src: ipld.Node{
"foo": "bar",
"bar": []int{1, 2, 3},
"@container": "@index",
"@index": "links",
"baz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
},
jsonld: ipld.Node{
"links": ipld.Node{
"foo": "bar",
"bar": []int{1, 2, 3},
"baz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
},
},
}, TC{
src: ipld.Node{
"@attrs": ipld.Node{
"attr": "val",
},
"foo": "bar",
"@index": "files",
"@type": "commit",
"@container": "@index",
"@context": "/ipfs/QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo/mdag",
"baz": ipld.Node{
"foobar": "barfoo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"\\@bazz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"bar/ra\\b": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPb",
},
"bar": ipld.Node{
"@container": "@index",
"foo": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPa",
},
},
},
jsonld: ipld.Node{
"attr": "val",
"@type": "commit",
"@context": "/ipfs/QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo/mdag",
"files": ipld.Node{
"foo": "bar",
"baz": ipld.Node{
"foobar": "barfoo",
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"@bazz": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPo",
},
"bar/ra\\b": ipld.Node{
"mlink": "QmZku7P7KeeHAnwMr6c4HveYfMzmtVinNXzibkiNbfDbPb",
},
"bar": ipld.Node{
},
},
},
})
}

func TestParsing(t *testing.T) {
for tci, tc := range testCases {
t.Logf("===== Test case #%d =====", tci)
doc := tc.src

// check JSON-LD mode
jsonld := ToLinkedDataAll(doc)
if !reflect.DeepEqual(tc.jsonld, jsonld) {
t.Errorf("JSON-LD version mismatch.\nGot: %#v\nExpect: %#v", jsonld, tc.jsonld)
} else {
t.Log("JSON-LD version OK")
}

}
}
75 changes: 75 additions & 0 deletions jsonld/jsonld.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package jsonld

import(
ipld "github.com/ipfs/go-ipld"
)

const DefaultIndexName string = "@index"

func ContainerIndexName(n ipld.Node, defaultval string) string {
var index_name string = defaultval

index_val, ok := n["@index"]
if str, is_string := index_val.(string); ok && is_string {
index_name = str
}

return index_name
}

// Like ToLinkedDataAll but on the root node only, for use in Walk
func ToLinkedData(d ipld.Node) ipld.Node {
attrs, directives, _, index := ipld.ParseNodeIndex(d)
for k, v := range directives {
if k != "@container" {
attrs[k] = v
}
}
if len(index) > 0 {
index_name := ipld.ContainerIndexName(attrs, ipld.DefaultIndexName)
delete(attrs, "@index")
if index_name[0] != '@' {
attrs[index_name] = index
}
}
return attrs
}

// Reorganize the data to be valid JSON-LD. This expand custom IPLD directives
// and unescape keys.
//
// The main processing now is to transform a IPLD data structure like this:
//
// {
// "@container": "@index",
// "@index": "index-name",
// "@attrs": {
// "key": "value",
// },
// "index": { ... }
// }
//
// to:
//
// {
// "key": "value",
// "index-name": {
// "index": { ... }
// }
// }
//
// In that case, it is good practice to define in the context the following
// type (this function cannot change the context):
//
// "index-name": { "@container": "@index" }
//
func ToLinkedDataAll(d ipld.Node) ipld.Node {
res, err := ipld.Transform(d, func(root, curr ipld.Node, path []string, err error) (ipld.Node, error) {
return ToLinkedData(curr), err
})
if err != nil {
panic(err) // should not happen
}
return res
}

Loading

0 comments on commit 376a7ed

Please sign in to comment.