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

fix(SPV-642): arc errors - simplification and comparsion for ArcError #97

Merged
merged 6 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading