Skip to content

Commit

Permalink
add loader to NewPathFromDocument function. (#71)
Browse files Browse the repository at this point in the history
* add loader to NewPathFromDocument function.
create new function NewPathFromDocumentWithLoader

* fix linter

* Add option to set document loader globaly

* Use the default document loader if Merklizer does not has any options to setup its own.

* Test merklization with default document loader

* fix irregular-failure of test

---------

Co-authored-by: Oleg Lomaka <[email protected]>
  • Loading branch information
vmidyllic and olomix authored Jun 20, 2023
1 parent 5c7d528 commit 481d168
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 71 deletions.
158 changes: 101 additions & 57 deletions merklize/merklize.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (
)

var (
defaultHasher Hasher = PoseidonHasher{}
numRE = regexp.MustCompile(`^\d+$`)
defaultHasher Hasher = PoseidonHasher{}
defaultDocumentLoader = NewDocumentLoader(nil, "")
numRE = regexp.MustCompile(`^\d+$`)
)

var (
Expand All @@ -43,10 +44,16 @@ func SetHasher(h Hasher) {
defaultHasher = h
}

// SetDocumentLoader changes default DocumentLoader
func SetDocumentLoader(docLoader ld.DocumentLoader) {
defaultDocumentLoader = docLoader
}

// Options type allows to change hashing algorithm and create Path and RDFEntry
// instances with hasher different from default one.
type Options struct {
Hasher Hasher
Hasher Hasher
DocumentLoader ld.DocumentLoader
}

func (o Options) getHasher() Hasher {
Expand All @@ -56,6 +63,23 @@ func (o Options) getHasher() Hasher {
return defaultHasher
}

func (o Options) getDocumentLoader() ld.DocumentLoader {
if o.DocumentLoader != nil {
return o.DocumentLoader
}
return defaultDocumentLoader
}

func (o Options) getJSONLdOptions() *ld.JsonLdOptions {
docLoader := o.getDocumentLoader()
if docLoader == nil {
return nil
}
return &ld.JsonLdOptions{
DocumentLoader: docLoader,
}
}

func (o Options) NewPath(parts ...interface{}) (Path, error) {
p := Path{hasher: o.getHasher()}
err := p.Append(parts)
Expand All @@ -64,7 +88,7 @@ func (o Options) NewPath(parts ...interface{}) (Path, error) {

func (o Options) PathFromContext(ctxBytes []byte, path string) (Path, error) {
out := Path{hasher: o.getHasher()}
err := out.pathFromContext(ctxBytes, path)
err := out.pathFromContext(ctxBytes, path, o.getJSONLdOptions())
return out, err
}

Expand All @@ -89,6 +113,28 @@ func (o Options) NewRDFEntry(key Path, value interface{}) (RDFEntry, error) {
return e, nil
}

func (o Options) NewPathFromDocument(docBytes []byte,
path string) (Path, error) {

var docObj map[string]interface{}
err := json.Unmarshal(docBytes, &docObj)
if err != nil {
return Path{}, err
}

pathParts := strings.Split(path, ".")
if len(pathParts) == 0 {
return Path{}, errors.New("path is empty")
}

pathPartsI, err := o.pathFromDocument(nil, docObj, pathParts, false)
if err != nil {
return Path{}, err
}

return Path{parts: pathPartsI, hasher: o.getHasher()}, nil
}

type Path struct {
parts []interface{} // string or int types
hasher Hasher
Expand All @@ -113,29 +159,14 @@ func NewPath(parts ...interface{}) (Path, error) {
// NewPathFromContext parses context and do its best to generate full Path
// from shortcut line field1.field2.field3...
func NewPathFromContext(ctxBytes []byte, path string) (Path, error) {
var out = Path{hasher: defaultHasher}
err := out.pathFromContext(ctxBytes, path)
defaultOpts := Options{}
var out = Path{hasher: defaultOpts.getHasher()}
err := out.pathFromContext(ctxBytes, path, defaultOpts.getJSONLdOptions())
return out, err
}

func NewPathFromDocument(docBytes []byte, path string) (Path, error) {
var docObj map[string]interface{}
err := json.Unmarshal(docBytes, &docObj)
if err != nil {
return Path{}, err
}

pathParts := strings.Split(path, ".")
if len(pathParts) == 0 {
return Path{}, errors.New("path is empty")
}

pathPartsI, err := pathFromDocument(nil, docObj, pathParts, false)
if err != nil {
return Path{}, err
}

return Path{parts: pathPartsI, hasher: defaultHasher}, nil
return Options{}.NewPathFromDocument(docBytes, path)
}

// NewFieldPathFromContext resolves field path without type path prefix
Expand Down Expand Up @@ -164,14 +195,17 @@ func NewFieldPathFromContext(ctxBytes []byte, ctxType, fieldPath string) (Path,
}

// TypeIDFromContext returns @id attribute for type from JSON-LD context
func TypeIDFromContext(ctxBytes []byte, typeName string) (string, error) {
func (o Options) TypeIDFromContext(ctxBytes []byte,
typeName string) (string, error) {

var ctxObj map[string]interface{}
err := json.Unmarshal(ctxBytes, &ctxObj)
if err != nil {
return "", err
}

ldCtx, err := ld.NewContext(nil, nil).Parse(ctxObj["@context"])
ldCtx, err := ld.NewContext(nil, o.getJSONLdOptions()).
Parse(ctxObj["@context"])
if err != nil {
return "", err
}
Expand All @@ -198,15 +232,21 @@ func TypeIDFromContext(ctxBytes []byte, typeName string) (string, error) {
return typeIDStr, nil
}

// TypeIDFromContext returns @id attribute for type from JSON-LD context
func TypeIDFromContext(ctxBytes []byte, typeName string) (string, error) {
return Options{}.TypeIDFromContext(ctxBytes, typeName)
}

// TypeFromContext returns type of field from context by path.
func TypeFromContext(ctxBytes []byte, path string) (string, error) {
func (o Options) TypeFromContext(ctxBytes []byte, path string) (string, error) {
var ctxObj map[string]interface{}
err := json.Unmarshal(ctxBytes, &ctxObj)
if err != nil {
return "", err
}

ldCtx, err := ld.NewContext(nil, nil).Parse(ctxObj["@context"])
ldCtx, err := ld.NewContext(nil, o.getJSONLdOptions()).
Parse(ctxObj["@context"])
if err != nil {
return "", err
}
Expand Down Expand Up @@ -236,15 +276,21 @@ func TypeFromContext(ctxBytes []byte, path string) (string, error) {
return ldCtx.GetTypeMapping(parts[len(parts)-1]), nil
}

func (p *Path) pathFromContext(ctxBytes []byte, path string) error {
// TypeFromContext returns type of field from context by path.
func TypeFromContext(ctxBytes []byte, path string) (string, error) {
return Options{}.TypeFromContext(ctxBytes, path)
}

func (p *Path) pathFromContext(ctxBytes []byte, path string,
jsonLdOptions *ld.JsonLdOptions) error {

var ctxObj map[string]interface{}
err := json.Unmarshal(ctxBytes, &ctxObj)
if err != nil {
return err
}

ldCtx, err := ld.NewContext(nil, nil).Parse(ctxObj["@context"])
ldCtx, err := ld.NewContext(nil, jsonLdOptions).Parse(ctxObj["@context"])
if err != nil {
return err
}
Expand Down Expand Up @@ -289,7 +335,7 @@ func (p *Path) pathFromContext(ctxBytes []byte, path string) error {
// Create path JSON-LD document.
// If acceptArray is true, the previous element was index, and we accept an
// array
func pathFromDocument(ldCtx *ld.Context, docObj interface{},
func (o Options) pathFromDocument(ldCtx *ld.Context, docObj interface{},
pathParts []string, acceptArray bool) ([]interface{}, error) {

if len(pathParts) == 0 {
Expand All @@ -305,7 +351,7 @@ func pathFromDocument(ldCtx *ld.Context, docObj interface{},
return nil, err
}

moreParts, err := pathFromDocument(ldCtx, docObj, newPathParts, true)
moreParts, err := o.pathFromDocument(ldCtx, docObj, newPathParts, true)
if err != nil {
return nil, err
}
Expand All @@ -325,7 +371,7 @@ func pathFromDocument(ldCtx *ld.Context, docObj interface{},
return nil, errors.New("unexpected array element")
}

return pathFromDocument(ldCtx, docObjT[0], pathParts, false)
return o.pathFromDocument(ldCtx, docObjT[0], pathParts, false)
case map[string]interface{}:
// pass
docObjMap = docObjT
Expand All @@ -334,7 +380,7 @@ func pathFromDocument(ldCtx *ld.Context, docObj interface{},
}

if ldCtx == nil {
ldCtx = ld.NewContext(nil, nil)
ldCtx = ld.NewContext(nil, o.getJSONLdOptions())
}

var err error
Expand Down Expand Up @@ -408,7 +454,7 @@ func pathFromDocument(ldCtx *ld.Context, docObj interface{},
}
}

moreParts, err := pathFromDocument(ldCtx, docObjMap[term], newPathParts,
moreParts, err := o.pathFromDocument(ldCtx, docObjMap[term], newPathParts,
true)
if err != nil {
return nil, err
Expand Down Expand Up @@ -589,21 +635,7 @@ func (v *value) AsBool() (bool, error) {
}

func NewRDFEntry(key Path, value any) (RDFEntry, error) {
e := RDFEntry{key: key}
if len(key.parts) == 0 {
return e, errors.New("key length is zero")
}

switch v := value.(type) {
case int:
e.value = int64(v)
case int64, string, bool, time.Time:
e.value = value
default:
return e, fmt.Errorf("incorrect value type: %T", value)
}

return e, nil
return Options{}.NewRDFEntry(key, value)
}

func (e RDFEntry) KeyMtEntry() (*big.Int, error) {
Expand Down Expand Up @@ -1525,12 +1557,7 @@ func MerklizeJSONLD(ctx context.Context, in io.Reader,
options := ld.NewJsonLdOptions("")
options.Algorithm = ld.AlgorithmURDNA2015
options.SafeMode = mz.safeMode

if mz.documentLoader == nil {
options.DocumentLoader = NewDocumentLoader(mz.ipfsCli, mz.ipfsGW)
} else {
options.DocumentLoader = mz.documentLoader
}
options.DocumentLoader = mz.getDocumentLoader()

normDoc, err := proc.Normalize(obj, options)
if err != nil {
Expand Down Expand Up @@ -1579,6 +1606,16 @@ func (m *Merklizer) entry(path Path) (RDFEntry, error) {
return e, nil
}

func (m *Merklizer) getDocumentLoader() ld.DocumentLoader {
if m.documentLoader != nil {
return m.documentLoader
}
if m.ipfsCli == nil && m.ipfsGW == "" {
return defaultDocumentLoader
}
return NewDocumentLoader(m.ipfsCli, m.ipfsGW)
}

func rvExtractObjField(obj any, field string) (any, error) {
jsObj, isJSONObj := obj.(map[string]any)
if !isJSONObj {
Expand Down Expand Up @@ -1657,11 +1694,18 @@ func (m *Merklizer) JSONLDType(path Path) (string, error) {
}

func (m *Merklizer) ResolveDocPath(path string) (Path, error) {
realPath, err := NewPathFromDocument(m.srcDoc, path)
opts := Options{
Hasher: m.hasher,
DocumentLoader: m.getDocumentLoader(),
}
if opts.Hasher == nil {
opts.Hasher = defaultHasher
}

realPath, err := opts.NewPathFromDocument(m.srcDoc, path)
if err != nil {
return Path{}, err
}
realPath.hasher = m.hasher
return realPath, nil
}

Expand Down
Loading

0 comments on commit 481d168

Please sign in to comment.