This repository has been archived by the owner on Aug 9, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Node transformation functions and implement conversion to JSON-LD
- Loading branch information
Showing
3 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package ipld | ||
|
||
import ( | ||
"errors" | ||
"strconv" | ||
"path" | ||
) | ||
|
||
// TransformFunc is the type of the function called for each node visited by | ||
// Transform. The root argument is the node from which the Transform began. The | ||
// curr argument is the currently visited node. The path argument is the | ||
// traversal path, from root to curr. | ||
// | ||
// If there was a problem walking to curr, the err argument will describe the | ||
// problem and the function can decide how to handle the error (and Transform | ||
// _will not_ descend into any of the children of curr). | ||
// | ||
// TransformFunc may return a node, in which case the returned node will be used | ||
// for further traversal instead of the curr node. | ||
// | ||
// TransformFunc may return an error. If the error is the special SkipNode | ||
// error, the children of curr are skipped. All other errors halt processing | ||
// early. | ||
type TransformFunc func(root, curr Node, path []string, err error) (Node, error) | ||
|
||
// Transform traverses the given root node and all its children, calling | ||
// TransformFunc with every Node visited, including root. All errors that arise | ||
// while visiting nodes are passed to given TransformFunc. The traversing | ||
// algorithm is the same as the Walk function. | ||
// | ||
// Transform returns a node constructed from the different nodes returned by | ||
// TransformFunc. | ||
func Transform(root Node, transformFn TransformFunc) (Node, error) { | ||
n, err := transform(root, root, nil, transformFn) | ||
if node, ok := n.(Node); ok { | ||
return node, err | ||
} else { | ||
return nil, err | ||
} | ||
} | ||
|
||
// TransformFrom is just like Transform, but starts the Walk at given startFrom | ||
// sub-node. | ||
func TransformFrom(root Node, startFrom []string, transformFn TransformFunc) (interface{}, error) { | ||
start := GetPathCmp(root, startFrom) | ||
if start == nil { | ||
return nil, errors.New("no descendant at " + path.Join(startFrom...)) | ||
} | ||
return transform(root, start, startFrom, transformFn) | ||
} | ||
|
||
// transform is used to implement Transform | ||
func transform(root Node, curr interface{}, npath []string, transformFunc TransformFunc) (interface{}, error) { | ||
|
||
if nc, ok := curr.(Node); ok { // it's a node! | ||
// first, call user's WalkFunc. | ||
newnode, err := transformFunc(root, nc, npath, nil) | ||
res := Node{} | ||
if err == SkipNode { | ||
return newnode, nil // ok, let's skip this one. | ||
} else if err != nil { | ||
return nil, err // something bad happened, return early. | ||
} else if newnode != nil { | ||
nc = newnode | ||
} | ||
|
||
// then recurse. | ||
for k, v := range nc { | ||
n, err := transform(root, v, append(npath, k), transformFunc) | ||
if err != nil { | ||
return nil, err | ||
} else if n != nil { | ||
res[k] = n | ||
} | ||
} | ||
|
||
return res, nil | ||
|
||
} else if sc, ok := curr.([]interface{}); ok { // it's a slice! | ||
res := []interface{}{} | ||
for i, v := range sc { | ||
k := strconv.Itoa(i) | ||
n, err := transform(root, v, append(npath, k), transformFunc) | ||
if err != nil { | ||
return nil, err | ||
} else if n != nil { | ||
res = append(res, n) | ||
} | ||
} | ||
return res, nil | ||
|
||
} else { // it's just data. | ||
// ignore it. | ||
} | ||
return curr, nil | ||
} | ||
|