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  }