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 }