github.com/storacha/go-ucanto@v0.7.2/core/schema/link.go (about)

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
     8  	"github.com/multiformats/go-base32"
     9  	mh "github.com/multiformats/go-multihash"
    10  	"github.com/storacha/go-ucanto/core/ipld"
    11  	"github.com/storacha/go-ucanto/core/result/failure"
    12  )
    13  
    14  type linkReader struct {
    15  	lc *linkCfg
    16  }
    17  
    18  func (lr linkReader) Read(input any) (ipld.Link, failure.Failure) {
    19  	link, asLink := input.(ipld.Link)
    20  	if !asLink {
    21  		node, asNode := input.(ipld.Node)
    22  		if !asNode {
    23  			// If input is not an IPLD node, can it be converted to one?
    24  			if builder, ok := input.(ipld.Builder); ok {
    25  				n, err := builder.ToIPLD()
    26  				if err != nil {
    27  					return nil, NewSchemaError(err.Error())
    28  				}
    29  				node = n
    30  			} else {
    31  				return nil, NewSchemaError("unexpected input: not an IPLD node or link")
    32  			}
    33  		}
    34  		var err error
    35  		link, err = node.AsLink()
    36  		if err != nil {
    37  			return nil, NewSchemaError(err.Error())
    38  		}
    39  	}
    40  
    41  	cidLink, ok := link.(cidlink.Link)
    42  	if !ok {
    43  		return nil, NewSchemaError("Unsupported Link Type")
    44  	}
    45  	cid := cidLink.Cid
    46  	if lr.lc.codec != nil && cid.Prefix().Codec != *lr.lc.codec {
    47  		return nil, NewSchemaError(fmt.Sprintf("Expected link to be CID with %X codec", *lr.lc.codec))
    48  	}
    49  
    50  	if lr.lc.version != nil && cid.Prefix().Version != *lr.lc.version {
    51  		return nil, NewSchemaError(fmt.Sprintf(
    52  			"Expected link to be CID version %d instead of %d", *lr.lc.version, cid.Prefix().Version))
    53  	}
    54  
    55  	if lr.lc.multihash != nil {
    56  		multihash := lr.lc.multihash
    57  		if multihash.code != nil && cid.Prefix().MhType != *multihash.code {
    58  			return nil, NewSchemaError(fmt.Sprintf("Expected link to be CID with %X hashing algorithm", *&multihash.code))
    59  		}
    60  		if multihash.digest != nil {
    61  			decoded, err := mh.Decode(cid.Hash())
    62  			if err != nil {
    63  				return nil, NewSchemaError(err.Error())
    64  			}
    65  
    66  			if bytes.Compare(decoded.Digest, *multihash.digest) != 0 {
    67  				return nil, NewSchemaError(fmt.Sprintf("Expected link with %s hash digest instead of %s", base32.StdEncoding.EncodeToString(*multihash.digest), base32.StdEncoding.EncodeToString(decoded.Digest)))
    68  			}
    69  		}
    70  	}
    71  	return link, nil
    72  }
    73  
    74  type multihashConfig struct {
    75  	code   *uint64
    76  	digest *[]byte
    77  }
    78  
    79  type MultihashOption func(*multihashConfig)
    80  
    81  func WithAlg(code uint64) MultihashOption {
    82  	return func(mc *multihashConfig) {
    83  		mc.code = &code
    84  	}
    85  }
    86  
    87  func WithDigest(digest []byte) MultihashOption {
    88  	return func(mc *multihashConfig) {
    89  		mc.digest = &digest
    90  	}
    91  }
    92  
    93  type linkCfg struct {
    94  	version   *uint64
    95  	codec     *uint64
    96  	multihash *multihashConfig
    97  }
    98  
    99  type LinkOption func(*linkCfg)
   100  
   101  func WithVersion(version uint64) LinkOption {
   102  	return func(lc *linkCfg) {
   103  		lc.version = &version
   104  	}
   105  }
   106  
   107  func WithCodec(codec uint64) LinkOption {
   108  	return func(lc *linkCfg) {
   109  		lc.codec = &codec
   110  	}
   111  }
   112  
   113  func WithMultihashConfig(opts ...MultihashOption) LinkOption {
   114  	return func(lc *linkCfg) {
   115  		mc := &multihashConfig{}
   116  		for _, opt := range opts {
   117  			opt(mc)
   118  		}
   119  		lc.multihash = mc
   120  	}
   121  }
   122  
   123  func Link(opts ...LinkOption) Reader[any, ipld.Link] {
   124  	lc := &linkCfg{}
   125  	for _, opt := range opts {
   126  		opt(lc)
   127  	}
   128  	return linkReader{lc}
   129  }