Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
fix(SPV-642): arc errors - simplification and comparsion for ArcError (
Browse files Browse the repository at this point in the history
…#97)

Co-authored-by: Damian Orzepowski <damian.orzepowski@4chain.studio>
  • Loading branch information
wregulski and dorzepowski authored May 22, 2024
1 parent bb8fe43 commit 0f787a8
Show file tree
Hide file tree
Showing 12 changed files with 54 additions and 80 deletions.
56 changes: 15 additions & 41 deletions broadcast/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ package broadcast
import (
"errors"
"fmt"
"github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/utils"
"strings"

"github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/utils"
)

// ErrClientUndefined is returned when the client is undefined.
Expand All @@ -28,22 +29,16 @@ var ErrUnableToDecodeResponse = errors.New("unable to decode response")
// ErrMissingStatus is returned when the tx status is missing.
var ErrMissingStatus = errors.New("missing tx status")

// ErrStrategyUnkown is returned when the strategy provided is unknown.
// ErrStrategyUnknown is returned when the strategy provided is unknown.
// Example:
//
// func NewBroadcaster(strategy Strategy, factories ...BroadcastFactory) broadcast.Client
// Calling NewBroadcaster we need to provide a strategy, if the strategy is unknown (we don't have an implementation for that) we return ErrStrategyUnkown.
var ErrStrategyUnkown = errors.New("unknown strategy")
// Calling NewBroadcaster we need to provide a strategy, if the strategy is unknown (we don't have an implementation for that) we return ErrStrategyUnknown.
var ErrStrategyUnknown = errors.New("unknown strategy")

// ErrNoMinerResponse is returned when no response is received from any miner.
var ErrNoMinerResponse = errors.New("failed to get reponse from any miner")

// ArcFailure is the interface for the error returned by the ArcClient.
type ArcFailure interface {
error
Details() *FailureResponse
}

// ArcError is general type for the error returned by the ArcClient.
type ArcError struct {
Type string `json:"type"`
Expand All @@ -55,13 +50,14 @@ type ArcError struct {
ExtraInfo string `json:"extraInfo,omitempty"`
}

// Details returns the details of the error it's the implementation of the ArcFailure interface.
func (failure *FailureResponse) Details() *FailureResponse {
return failure
// IsRejectedTransaction returns true if the transaction is in rejected status.
func (err *ArcError) IsRejectedTransaction() bool {
const RejectedStatus = 109
return err.Status == RejectedStatus
}

// Error returns the error string it's the implementation of the error interface.
func (err ArcError) Error() string {
func (err *ArcError) Error() string {
sb := strings.Builder{}

sb.WriteString("arc error: {")
Expand All @@ -84,34 +80,12 @@ func (err ArcError) Error() string {
return sb.String()
}

// FailureResponse is the response returned by the ArcClient when the request fails.
type FailureResponse struct {
Description string
ArcErrorResponse *ArcError
}

// Error returns the error string it's the implementation of the error interface.
func (failure *FailureResponse) Error() string {
sb := strings.Builder{}
sb.WriteString(failure.Description)

if failure.ArcErrorResponse != nil {
sb.WriteString(", ")
sb.WriteString(failure.ArcErrorResponse.Error())
}

return sb.String()
func (err *ArcError) Is(target error) bool {
var arcError *ArcError
return errors.As(target, &arcError)
}

// Failure returns a new FailureResponse with the description and the error.
func Failure(description string, err error) *FailureResponse {
var arcErr ArcError
if errors.As(err, &arcErr) {
return &FailureResponse{
Description: description,
ArcErrorResponse: &arcErr,
}
}

return &FailureResponse{Description: utils.WithCause(errors.New(description), err).Error()}
func Failure(description string, err error) error {
return utils.WithCause(errors.New(description), err)
}
10 changes: 5 additions & 5 deletions broadcast/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,35 @@ import (
// FeeQuoter it the interface that wraps GetFeeQuote method.
// It retrieves the Fee Quote from the configured miners.
type FeeQuoter interface {
GetFeeQuote(ctx context.Context) ([]*FeeQuote, ArcFailure)
GetFeeQuote(ctx context.Context) ([]*FeeQuote, error)
}

// PolicyQuoter it the interface that wraps GetPolicyQuote method.
// It retrieves the Policy Quote from the configured miners.
type PolicyQuoter interface {
GetPolicyQuote(ctx context.Context) ([]*PolicyQuoteResponse, ArcFailure)
GetPolicyQuote(ctx context.Context) ([]*PolicyQuoteResponse, error)
}

// TransactionQuerier is the interface that wraps the QueryTransaction method.
// It takes a transaction ID and returns the transaction details, like it's status, hash, height etc.
// Everything is wrapped in the QueryTxResponse struct.
type TransactionQuerier interface {
QueryTransaction(ctx context.Context, txID string) (*QueryTxResponse, ArcFailure)
QueryTransaction(ctx context.Context, txID string) (*QueryTxResponse, error)
}

// TransactionSubmitter is the interface that wraps the SubmitTransaction method.
// It takes a transaction and tries to broadcast it to the P2P network.
// Transaction object needs RawTx to be set. All other fields are optional and used to append headers related to status callbacks.
// As a result it returns a SubmitTxResponse object.
type TransactionSubmitter interface {
SubmitTransaction(ctx context.Context, tx *Transaction, opts ...TransactionOptFunc) (*SubmitTxResponse, ArcFailure)
SubmitTransaction(ctx context.Context, tx *Transaction, opts ...TransactionOptFunc) (*SubmitTxResponse, error)
}

// TransactionsSubmitter is the interface that wraps the SubmitBatchTransactions method.
// It is the same as TransactionSubmitter but it takes a slice of transactions and tries to broadcast them to the P2P network.
// As a result it returns a SubmitBatchTxResponse, which includes a slice of SubmitTxResponse objects.
type TransactionsSubmitter interface {
SubmitBatchTransactions(ctx context.Context, tx []*Transaction, opts ...TransactionOptFunc) (*SubmitBatchTxResponse, ArcFailure)
SubmitBatchTransactions(ctx context.Context, tx []*Transaction, opts ...TransactionOptFunc) (*SubmitBatchTxResponse, error)
}

// Client is a grouping interface that represents the entire exposed functionality of the broadcast client.
Expand Down
2 changes: 1 addition & 1 deletion broadcast/internal/arc/arc_fee_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/bitcoin-sv/go-broadcast-client/broadcast"
)

func (a *ArcClient) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, broadcast.ArcFailure) {
func (a *ArcClient) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, error) {
if a == nil {
return nil, broadcast.Failure("GetFeeQuote: arc client is nil", nil)
}
Expand Down
2 changes: 1 addition & 1 deletion broadcast/internal/arc/arc_policy_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/bitcoin-sv/go-broadcast-client/httpclient"
)

func (a *ArcClient) GetPolicyQuote(ctx context.Context) ([]*broadcast.PolicyQuoteResponse, broadcast.ArcFailure) {
func (a *ArcClient) GetPolicyQuote(ctx context.Context) ([]*broadcast.PolicyQuoteResponse, error) {
if a == nil {
return nil, broadcast.Failure("GetPolicyQuote:", broadcast.ErrClientUndefined)
}
Expand Down
2 changes: 1 addition & 1 deletion broadcast/internal/arc/arc_query_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

var ErrMissingTxID = errors.New("missing tx id")

func (a *ArcClient) QueryTransaction(ctx context.Context, txID string) (*broadcast.QueryTxResponse, broadcast.ArcFailure) {
func (a *ArcClient) QueryTransaction(ctx context.Context, txID string) (*broadcast.QueryTxResponse, error) {
if a == nil {
return nil, broadcast.Failure("QueryTransaction:", broadcast.ErrClientUndefined)
}
Expand Down
4 changes: 2 additions & 2 deletions broadcast/internal/arc/arc_submit_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type SubmitTxRequest struct {

var ErrSubmitTxMarshal = errors.New("error while marshalling submit tx body")

func (a *ArcClient) SubmitTransaction(ctx context.Context, tx *broadcast.Transaction, opts ...broadcast.TransactionOptFunc) (*broadcast.SubmitTxResponse, broadcast.ArcFailure) {
func (a *ArcClient) SubmitTransaction(ctx context.Context, tx *broadcast.Transaction, opts ...broadcast.TransactionOptFunc) (*broadcast.SubmitTxResponse, error) {
if a == nil {
return nil, broadcast.Failure("SubmitTransaction:", broadcast.ErrClientUndefined)
}
Expand Down Expand Up @@ -50,7 +50,7 @@ func (a *ArcClient) SubmitTransaction(ctx context.Context, tx *broadcast.Transac
return response, nil
}

func (a *ArcClient) SubmitBatchTransactions(ctx context.Context, txs []*broadcast.Transaction, opts ...broadcast.TransactionOptFunc) (*broadcast.SubmitBatchTxResponse, broadcast.ArcFailure) {
func (a *ArcClient) SubmitBatchTransactions(ctx context.Context, txs []*broadcast.Transaction, opts ...broadcast.TransactionOptFunc) (*broadcast.SubmitBatchTxResponse, error) {
if a == nil {
return nil, broadcast.Failure("SubmitBatchTransactions:", broadcast.ErrClientUndefined)
}
Expand Down
10 changes: 5 additions & 5 deletions broadcast/internal/arc/mocks/arc_client_mock_no_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ import (
type ArcClientMockFailure struct{}

// GetFeeQuote returns an error.
func (*ArcClientMockFailure) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, broadcast_api.ArcFailure) {
func (*ArcClientMockFailure) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, error) {
return nil, broadcast_api.Failure("", broadcast_api.ErrNoMinerResponse)
}

// GetPolicyQuote returns an error.
func (*ArcClientMockFailure) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockFailure) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, error) {
return nil, broadcast_api.Failure("", broadcast_api.ErrNoMinerResponse)
}

// QueryTransaction returns an error.
func (*ArcClientMockFailure) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockFailure) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, error) {
return nil, broadcast_api.Failure("", broadcast_api.ErrNoMinerResponse)
}

// SubmitBatchTransactions returns an error.
func (*ArcClientMockFailure) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockFailure) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, error) {
return nil, broadcast_api.Failure("", broadcast_api.ErrAllBroadcastersFailed)
}

// SubmitTransaction returns an error.
func (*ArcClientMockFailure) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockFailure) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, error) {
return nil, broadcast_api.Failure("", broadcast_api.ErrAllBroadcastersFailed)
}

Expand Down
10 changes: 5 additions & 5 deletions broadcast/internal/arc/mocks/arc_client_mock_success.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type ArcClientMock struct{}

// GetFeeQuote returns a successful FeeQuote response.
func (*ArcClientMock) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, broadcast_api.ArcFailure) {
func (*ArcClientMock) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, error) {
quotes := make([]*broadcast_api.FeeQuote, 0)
quotes = append(quotes, Fee1)
quotes = append(quotes, Fee2)
Expand All @@ -19,7 +19,7 @@ func (*ArcClientMock) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuot
}

// GetPolicyQuote return a successful PolicyQuoteResponse.
func (*ArcClientMock) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, broadcast_api.ArcFailure) {
func (*ArcClientMock) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, error) {
policies := make([]*broadcast_api.PolicyQuoteResponse, 0)
policies = append(policies, Policy1)
policies = append(policies, Policy2)
Expand All @@ -28,20 +28,20 @@ func (*ArcClientMock) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.Poli
}

// QueryTransaction returns a successful QueryTxResponse.
func (*ArcClientMock) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMock) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, error) {
return QueryTx(txID), nil
}

// SubmitTransaction returns a successful SubmitTxResponse.
func (*ArcClientMock) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMock) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, error) {
return &broadcast_api.SubmitTxResponse{
BaseResponse: broadcast_api.BaseResponse{Miner: fixtures.ProviderMain},
SubmittedTx: SubmittedTx,
}, nil
}

// SubmitBatchTransactions returns a successful SubmitBatchTxResponse.
func (*ArcClientMock) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMock) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, error) {
return &broadcast_api.SubmitBatchTxResponse{
BaseResponse: broadcast_api.BaseResponse{Miner: fixtures.ProviderMain},
Transactions: []*broadcast_api.SubmittedTx{
Expand Down
10 changes: 5 additions & 5 deletions broadcast/internal/arc/mocks/arc_client_mock_timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
type ArcClientMockTimeout struct{}

// GetFeeQuote returns a successful FeeQuote response.
func (*ArcClientMockTimeout) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, broadcast_api.ArcFailure) {
func (*ArcClientMockTimeout) GetFeeQuote(ctx context.Context) ([]*broadcast_api.FeeQuote, error) {
if deadline, ok := ctx.Deadline(); ok {
time.Sleep(time.Until(deadline) + 10*time.Millisecond)
}
Expand All @@ -24,7 +24,7 @@ func (*ArcClientMockTimeout) GetFeeQuote(ctx context.Context) ([]*broadcast_api.
}

// GetPolicyQuote return a successful PolicyQuoteResponse.
func (*ArcClientMockTimeout) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockTimeout) GetPolicyQuote(ctx context.Context) ([]*broadcast_api.PolicyQuoteResponse, error) {
if deadline, ok := ctx.Deadline(); ok {
time.Sleep(time.Until(deadline) + 10*time.Millisecond)
}
Expand All @@ -37,7 +37,7 @@ func (*ArcClientMockTimeout) GetPolicyQuote(ctx context.Context) ([]*broadcast_a
}

// QueryTransaction returns a successful QueryTxResponse.
func (*ArcClientMockTimeout) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockTimeout) QueryTransaction(ctx context.Context, txID string) (*broadcast_api.QueryTxResponse, error) {
if deadline, ok := ctx.Deadline(); ok {
time.Sleep(time.Until(deadline) + 10*time.Millisecond)
}
Expand All @@ -46,7 +46,7 @@ func (*ArcClientMockTimeout) QueryTransaction(ctx context.Context, txID string)
}

// SubmitTransaction returns a successful SubmitTxResponse.
func (*ArcClientMockTimeout) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockTimeout) SubmitTransaction(ctx context.Context, tx *broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitTxResponse, error) {
if deadline, ok := ctx.Deadline(); ok {
time.Sleep(time.Until(deadline) + 10*time.Millisecond)
}
Expand All @@ -58,7 +58,7 @@ func (*ArcClientMockTimeout) SubmitTransaction(ctx context.Context, tx *broadcas
}

// SubmitBatchTransactions returns a successful SubmitBatchTxResponse.
func (*ArcClientMockTimeout) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, broadcast_api.ArcFailure) {
func (*ArcClientMockTimeout) SubmitBatchTransactions(ctx context.Context, tx []*broadcast_api.Transaction, opts ...broadcast_api.TransactionOptFunc) (*broadcast_api.SubmitBatchTxResponse, error) {
if deadline, ok := ctx.Deadline(); ok {
time.Sleep(time.Until(deadline) + 10*time.Millisecond)
}
Expand Down
16 changes: 8 additions & 8 deletions broadcast/internal/composite/broadcaster.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewBroadcaster(strategy Strategy, factories ...BroadcastFactory) broadcast.

func (c *compositeBroadcaster) GetPolicyQuote(
ctx context.Context,
) ([]*broadcast.PolicyQuoteResponse, broadcast.ArcFailure) {
) ([]*broadcast.PolicyQuoteResponse, error) {
var policyQuotes []*broadcast.PolicyQuoteResponse

for _, broadcaster := range c.broadcasters {
Expand All @@ -50,7 +50,7 @@ func (c *compositeBroadcaster) GetPolicyQuote(
return policyQuotes, nil
}

func (c *compositeBroadcaster) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, broadcast.ArcFailure) {
func (c *compositeBroadcaster) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, error) {
var feeQuotes []*broadcast.FeeQuote

for _, broadcaster := range c.broadcasters {
Expand All @@ -70,11 +70,11 @@ func (c *compositeBroadcaster) GetFeeQuote(ctx context.Context) ([]*broadcast.Fe
func (c *compositeBroadcaster) QueryTransaction(
ctx context.Context,
txID string,
) (*broadcast.QueryTxResponse, broadcast.ArcFailure) {
) (*broadcast.QueryTxResponse, error) {
executionFuncs := make([]executionFunc, len(c.broadcasters))
for i, broadcaster := range c.broadcasters {
currentBroadcaster := broadcaster
executionFuncs[i] = func(ctx context.Context) (Result, broadcast.ArcFailure) {
executionFuncs[i] = func(ctx context.Context) (Result, error) {
return currentBroadcaster.QueryTransaction(ctx, txID)
}
}
Expand All @@ -96,11 +96,11 @@ func (c *compositeBroadcaster) SubmitTransaction(
ctx context.Context,
tx *broadcast.Transaction,
opts ...broadcast.TransactionOptFunc,
) (*broadcast.SubmitTxResponse, broadcast.ArcFailure) {
) (*broadcast.SubmitTxResponse, error) {
executionFuncs := make([]executionFunc, len(c.broadcasters))
for i, broadcaster := range c.broadcasters {
currentBroadcaster := broadcaster
executionFuncs[i] = func(ctx context.Context) (Result, broadcast.ArcFailure) {
executionFuncs[i] = func(ctx context.Context) (Result, error) {
return currentBroadcaster.SubmitTransaction(ctx, tx)
}
}
Expand All @@ -122,11 +122,11 @@ func (c *compositeBroadcaster) SubmitBatchTransactions(
ctx context.Context,
txs []*broadcast.Transaction,
opts ...broadcast.TransactionOptFunc,
) (*broadcast.SubmitBatchTxResponse, broadcast.ArcFailure) {
) (*broadcast.SubmitBatchTxResponse, error) {
executionFuncs := make([]executionFunc, len(c.broadcasters))
for i, broadcaster := range c.broadcasters {
currentBroadcaster := broadcaster
executionFuncs[i] = func(ctx context.Context) (Result, broadcast.ArcFailure) {
executionFuncs[i] = func(ctx context.Context) (Result, error) {
return currentBroadcaster.SubmitBatchTransactions(ctx, txs)
}
}
Expand Down
10 changes: 5 additions & 5 deletions broadcast/internal/composite/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ const (

type Result interface{}

type executionFunc func(context.Context) (Result, broadcast.ArcFailure)
type executionFunc func(context.Context) (Result, error)

type StrategyExecutionFunc func(context.Context, []executionFunc) (Result, broadcast.ArcFailure)
type StrategyExecutionFunc func(context.Context, []executionFunc) (Result, error)

// Strategy is a component designed to offer flexibility in selecting a communication approach
// for interacting with multiple broadcasting services, such as multiple Arc services.
Expand All @@ -33,16 +33,16 @@ func New(name StrategyName) (*Strategy, error) {
case OneByOneStrategy:
return &Strategy{name: name, executionFunc: OneByOne.executionFunc}, nil
default:
return nil, broadcast.ErrStrategyUnkown
return nil, broadcast.ErrStrategyUnknown
}
}

func (s *Strategy) Execute(ctx context.Context, executionFuncs []executionFunc) (Result, broadcast.ArcFailure) {
func (s *Strategy) Execute(ctx context.Context, executionFuncs []executionFunc) (Result, error) {
return s.executionFunc(ctx, executionFuncs)
}

var (
OneByOne = &Strategy{name: OneByOneStrategy, executionFunc: func(ctx context.Context, executionFuncs []executionFunc) (Result, broadcast.ArcFailure) {
OneByOne = &Strategy{name: OneByOneStrategy, executionFunc: func(ctx context.Context, executionFuncs []executionFunc) (Result, error) {
for _, executionFunc := range executionFuncs {
result, err := executionFunc(ctx)
if err != nil {
Expand Down
Loading

0 comments on commit 0f787a8

Please sign in to comment.