github.com/storacha/go-ucanto@v0.7.2/client/connection.go (about) 1 package client 2 3 import ( 4 "context" 5 "crypto/sha256" 6 "fmt" 7 "hash" 8 "iter" 9 10 "github.com/storacha/go-ucanto/core/invocation" 11 "github.com/storacha/go-ucanto/core/ipld/block" 12 "github.com/storacha/go-ucanto/core/message" 13 "github.com/storacha/go-ucanto/transport" 14 "github.com/storacha/go-ucanto/transport/car" 15 "github.com/storacha/go-ucanto/ucan" 16 ) 17 18 type Connection interface { 19 ID() ucan.Principal 20 Channel() transport.Channel 21 Codec() transport.OutboundCodec 22 Hasher() hash.Hash 23 } 24 25 // Option is an option configuring a ucanto connection. 26 type Option func(cfg *connConfig) error 27 28 type connConfig struct { 29 hasher func() hash.Hash 30 codec transport.OutboundCodec 31 } 32 33 // WithHasher configures the hasher factory. 34 func WithHasher(hasher func() hash.Hash) Option { 35 return func(cfg *connConfig) error { 36 cfg.hasher = hasher 37 return nil 38 } 39 } 40 41 // WithOutboundCodec configures the codec used to encode requests and decode 42 // responses. 43 func WithOutboundCodec(codec transport.OutboundCodec) Option { 44 return func(cfg *connConfig) error { 45 cfg.codec = codec 46 return nil 47 } 48 } 49 50 func NewConnection(id ucan.Principal, channel transport.Channel, options ...Option) (Connection, error) { 51 cfg := connConfig{hasher: sha256.New} 52 for _, opt := range options { 53 if err := opt(&cfg); err != nil { 54 return nil, err 55 } 56 } 57 58 hasher := cfg.hasher 59 if hasher == nil { 60 hasher = sha256.New 61 } 62 63 codec := cfg.codec 64 if codec == nil { 65 codec = car.NewOutboundCodec() 66 } 67 68 c := conn{id, codec, channel, hasher} 69 return &c, nil 70 } 71 72 type conn struct { 73 id ucan.Principal 74 codec transport.OutboundCodec 75 channel transport.Channel 76 hasher func() hash.Hash 77 } 78 79 var _ Connection = (*conn)(nil) 80 81 func (c *conn) ID() ucan.Principal { 82 return c.id 83 } 84 85 func (c *conn) Codec() transport.OutboundCodec { 86 return c.codec 87 } 88 89 func (c *conn) Channel() transport.Channel { 90 return c.channel 91 } 92 93 func (c *conn) Hasher() hash.Hash { 94 return c.hasher() 95 } 96 97 type ExecutionResponse interface { 98 // Blocks returns an iterator of all the IPLD blocks that are included in 99 // the response. 100 Blocks() iter.Seq2[block.Block, error] 101 // Get returns a link to a receipt, given an invocation link. 102 Get(inv ucan.Link) (ucan.Link, bool) 103 } 104 105 func Execute(ctx context.Context, invocations []invocation.Invocation, conn Connection) (ExecutionResponse, error) { 106 input, err := message.Build(invocations, nil) 107 if err != nil { 108 return nil, fmt.Errorf("building message: %w", err) 109 } 110 111 req, err := conn.Codec().Encode(input) 112 if err != nil { 113 return nil, fmt.Errorf("encoding message: %w", err) 114 } 115 116 res, err := conn.Channel().Request(ctx, req) 117 if err != nil { 118 return nil, fmt.Errorf("sending message: %w", err) 119 } 120 defer res.Body().Close() 121 122 output, err := conn.Codec().Decode(res) 123 if err != nil { 124 return nil, fmt.Errorf("decoding message: %w", err) 125 } 126 127 return ExecutionResponse(output), nil 128 }