diff --git a/go.mod b/go.mod index fa9f2646..a3e9cb94 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.28.0 github.com/samber/lo v1.47.0 + github.com/samber/mo v1.13.0 github.com/stretchr/testify v1.8.0 github.com/urfave/cli v1.22.9 go.mongodb.org/mongo-driver v1.17.1 diff --git a/go.sum b/go.sum index d5301ade..652af5f7 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/mo v1.13.0 h1:LB1OwfJMju3a6FjghH+AIvzMG0ZPOzgTWj1qaHs1IQ4= +github.com/samber/mo v1.13.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/internal/util/error.go b/internal/util/error.go index 0d9e9572..c6e90c45 100644 --- a/internal/util/error.go +++ b/internal/util/error.go @@ -20,8 +20,9 @@ import ( // `ErrorCode` newtype, but that requires a more invasive change to everything // that uses error codes. const ( - LockFailed int = 107 - SampleTooManyDuplicates int = 28799 + LockFailed = 107 + SampleTooManyDuplicates = 28799 + CursorKilled = 237 ) // diff --git a/internal/verifier/change_stream.go b/internal/verifier/change_stream.go index dcd8122a..8aea43f9 100644 --- a/internal/verifier/change_stream.go +++ b/internal/verifier/change_stream.go @@ -6,8 +6,11 @@ import ( "time" "github.com/10gen/migration-verifier/internal/keystring" + "github.com/10gen/migration-verifier/internal/retry" + "github.com/10gen/migration-verifier/internal/util" "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/samber/mo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -63,7 +66,7 @@ func (verifier *Verifier) HandleChangeStreamEvents(ctx context.Context, batch [] for i, changeEvent := range batch { if changeEvent.ClusterTime != nil && (verifier.lastChangeEventTime == nil || - verifier.lastChangeEventTime.Compare(*changeEvent.ClusterTime) < 0) { + verifier.lastChangeEventTime.Before(*changeEvent.ClusterTime)) { verifier.lastChangeEventTime = changeEvent.ClusterTime } switch changeEvent.OpType { @@ -136,6 +139,7 @@ func (verifier *Verifier) GetChangeStreamFilter() []bson.D { // shouldn’t really happen anyway by definition. func (verifier *Verifier) readAndHandleOneChangeEventBatch( ctx context.Context, + ri *retry.Info, cs *mongo.ChangeStream, ) error { eventsRead := 0 @@ -163,6 +167,8 @@ func (verifier *Verifier) readAndHandleOneChangeEventBatch( eventsRead++ } + ri.IterationSuccess() + if eventsRead == 0 { return nil } @@ -175,9 +181,11 @@ func (verifier *Verifier) readAndHandleOneChangeEventBatch( return nil } -func (verifier *Verifier) iterateChangeStream(ctx context.Context, cs *mongo.ChangeStream) { - defer cs.Close(ctx) - +func (verifier *Verifier) iterateChangeStream( + ctx context.Context, + ri *retry.Info, + cs *mongo.ChangeStream, +) error { var lastPersistedTime time.Time persistResumeTokenIfNeeded := func() error { @@ -201,10 +209,7 @@ func (verifier *Verifier) iterateChangeStream(ctx context.Context, cs *mongo.Cha // If the context is canceled, return immmediately. case <-ctx.Done(): - verifier.logger.Debug(). - Err(ctx.Err()). - Msg("Change stream quitting.") - return + return ctx.Err() // If the changeStreamEnderChan has a message, the user has indicated that // source writes are ended. This means we should exit rather than continue @@ -222,11 +227,12 @@ func (verifier *Verifier) iterateChangeStream(ctx context.Context, cs *mongo.Cha var curTs primitive.Timestamp curTs, err = extractTimestampFromResumeToken(cs.ResumeToken()) if err != nil { - err = errors.Wrap(err, "failed to extract timestamp from change stream's resume token") - break + return errors.Wrap(err, "failed to extract timestamp from change stream's resume token") } - if curTs.After(writesOffTs) { + // writesOffTs never refers to a real event, + // so we can stop once curTs >= writesOffTs. + if !curTs.Before(writesOffTs) { verifier.logger.Debug(). Interface("currentTimestamp", curTs). Interface("writesOffTimestamp", writesOffTs). @@ -235,30 +241,22 @@ func (verifier *Verifier) iterateChangeStream(ctx context.Context, cs *mongo.Cha break } - err = verifier.readAndHandleOneChangeEventBatch(ctx, cs) + err = verifier.readAndHandleOneChangeEventBatch(ctx, ri, cs) if err != nil { - break + return err } } default: - err = verifier.readAndHandleOneChangeEventBatch(ctx, cs) + err = verifier.readAndHandleOneChangeEventBatch(ctx, ri, cs) if err == nil { err = persistResumeTokenIfNeeded() } - } - if err != nil && !errors.Is(err, context.Canceled) { - verifier.logger.Debug(). - Err(err). - Msg("Sending change stream error.") - - verifier.changeStreamErrChan <- err - - if !gotwritesOffTimestamp { - break + if err != nil { + return err } } @@ -284,10 +282,13 @@ func (verifier *Verifier) iterateChangeStream(ctx context.Context, cs *mongo.Cha } infoLog.Msg("Change stream is done.") + + return nil } -// StartChangeStream starts the change stream. -func (verifier *Verifier) StartChangeStream(ctx context.Context) error { +func (verifier *Verifier) createChangeStream( + ctx context.Context, +) (*mongo.ChangeStream, primitive.Timestamp, error) { pipeline := verifier.GetChangeStreamFilter() opts := options.ChangeStream(). SetMaxAwaitTime(1 * time.Second). @@ -299,7 +300,7 @@ func (verifier *Verifier) StartChangeStream(ctx context.Context) error { savedResumeToken, err := verifier.loadChangeStreamResumeToken(ctx) if err != nil { - return errors.Wrap(err, "failed to load persisted change stream resume token") + return nil, primitive.Timestamp{}, errors.Wrap(err, "failed to load persisted change stream resume token") } csStartLogEvent := verifier.logger.Info() @@ -326,40 +327,100 @@ func (verifier *Verifier) StartChangeStream(ctx context.Context) error { sess, err := verifier.srcClient.StartSession() if err != nil { - return errors.Wrap(err, "failed to start session") + return nil, primitive.Timestamp{}, errors.Wrap(err, "failed to start session") } sctx := mongo.NewSessionContext(ctx, sess) srcChangeStream, err := verifier.srcClient.Watch(sctx, pipeline, opts) if err != nil { - return errors.Wrap(err, "failed to open change stream") + return nil, primitive.Timestamp{}, errors.Wrap(err, "failed to open change stream") } err = verifier.persistChangeStreamResumeToken(ctx, srcChangeStream) if err != nil { - return err + return nil, primitive.Timestamp{}, err } - csTimestamp, err := extractTimestampFromResumeToken(srcChangeStream.ResumeToken()) + startTs, err := extractTimestampFromResumeToken(srcChangeStream.ResumeToken()) if err != nil { - return errors.Wrap(err, "failed to extract timestamp from change stream's resume token") + return nil, primitive.Timestamp{}, errors.Wrap(err, "failed to extract timestamp from change stream's resume token") } + // With sharded clusters the resume token might lead the cluster time + // by 1 increment. In that case we need the actual cluster time; + // otherwise we will get errors. clusterTime, err := getClusterTimeFromSession(sess) if err != nil { - return errors.Wrap(err, "failed to read cluster time from session") + return nil, primitive.Timestamp{}, errors.Wrap(err, "failed to read cluster time from session") + } + + if startTs.After(clusterTime) { + startTs = clusterTime } - verifier.srcStartAtTs = &csTimestamp - if csTimestamp.After(clusterTime) { - verifier.srcStartAtTs = &clusterTime + return srcChangeStream, startTs, nil +} + +// StartChangeStream starts the change stream. +func (verifier *Verifier) StartChangeStream(ctx context.Context) error { + // This channel holds the first change stream creation's result, whether + // success or failure. Rather than using a Result we could make separate + // Timestamp and error channels, but the single channel is cleaner since + // there's no chance of "nonsense" like both channels returning a payload. + initialCreateResultChan := make(chan mo.Result[primitive.Timestamp]) + + go func() { + retryer := retry.New(retry.DefaultDurationLimit) + retryer = retryer.WithErrorCodes(util.CursorKilled) + + parentThreadWaiting := true + + err := retryer.RunForTransientErrorsOnly( + ctx, + verifier.logger, + func(ri *retry.Info) error { + srcChangeStream, startTs, err := verifier.createChangeStream(ctx) + if err != nil { + if parentThreadWaiting { + initialCreateResultChan <- mo.Err[primitive.Timestamp](err) + return nil + } + + return err + } + + defer srcChangeStream.Close(ctx) + + if parentThreadWaiting { + initialCreateResultChan <- mo.Ok(startTs) + close(initialCreateResultChan) + parentThreadWaiting = false + } + + return verifier.iterateChangeStream(ctx, ri, srcChangeStream) + }, + ) + + if err != nil { + // NB: This failure always happens after the initial change stream + // creation. + verifier.changeStreamErrChan <- err + close(verifier.changeStreamErrChan) + } + }() + + result := <-initialCreateResultChan + + startTs, err := result.Get() + if err != nil { + return err } + verifier.srcStartAtTs = &startTs + verifier.mux.Lock() verifier.changeStreamRunning = true verifier.mux.Unlock() - go verifier.iterateChangeStream(ctx, srcChangeStream) - return nil } diff --git a/internal/verifier/change_stream_test.go b/internal/verifier/change_stream_test.go index da2153d8..71fc326f 100644 --- a/internal/verifier/change_stream_test.go +++ b/internal/verifier/change_stream_test.go @@ -13,6 +13,8 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readconcern" ) func TestChangeStreamFilter(t *testing.T) { @@ -263,6 +265,92 @@ func (suite *IntegrationTestSuite) TestWithChangeEventsBatching() { } +func (suite *IntegrationTestSuite) TestCursorKilledResilience() { + ctx := suite.Context() + + verifier := suite.BuildVerifier() + + db := suite.srcMongoClient.Database(suite.DBNameForTest()) + coll := db.Collection("mycoll") + suite.Require().NoError( + db.CreateCollection(ctx, coll.Name()), + ) + + // start verifier + verifierRunner := RunVerifierCheck(suite.Context(), suite.T(), verifier) + + // wait for generation 0 to end + verifierRunner.AwaitGenerationEnd() + + const mvName = "Migration Verifier" + + // Kill verifier’s change stream. + cursor, err := suite.srcMongoClient.Database( + "admin", + options.Database().SetReadConcern(readconcern.Local()), + ).Aggregate( + ctx, + mongo.Pipeline{ + { + {"$currentOp", bson.D{ + {"idleCursors", true}, + }}, + }, + { + {"$match", bson.D{ + {"clientMetadata.application.name", mvName}, + {"command.collection", "$cmd.aggregate"}, + {"cursor.originatingCommand.pipeline.0.$_internalChangeStreamOplogMatch", + bson.D{{"$type", "object"}}, + }, + }}, + }, + }, + ) + suite.Require().NoError(err) + + var ops []bson.Raw + suite.Require().NoError(cursor.All(ctx, &ops)) + + for _, cursorRaw := range ops { + opId, err := cursorRaw.LookupErr("opid") + suite.Require().NoError(err, "should get opid from op") + + suite.T().Logf("Killing change stream op %+v", opId) + + suite.Require().NoError( + suite.srcMongoClient.Database("admin").RunCommand( + ctx, + bson.D{ + {"killOp", 1}, + {"op", opId}, + }, + ).Err(), + ) + } + + _, err = coll.InsertOne( + ctx, + bson.D{{"_id", "after kill"}}, + ) + suite.Require().NoError(err) + + suite.Require().NoError(verifier.WritesOff(ctx)) + + suite.Require().NoError(verifierRunner.Await()) + + failedTasks, incompleteTasks, err := FetchFailedAndIncompleteTasks( + ctx, + verifier.verificationTaskCollection(), + verificationTaskVerifyDocuments, + verifier.generation, + ) + suite.Require().NoError(err) + + suite.Assert().Zero(incompleteTasks, "no incomplete tasks") + suite.Require().Len(failedTasks, 1, "expect one failed task") +} + func (suite *IntegrationTestSuite) TestManyInsertsBeforeWritesOff() { suite.testInsertsBeforeWritesOff(10_000) } diff --git a/internal/verifier/check.go b/internal/verifier/check.go index 6be24116..a9307d55 100644 --- a/internal/verifier/check.go +++ b/internal/verifier/check.go @@ -87,7 +87,7 @@ func (verifier *Verifier) CheckWorker(ctx context.Context) error { select { case err := <-verifier.changeStreamErrChan: cancel() - return err + return errors.Wrap(err, "change stream failed") case <-ctx.Done(): cancel() return nil diff --git a/internal/verifier/clustertime.go b/internal/verifier/clustertime.go index 30a2c579..6eb273bd 100644 --- a/internal/verifier/clustertime.go +++ b/internal/verifier/clustertime.go @@ -7,6 +7,7 @@ import ( "github.com/10gen/migration-verifier/internal/retry" "github.com/10gen/migration-verifier/internal/util" "github.com/10gen/migration-verifier/mbson" + "github.com/10gen/migration-verifier/option" "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" @@ -17,8 +18,8 @@ import ( const opTimeKeyInServerResponse = "operationTime" -// GetNewClusterTime advances the cluster time and returns that time. -// All shards’ cluster times will meet or exceed the returned time. +// GetNewClusterTime creates a new cluster time, updates all shards’ +// cluster times to meet or exceed that time, then returns it. func GetNewClusterTime( ctx context.Context, logger *logger.Logger, @@ -35,7 +36,12 @@ func GetNewClusterTime( logger, func(_ *retry.Info) error { var err error - clusterTime, err = fetchClusterTime(ctx, client) + clusterTime, err = runAppendOplogNote( + ctx, + client, + "new ts", + option.None[primitive.Timestamp](), + ) return err }, ) @@ -52,7 +58,12 @@ func GetNewClusterTime( logger, func(_ *retry.Info) error { var err error - _, err = syncClusterTimeAcrossShards(ctx, client, clusterTime) + _, err = runAppendOplogNote( + ctx, + client, + "sync ts", + option.Some(clusterTime), + ) return err }, ) @@ -65,46 +76,31 @@ func GetNewClusterTime( return clusterTime, nil } -// Use this when we just need the correct cluster time without -// actually changing any shards’ oplogs. -func fetchClusterTime( +func runAppendOplogNote( ctx context.Context, client *mongo.Client, + note string, + maxClusterTimeOpt option.Option[primitive.Timestamp], ) (primitive.Timestamp, error) { - cmd, rawResponse, err := runAppendOplogNote( - ctx, - client, - "expect StaleClusterTime error", - primitive.Timestamp{1, 0}, - ) - - // We expect an error here; if we didn't get one then something is - // amiss on the server. - if err == nil { - return primitive.Timestamp{}, errors.Errorf("server request unexpectedly succeeded: %v", cmd) + cmd := bson.D{ + {"appendOplogNote", 1}, + {"data", bson.D{ + {"migration-verifier", note}, + }}, } - if !util.IsStaleClusterTimeError(err) { - return primitive.Timestamp{}, errors.Wrap( - err, - "unexpected error (expected StaleClusterTime) from request", - ) + if maxClusterTime, has := maxClusterTimeOpt.Get(); has { + cmd = append(cmd, bson.E{"maxClusterTime", maxClusterTime}) } - return getOpTimeFromRawResponse(rawResponse) -} + resp := client. + Database( + "admin", + options.Database().SetWriteConcern(writeconcern.Majority()), + ). + RunCommand(ctx, cmd) -func syncClusterTimeAcrossShards( - ctx context.Context, - client *mongo.Client, - maxTime primitive.Timestamp, -) (primitive.Timestamp, error) { - _, rawResponse, err := runAppendOplogNote( - ctx, - client, - "syncing cluster time", - maxTime, - ) + rawResponse, err := resp.Raw() // If any shard’s cluster time >= maxTime, the mongos will return a // StaleClusterTime error. This particular error doesn’t indicate a @@ -119,36 +115,6 @@ func syncClusterTimeAcrossShards( return getOpTimeFromRawResponse(rawResponse) } -func runAppendOplogNote( - ctx context.Context, - client *mongo.Client, - note string, - maxClusterTime primitive.Timestamp, -) (bson.D, bson.Raw, error) { - cmd := bson.D{ - {"appendOplogNote", 1}, - {"maxClusterTime", maxClusterTime}, - {"data", bson.D{ - {"migration-verifier", note}, - }}, - } - - resp := client. - Database( - "admin", - options.Database().SetWriteConcern(writeconcern.Majority()), - ). - RunCommand(ctx, cmd) - - raw, err := resp.Raw() - - return cmd, raw, errors.Wrapf( - err, - "command (%v) failed unexpectedly", - cmd, - ) -} - func getOpTimeFromRawResponse(rawResponse bson.Raw) (primitive.Timestamp, error) { // Get the `operationTime` from the response and return it. var optime primitive.Timestamp diff --git a/internal/verifier/clustertime_test.go b/internal/verifier/clustertime_test.go index 6d2e5acb..6a13c02d 100644 --- a/internal/verifier/clustertime_test.go +++ b/internal/verifier/clustertime_test.go @@ -1,13 +1,35 @@ package verifier -import "context" +import ( + "context" -func (suite *IntegrationTestSuite) TestGetClusterTime() { + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +func (suite *IntegrationTestSuite) TestGetNewClusterTime() { ctx := context.Background() logger, _ := getLoggerAndWriter("stdout") + sess, err := suite.srcMongoClient.StartSession() + suite.Require().NoError(err) + + _, err = suite.srcMongoClient. + Database(suite.DBNameForTest()). + Collection("mycoll"). + InsertOne(mongo.NewSessionContext(ctx, sess), bson.D{}) + suite.Require().NoError(err) + + clusterTimeVal, err := sess.ClusterTime().LookupErr("$clusterTime", "clusterTime") + suite.Require().NoError(err, "should extract cluster time from %+v", sess.ClusterTime()) + + clusterT, clusterI, ok := clusterTimeVal.TimestampOK() + suite.Require().True(ok, "session cluster time (%s: %v) must be a timestamp", clusterTimeVal.Type, clusterTimeVal) + ts, err := GetNewClusterTime(ctx, logger, suite.srcMongoClient) suite.Require().NoError(err) - suite.Assert().NotZero(ts, "timestamp should be nonzero") + suite.Require().NotZero(ts, "timestamp should be nonzero") + suite.Assert().True(ts.After(primitive.Timestamp{T: clusterT, I: clusterI})) } diff --git a/internal/verifier/migration_verifier.go b/internal/verifier/migration_verifier.go index 7a6b0d39..519fd7be 100644 --- a/internal/verifier/migration_verifier.go +++ b/internal/verifier/migration_verifier.go @@ -258,7 +258,7 @@ func (verifier *Verifier) WritesOff(ctx context.Context) error { verifier.mux.Unlock() - // This has to happen under the lock because the change stream + // This has to happen outside the lock because the change stream // might be inserting docs into the recheck queue, which happens // under the lock. select { diff --git a/vendor/github.com/samber/mo/.gitignore b/vendor/github.com/samber/mo/.gitignore new file mode 100644 index 00000000..7b9b3e77 --- /dev/null +++ b/vendor/github.com/samber/mo/.gitignore @@ -0,0 +1,37 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/go +# Edit at https://www.toptal.com/developers/gitignore?templates=go + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Go Patch ### +/vendor/ +/Godeps/ + +# End of https://www.toptal.com/developers/gitignore/api/go + +cover.out +cover.html +.vscode +.idea \ No newline at end of file diff --git a/vendor/github.com/samber/mo/Dockerfile b/vendor/github.com/samber/mo/Dockerfile new file mode 100644 index 00000000..12672285 --- /dev/null +++ b/vendor/github.com/samber/mo/Dockerfile @@ -0,0 +1,8 @@ + +FROM golang:1.22.5-bullseye + +WORKDIR /go/src/github.com/samber/mo + +COPY Makefile go.* ./ + +RUN make tools diff --git a/vendor/github.com/samber/mo/LICENSE b/vendor/github.com/samber/mo/LICENSE new file mode 100644 index 00000000..c3dc72d9 --- /dev/null +++ b/vendor/github.com/samber/mo/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Samuel Berthe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/samber/mo/Makefile b/vendor/github.com/samber/mo/Makefile new file mode 100644 index 00000000..0ac9abc2 --- /dev/null +++ b/vendor/github.com/samber/mo/Makefile @@ -0,0 +1,44 @@ + +build: + go build -v ./... + +test: + go test -v ./... +watch-test: + reflex -t 50ms -s -- sh -c 'gotest -v ./...' + +bench: + go test -benchmem -count 3 -bench ./... +watch-bench: + reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...' + +coverage: + go test -v -coverprofile=cover.out -covermode=atomic ./... + go tool cover -html=cover.out -o cover.html + +# tools +tools: + go install github.com/cespare/reflex@latest + go install github.com/rakyll/gotest@latest + go install github.com/psampaz/go-mod-outdated@latest + go install github.com/jondot/goweight@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + go get -t -u golang.org/x/tools/cmd/cover + go get -t -u github.com/sonatype-nexus-community/nancy@latest + go mod tidy + +lint: + golangci-lint run --timeout 60s --max-same-issues 50 ./... +lint-fix: + golangci-lint run --timeout 60s --max-same-issues 50 --fix ./... + +audit: + go mod tidy + go list -json -m all | nancy sleuth + +outdated: + go mod tidy + go list -u -m -json all | go-mod-outdated -update -direct + +weight: + goweight diff --git a/vendor/github.com/samber/mo/README.md b/vendor/github.com/samber/mo/README.md new file mode 100644 index 00000000..48db2216 --- /dev/null +++ b/vendor/github.com/samber/mo/README.md @@ -0,0 +1,423 @@ +# mo - Monads + +[![tag](https://img.shields.io/github/tag/samber/mo.svg)](https://github.com/samber/mo/releases) +![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.18-%23007d9c) +[![GoDoc](https://godoc.org/github.com/samber/mo?status.svg)](https://pkg.go.dev/github.com/samber/mo) +![Build Status](https://github.com/samber/mo/actions/workflows/test.yml/badge.svg) +[![Go report](https://goreportcard.com/badge/github.com/samber/mo)](https://goreportcard.com/report/github.com/samber/mo) +[![Coverage](https://img.shields.io/codecov/c/github/samber/do)](https://codecov.io/gh/samber/mo) +[![License](https://img.shields.io/github/license/samber/mo)](./LICENSE) + +🦄 **`samber/mo` brings monads and popular FP abstractions to Go projects. `samber/mo` uses the recent Go 1.18+ Generics.** + +**Inspired by:** + +- Scala +- Rust +- FP-TS + +**See also:** + +- [samber/lo](https://github.com/samber/lo): A Lodash-style Go library based on Go 1.18+ Generics +- [samber/do](https://github.com/samber/do): A dependency injection toolkit based on Go 1.18+ Generics + +**Why this name?** + +I love **short name** for such utility library. This name is similar to "Monad Go" and no Go package uses this name. + +## 💡 Features + +We currently support the following data types: + +- `Option[T]` (Maybe) +- `Result[T]` +- `Either[A, B]` +- `EitherX[T1, ..., TX]` (With X between 3 and 5) +- `Future[T]` +- `IO[T]` +- `IOEither[T]` +- `Task[T]` +- `TaskEither[T]` +- `State[S, A]` + +## 🚀 Install + +```sh +go get github.com/samber/mo@v1 +``` + +This library is v1 and follows SemVer strictly. + +No breaking changes will be made to exported APIs before v2.0.0. + +This library has no dependencies except the Go std lib. + +## 💡 Quick start + +You can import `mo` using: + +```go +import ( + "github.com/samber/mo" +) +``` + +Then use one of the helpers below: + +```go +option1 := mo.Some(42) +// Some(42) + +option1. + FlatMap(func (value int) Option[int] { + return Some(value*2) + }). + FlatMap(func (value int) Option[int] { + return Some(value%2) + }). + FlatMap(func (value int) Option[int] { + return Some(value+21) + }). + OrElse(1234) +// 21 + +option2 := mo.None[int]() +// None + +option2.OrElse(1234) +// 1234 + +option3 := option1.Match( + func(i int) (int, bool) { + // when value is present + return i * 2, true + }, + func() (int, bool) { + // when value is absent + return 0, false + }, +) +// Some(42) +``` + +More examples in [documentation](https://godoc.org/github.com/samber/mo). + +### Tips for lazy developers + +I cannot recommend it, but in case you are too lazy for repeating `mo.` everywhere, you can import the entire library into the namespace. + +```go +import ( + . "github.com/samber/mo" +) +``` + +I take no responsibility on this junk. 😁 💩 + +## 🤠 Documentation and examples + +[GoDoc: https://godoc.org/github.com/samber/mo](https://godoc.org/github.com/samber/mo) + +### Option[T any] + +`Option` is a container for an optional value of type `T`. If value exists, `Option` is of type `Some`. If the value is absent, `Option` is of type `None`. + +Implements: +- `mo.Foldable[T, U]` + +Constructors: + +- `mo.Some()` [doc](https://pkg.go.dev/github.com/samber/mo#Some) - [play](https://go.dev/play/p/iqz2n9n0tDM) +- `mo.None()` [doc](https://pkg.go.dev/github.com/samber/mo#None) - [play](https://go.dev/play/p/yYQPsYCSYlD) +- `mo.TupleToOption()` [doc](https://pkg.go.dev/github.com/samber/mo#TupleToOption) - [play](https://go.dev/play/p/gkrg2pZwOty) +- `mo.EmptyableToOption()` [doc](https://pkg.go.dev/github.com/samber/mo#EmptyableToOption) - [play](https://go.dev/play/p/GSpQQ-q-UES) +- `mo.PointerToOption()` [doc](https://pkg.go.dev/github.com/samber/mo#PointerToOption) - [play](https://go.dev/play/p/yPVMj4DUb-I) + +Methods: + +- `.IsPresent()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.IsPresent) - [play](https://go.dev/play/p/nDqIaiihyCA) +- `.IsAbsent()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.IsAbsent) - [play](https://go.dev/play/p/23e2zqyVOQm) +- `.Size()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Size) - [play](https://go.dev/play/p/7ixCNG1E9l7) +- `.Get()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Get) - [play](https://go.dev/play/p/0-JBa1usZRT) +- `.MustGet()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.MustGet) - [play](https://go.dev/play/p/RVBckjdi5WR) +- `.OrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.OrElse) - [play](https://go.dev/play/p/TrGByFWCzXS) +- `.OrEmpty()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.OrEmpty) - [play](https://go.dev/play/p/SpSUJcE-tQm) +- `.ToPointer()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.ToPointer) - [play](https://go.dev/play/p/N43w92SM-Bs) +- `.ForEach()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.ForEach) +- `.Match()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Match) - [play](https://go.dev/play/p/1V6st3LDJsM) +- `.Map()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Map) - [play](https://go.dev/play/p/mvfP3pcP_eJ) +- `.MapNone()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.MapNone) - [play](https://go.dev/play/p/_KaHWZ6Q17b) +- `.FlatMap()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.FlatMap) - [play](https://go.dev/play/p/OXO-zJx6n5r) +- `.MarshalJSON()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.MarshalJSON) +- `.UnmarshalJSON()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.UnmarshalJSON) +- `.MarshalText()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.MarshalText) +- `.UnmarshalText()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.UnmarshalText) +- `.MarshalBinary()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.MarshalBinary) +- `.UnmarshalBinary()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.UnmarshalBinary) +- `.GobEncode()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.GobEncode) +- `.GobDecode()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.GobDecode) +- `.Scan()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Scan) +- `.Value()` [doc](https://pkg.go.dev/github.com/samber/mo#Option.Value) + +Other: + +- `mo.Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R` [doc](https://pkg.go.dev/github.com/samber/mo#Fold) + +### Result[T any] + +`Result` respresent a result of an action having one of the following output: success or failure. An instance of `Result` is an instance of either `Ok` or `Err`. It could be compared to `Either[error, T]`. + +Implements: +- `mo.Foldable[T, U]` + +Constructors: + +- `mo.Ok()` [doc](https://pkg.go.dev/github.com/samber/mo#Ok) - [play](https://go.dev/play/p/PDwADdzNoyZ) +- `mo.Err()` [doc](https://pkg.go.dev/github.com/samber/mo#Err) - [play](https://go.dev/play/p/PDwADdzNoyZ) +- `mo.Errf()` [doc](https://pkg.go.dev/github.com/samber/mo#Errf) - [play](https://go.dev/play/p/N43w92SM-Bs) +- `mo.TupleToResult()` [doc](https://pkg.go.dev/github.com/samber/mo#TupleToResult) - [play](https://go.dev/play/p/KWjfqQDHQwa) +- `mo.Try()` [doc](https://pkg.go.dev/github.com/samber/mo#Try) - [play](https://go.dev/play/p/ilOlQx-Mx42) + +Methods: + +- `.IsOk()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.IsOk) - [play](https://go.dev/play/p/sfNvBQyZfgU) +- `.IsError()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.IsError) - [play](https://go.dev/play/p/xkV9d464scV) +- `.Error()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.Error) - [play](https://go.dev/play/p/CSkHGTyiXJ5) +- `.Get()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.Get) - [play](https://go.dev/play/p/8KyX3z6TuNo) +- `.MustGet()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.MustGet) - [play](https://go.dev/play/p/8LSlndHoTAE) +- `.OrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.OrElse) - [play](https://go.dev/play/p/MN_ULx0soi6) +- `.OrEmpty()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.OrEmpty) - [play](https://go.dev/play/p/rdKtBmOcMLh) +- `.ToEither()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.ToEither) - [play](https://go.dev/play/p/Uw1Zz6b952q) +- `.ForEach()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.ForEach) +- `.Match()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.Match) - [play](https://go.dev/play/p/-_eFaLJ31co) +- `.Map()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.Map) - [play](https://go.dev/play/p/-ndpN_b_OSc) +- `.MapErr()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.MapErr) - [play](https://go.dev/play/p/WraZixg9GGf) +- `.FlatMap()` [doc](https://pkg.go.dev/github.com/samber/mo#Result.FlatMap) - [play](https://go.dev/play/p/Ud5QjZOqg-7) + +Other: + +- `mo.Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R` [doc](https://pkg.go.dev/github.com/samber/mo#Fold) +- `mo.Do[T any](fn func() T) (result mo.Result[T])` [doc](https://pkg.go.dev/github.com/samber/mo#Do) + +### Either[L any, R any] + +`Either` represents a value of 2 possible types. An instance of `Either` is an instance of either `A` or `B`. + +Implements: +- `mo.Foldable[T, U]` + +Constructors: + +- `mo.Left()` [doc](https://pkg.go.dev/github.com/samber/mo#Left) +- `mo.Right()` [doc](https://pkg.go.dev/github.com/samber/mo#Right) + +Methods: + +- `.IsLeft()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.IsLeft) +- `.IsRight()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.IsRight) +- `.Left()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.Left) +- `.Right()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.Right) +- `.MustLeft()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.MustLeft) +- `.MustRight()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.MustRight) +- `.Unpack()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.Unpack) +- `.LeftOrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.LeftOrElse) +- `.RightOrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.RightOrElse) +- `.LeftOrEmpty()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.LeftOrEmpty) +- `.RightOrEmpty()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.RightOrEmpty) +- `.Swap()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.Swap) +- `.ForEach()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.ForEach) +- `.Match()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.Match) +- `.MapLeft()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.MapLeft) +- `.MapRight()` [doc](https://pkg.go.dev/github.com/samber/mo#Either.MapRight) + +Other: + +- `mo.Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R` [doc](https://pkg.go.dev/github.com/samber/mo#Fold) + +### EitherX[T1, ..., TX] (With X between 3 and 5) + +`EitherX` respresents a value of X possible types. For example, an `Either3` value is either `T1`, `T2` or `T3`. + +Constructors: + +- `mo.NewEitherXArgY()` [doc](https://pkg.go.dev/github.com/samber/mo#NewEither5Arg1). Eg: + - `mo.NewEither3Arg1[A, B, C](A)` + - `mo.NewEither3Arg2[A, B, C](B)` + - `mo.NewEither3Arg3[A, B, C](C)` + - `mo.NewEither4Arg1[A, B, C, D](A)` + - `mo.NewEither4Arg2[A, B, C, D](B)` + - ... + +Methods: + +- `.IsArgX()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.IsArg1) +- `.ArgX()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.Arg1) +- `.MustArgX()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.MustArg1) +- `.Unpack()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.Unpack) +- `.ArgXOrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.Arg1OrElse) +- `.ArgXOrEmpty()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.Arg1OrEmpty) +- `.ForEach()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.ForEach) +- `.Match()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.Match) +- `.MapArgX()` [doc](https://pkg.go.dev/github.com/samber/mo#Either5.MapArg1) + +### Future[T any] + +`Future` represents a value which may or may not currently be available, but will be available at some point, or an exception if that value could not be made available. + +Constructors: + +- `mo.NewFuture()` [doc](https://pkg.go.dev/github.com/samber/mo#NewFuture) + +Methods: + +- `.Then()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Then) +- `.Catch()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Catch) +- `.Finally()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Finally) +- `.Collect()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Collect) +- `.Result()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Result) +- `.Cancel()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Cancel) + +### IO[T any] + +`IO` represents a non-deterministic synchronous computation that can cause side effects, yields a value of type `R` and never fails. + +Constructors: + +- `mo.NewIO()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO) +- `mo.NewIO1()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO1) +- `mo.NewIO2()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO2) +- `mo.NewIO3()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO3) +- `mo.NewIO4()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO4) +- `mo.NewIO5()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIO5) + +Methods: + +- `.Run()` [doc](https://pkg.go.dev/github.com/samber/mo#Future.Run) + +### IOEither[T any] + +`IO` represents a non-deterministic synchronous computation that can cause side effects, yields a value of type `R` and can fail. + +Constructors: + +- `mo.NewIOEither()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither) +- `mo.NewIOEither1()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither1) +- `mo.NewIOEither2()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither2) +- `mo.NewIOEither3()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither3) +- `mo.NewIOEither4()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither4) +- `mo.NewIOEither5()` [doc](https://pkg.go.dev/github.com/samber/mo#NewIOEither5) + +Methods: + +- `.Run()` [doc](https://pkg.go.dev/github.com/samber/mo#IOEither.Run) + +### Task[T any] + +`Task` represents a non-deterministic asynchronous computation that can cause side effects, yields a value of type `R` and never fails. + +Constructors: + +- `mo.NewTask()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask) +- `mo.NewTask1()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask1) +- `mo.NewTask2()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask2) +- `mo.NewTask3()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask3) +- `mo.NewTask4()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask4) +- `mo.NewTask5()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTask5) +- `mo.NewTaskFromIO()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO) +- `mo.NewTaskFromIO1()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO1) +- `mo.NewTaskFromIO2()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO2) +- `mo.NewTaskFromIO3()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO3) +- `mo.NewTaskFromIO4()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO4) +- `mo.NewTaskFromIO5()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskFromIO5) + +Methods: + +- `.Run()` [doc](https://pkg.go.dev/github.com/samber/mo#Task.Run) + +### TaskEither[T any] + +`TaskEither` represents a non-deterministic asynchronous computation that can cause side effects, yields a value of type `R` and can fail. + +Constructors: + +- `mo.NewTaskEither()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskEither) +- `mo.NewTaskEitherFromIOEither()` [doc](https://pkg.go.dev/github.com/samber/mo#NewTaskEitherFromIOEither) + +Methods: + +- `.Run()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Run) +- `.OrElse()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.OrElse) +- `.Match()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Match) +- `.TryCatch()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.TryCatch) +- `.ToTask()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.ToTask) +- `.ToEither()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.ToEither) + +### State[S any, A any] + +`State` represents a function `(S) -> (A, S)`, where `S` is state, `A` is result. + +Constructors: + +- `mo.NewState()` [doc](https://pkg.go.dev/github.com/samber/mo#NewState) +- `mo.ReturnState()` [doc](https://pkg.go.dev/github.com/samber/mo#ReturnState) + +Methods: + +- `.Run()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Run) +- `.Get()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Get) +- `.Modify()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Modify) +- `.Put()` [doc](https://pkg.go.dev/github.com/samber/mo#TaskEither.Put) + +### Foldable[T, U] + +Foldable represents a type that can be folded into a single value based on its state. + +- `mo.Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R` [doc](https://pkg.go.dev/github.com/samber/mo#Fold) + +## 🛩 Benchmark + +// @TODO + +This library does not use `reflect` package. We don't expect overhead. + +## 🤝 Contributing + +- Ping me on Twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :)) +- Fork the [project](https://github.com/samber/mo) +- Fix [open issues](https://github.com/samber/mo/issues) or request new features + +Don't hesitate ;) + +### With Docker + +```bash +docker-compose run --rm dev +``` + +### Without Docker + +```bash +# Install some dev dependencies +make tools + +# Run tests +make test +# or +make watch-test +``` + +## 👤 Contributors + +![Contributors](https://contrib.rocks/image?repo=samber/mo) + +## 💫 Show your support + +Give a ⭐️ if this project helped you! + +[![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber) + +## 📝 License + +Copyright © 2022 [Samuel Berthe](https://github.com/samber). + +This project is [MIT](./LICENSE) licensed. diff --git a/vendor/github.com/samber/mo/do.go b/vendor/github.com/samber/mo/do.go new file mode 100644 index 00000000..42d430c7 --- /dev/null +++ b/vendor/github.com/samber/mo/do.go @@ -0,0 +1,22 @@ +package mo + +import ( + "errors" + "fmt" +) + +// Do executes a function within a monadic context, capturing any errors that occur. +// If the function executes successfully, its result is wrapped in a successful Result. +// If the function panics (indicating a failure), the panic is caught and converted into an error Result. +func Do[T any](fn func() T) (result Result[T]) { + defer func() { + if r := recover(); r != nil { + if err, ok := r.(error); ok { + result = Err[T](err) + } else { + result = Err[T](errors.New(fmt.Sprint(r))) + } + } + }() + return Ok(fn()) +} diff --git a/vendor/github.com/samber/mo/either.go b/vendor/github.com/samber/mo/either.go new file mode 100644 index 00000000..d9c51e99 --- /dev/null +++ b/vendor/github.com/samber/mo/either.go @@ -0,0 +1,189 @@ +package mo + +import "fmt" + +var eitherShouldBeLeftOrRight = fmt.Errorf("either should be Left or Right") +var eitherMissingLeftValue = fmt.Errorf("no such Left value") +var eitherMissingRightValue = fmt.Errorf("no such Right value") + +// Left builds the left side of the Either struct, as opposed to the Right side. +func Left[L any, R any](value L) Either[L, R] { + return Either[L, R]{ + isLeft: true, + left: value, + } +} + +// Right builds the right side of the Either struct, as opposed to the Left side. +func Right[L any, R any](value R) Either[L, R] { + return Either[L, R]{ + isLeft: false, + right: value, + } +} + +// Either respresents a value of 2 possible types. +// An instance of Either is an instance of either A or B. +type Either[L any, R any] struct { + isLeft bool + + left L + right R +} + +// IsLeft returns true if Either is an instance of Left. +func (e Either[L, R]) IsLeft() bool { + return e.isLeft +} + +// IsRight returns true if Either is an instance of Right. +func (e Either[L, R]) IsRight() bool { + return !e.isLeft +} + +// Left returns left value of a Either struct. +func (e Either[L, R]) Left() (L, bool) { + if e.IsLeft() { + return e.left, true + } + return empty[L](), false +} + +// Right returns right value of a Either struct. +func (e Either[L, R]) Right() (R, bool) { + if e.IsRight() { + return e.right, true + } + return empty[R](), false +} + +// MustLeft returns left value of a Either struct or panics. +func (e Either[L, R]) MustLeft() L { + if !e.IsLeft() { + panic(eitherMissingLeftValue) + } + + return e.left +} + +// MustRight returns right value of a Either struct or panics. +func (e Either[L, R]) MustRight() R { + if !e.IsRight() { + panic(eitherMissingRightValue) + } + + return e.right +} + +// Unpack returns all values +func (e Either[L, R]) Unpack() (L, R) { + return e.left, e.right +} + +// LeftOrElse returns left value of a Either struct or fallback. +func (e Either[L, R]) LeftOrElse(fallback L) L { + if e.IsLeft() { + return e.left + } + + return fallback +} + +// RightOrElse returns right value of a Either struct or fallback. +func (e Either[L, R]) RightOrElse(fallback R) R { + if e.IsRight() { + return e.right + } + + return fallback +} + +// LeftOrEmpty returns left value of a Either struct or empty value. +func (e Either[L, R]) LeftOrEmpty() L { + if e.IsLeft() { + return e.left + } + + return empty[L]() +} + +// RightOrEmpty returns right value of a Either struct or empty value. +func (e Either[L, R]) RightOrEmpty() R { + if e.IsRight() { + return e.right + } + + return empty[R]() +} + +// Swap returns the left value in Right and vice versa. +func (e Either[L, R]) Swap() Either[R, L] { + if e.IsLeft() { + return Right[R, L](e.left) + } + + return Left[R, L](e.right) +} + +// ForEach executes the given side-effecting function, depending of value is Left or Right. +func (e Either[L, R]) ForEach(leftCb func(L), rightCb func(R)) { + if e.IsLeft() { + leftCb(e.left) + } else if e.IsRight() { + rightCb(e.right) + } +} + +// Match executes the given function, depending of value is Left or Right, and returns result. +func (e Either[L, R]) Match(onLeft func(L) Either[L, R], onRight func(R) Either[L, R]) Either[L, R] { + if e.IsLeft() { + return onLeft(e.left) + } else if e.IsRight() { + return onRight(e.right) + } + + panic(eitherShouldBeLeftOrRight) +} + +// MapLeft executes the given function, if Either is of type Left, and returns result. +func (e Either[L, R]) MapLeft(mapper func(L) Either[L, R]) Either[L, R] { + if e.IsLeft() { + return mapper(e.left) + } else if e.IsRight() { + return Right[L, R](e.right) + } + + panic(eitherShouldBeLeftOrRight) +} + +// MapRight executes the given function, if Either is of type Right, and returns result. +func (e Either[L, R]) MapRight(mapper func(R) Either[L, R]) Either[L, R] { + if e.isLeft { + return Left[L, R](e.left) + } else if e.IsRight() { + return mapper(e.right) + } + + panic(eitherShouldBeLeftOrRight) +} + +// leftValue returns left value of a Either struct.(implementation of Foldable interface) +// +//nolint:unused +func (e Either[L, R]) leftValue() L { + return e.left +} + +// rightValue returns right value of a Either struct.(implementation of Foldable interface) +// +//nolint:unused +func (e Either[L, R]) rightValue() R { + return e.right +} + +// hasLeft returns true if the Result represents an error state. +// +//nolint:unused +func (e Either[L, R]) hasLeftValue() bool { + return e.isLeft +} diff --git a/vendor/github.com/samber/mo/either3.go b/vendor/github.com/samber/mo/either3.go new file mode 100644 index 00000000..7304420d --- /dev/null +++ b/vendor/github.com/samber/mo/either3.go @@ -0,0 +1,223 @@ +package mo + +import "fmt" + +const ( + either3ArgId1 = iota + either3ArgId2 + either3ArgId3 +) + +var ( + either3InvalidArgumentId = fmt.Errorf("either3 argument should be between 1 and 3") + either3MissingArg1 = fmt.Errorf("either3 doesn't contain expected argument 1") + either3MissingArg2 = fmt.Errorf("either3 doesn't contain expected argument 2") + either3MissingArg3 = fmt.Errorf("either3 doesn't contain expected argument 3") +) + +// NewEither3Arg1 builds the first argument of the Either3 struct. +func NewEither3Arg1[T1 any, T2 any, T3 any](value T1) Either3[T1, T2, T3] { + return Either3[T1, T2, T3]{ + argId: either3ArgId1, + arg1: value, + } +} + +// NewEither3Arg2 builds the second argument of the Either3 struct. +func NewEither3Arg2[T1 any, T2 any, T3 any](value T2) Either3[T1, T2, T3] { + return Either3[T1, T2, T3]{ + argId: either3ArgId2, + arg2: value, + } +} + +// NewEither3Arg3 builds the third argument of the Either3 struct. +func NewEither3Arg3[T1 any, T2 any, T3 any](value T3) Either3[T1, T2, T3] { + return Either3[T1, T2, T3]{ + argId: either3ArgId3, + arg3: value, + } +} + +// Either3 represents a value of 3 possible types. +// An instance of Either3 is an instance of either T1, T2 or T3. +type Either3[T1 any, T2 any, T3 any] struct { + argId int8 + + arg1 T1 + arg2 T2 + arg3 T3 +} + +// IsArg1 returns true if Either3 uses the first argument. +func (e Either3[T1, T2, T3]) IsArg1() bool { + return e.argId == either3ArgId1 +} + +// IsArg2 returns true if Either3 uses the second argument. +func (e Either3[T1, T2, T3]) IsArg2() bool { + return e.argId == either3ArgId2 +} + +// IsArg3 returns true if Either3 uses the third argument. +func (e Either3[T1, T2, T3]) IsArg3() bool { + return e.argId == either3ArgId3 +} + +// Arg1 returns the first argument of a Either3 struct. +func (e Either3[T1, T2, T3]) Arg1() (T1, bool) { + if e.IsArg1() { + return e.arg1, true + } + return empty[T1](), false +} + +// Arg2 returns the second argument of a Either3 struct. +func (e Either3[T1, T2, T3]) Arg2() (T2, bool) { + if e.IsArg2() { + return e.arg2, true + } + return empty[T2](), false +} + +// Arg3 returns the third argument of a Either3 struct. +func (e Either3[T1, T2, T3]) Arg3() (T3, bool) { + if e.IsArg3() { + return e.arg3, true + } + return empty[T3](), false +} + +// MustArg1 returns the first argument of a Either3 struct or panics. +func (e Either3[T1, T2, T3]) MustArg1() T1 { + if !e.IsArg1() { + panic(either3MissingArg1) + } + return e.arg1 +} + +// MustArg2 returns the second argument of a Either3 struct or panics. +func (e Either3[T1, T2, T3]) MustArg2() T2 { + if !e.IsArg2() { + panic(either3MissingArg2) + } + return e.arg2 +} + +// MustArg3 returns the third argument of a Either3 struct or panics. +func (e Either3[T1, T2, T3]) MustArg3() T3 { + if !e.IsArg3() { + panic(either3MissingArg3) + } + return e.arg3 +} + +// Unpack returns all values +func (e Either3[T1, T2, T3]) Unpack() (T1, T2, T3) { + return e.arg1, e.arg2, e.arg3 +} + +// Arg1OrElse returns the first argument of a Either3 struct or fallback. +func (e Either3[T1, T2, T3]) Arg1OrElse(fallback T1) T1 { + if e.IsArg1() { + return e.arg1 + } + return fallback +} + +// Arg2OrElse returns the second argument of a Either3 struct or fallback. +func (e Either3[T1, T2, T3]) Arg2OrElse(fallback T2) T2 { + if e.IsArg2() { + return e.arg2 + } + return fallback +} + +// Arg3OrElse returns the third argument of a Either3 struct or fallback. +func (e Either3[T1, T2, T3]) Arg3OrElse(fallback T3) T3 { + if e.IsArg3() { + return e.arg3 + } + return fallback +} + +// Arg1OrEmpty returns the first argument of a Either3 struct or empty value. +func (e Either3[T1, T2, T3]) Arg1OrEmpty() T1 { + if e.IsArg1() { + return e.arg1 + } + return empty[T1]() +} + +// Arg2OrEmpty returns the second argument of a Either3 struct or empty value. +func (e Either3[T1, T2, T3]) Arg2OrEmpty() T2 { + if e.IsArg2() { + return e.arg2 + } + return empty[T2]() +} + +// Arg3OrEmpty returns the third argument of a Either3 struct or empty value. +func (e Either3[T1, T2, T3]) Arg3OrEmpty() T3 { + if e.IsArg3() { + return e.arg3 + } + return empty[T3]() +} + +// ForEach executes the given side-effecting function, depending of the argument set. +func (e Either3[T1, T2, T3]) ForEach(arg1Cb func(T1), arg2Cb func(T2), arg3Cb func(T3)) { + switch e.argId { + case either3ArgId1: + arg1Cb(e.arg1) + case either3ArgId2: + arg2Cb(e.arg2) + case either3ArgId3: + arg3Cb(e.arg3) + } +} + +// Match executes the given function, depending of the argument set, and returns result. +func (e Either3[T1, T2, T3]) Match( + onArg1 func(T1) Either3[T1, T2, T3], + onArg2 func(T2) Either3[T1, T2, T3], + onArg3 func(T3) Either3[T1, T2, T3]) Either3[T1, T2, T3] { + + switch e.argId { + case either3ArgId1: + return onArg1(e.arg1) + case either3ArgId2: + return onArg2(e.arg2) + case either3ArgId3: + return onArg3(e.arg3) + } + + panic(either3InvalidArgumentId) +} + +// MapArg1 executes the given function, if Either3 use the first argument, and returns result. +func (e Either3[T1, T2, T3]) MapArg1(mapper func(T1) Either3[T1, T2, T3]) Either3[T1, T2, T3] { + if e.IsArg1() { + return mapper(e.arg1) + } + + return e +} + +// MapArg2 executes the given function, if Either3 use the second argument, and returns result. +func (e Either3[T1, T2, T3]) MapArg2(mapper func(T2) Either3[T1, T2, T3]) Either3[T1, T2, T3] { + if e.IsArg2() { + return mapper(e.arg2) + } + + return e +} + +// MapArg3 executes the given function, if Either3 use the third argument, and returns result. +func (e Either3[T1, T2, T3]) MapArg3(mapper func(T3) Either3[T1, T2, T3]) Either3[T1, T2, T3] { + if e.IsArg3() { + return mapper(e.arg3) + } + + return e +} diff --git a/vendor/github.com/samber/mo/either4.go b/vendor/github.com/samber/mo/either4.go new file mode 100644 index 00000000..16e207a8 --- /dev/null +++ b/vendor/github.com/samber/mo/either4.go @@ -0,0 +1,285 @@ +package mo + +import "fmt" + +const ( + either4ArgId1 = iota + either4ArgId2 + either4ArgId3 + either4ArgId4 +) + +var ( + either4InvalidArgumentId = fmt.Errorf("either4 argument should be between 1 and 4") + either4MissingArg1 = fmt.Errorf("either4 doesn't contain expected argument 1") + either4MissingArg2 = fmt.Errorf("either4 doesn't contain expected argument 2") + either4MissingArg3 = fmt.Errorf("either4 doesn't contain expected argument 3") + either4MissingArg4 = fmt.Errorf("either4 doesn't contain expected argument 4") +) + +// NewEither4Arg1 builds the first argument of the Either4 struct. +func NewEither4Arg1[T1 any, T2 any, T3 any, T4 any](value T1) Either4[T1, T2, T3, T4] { + return Either4[T1, T2, T3, T4]{ + argId: either4ArgId1, + arg1: value, + } +} + +// NewEither4Arg2 builds the second argument of the Either4 struct. +func NewEither4Arg2[T1 any, T2 any, T3 any, T4 any](value T2) Either4[T1, T2, T3, T4] { + return Either4[T1, T2, T3, T4]{ + argId: either4ArgId2, + arg2: value, + } +} + +// NewEither4Arg3 builds the third argument of the Either4 struct. +func NewEither4Arg3[T1 any, T2 any, T3 any, T4 any](value T3) Either4[T1, T2, T3, T4] { + return Either4[T1, T2, T3, T4]{ + argId: either4ArgId3, + arg3: value, + } +} + +// NewEither4Arg4 builds the fourth argument of the Either4 struct. +func NewEither4Arg4[T1 any, T2 any, T3 any, T4 any](value T4) Either4[T1, T2, T3, T4] { + return Either4[T1, T2, T3, T4]{ + argId: either4ArgId4, + arg4: value, + } +} + +// Either4 respresents a value of 4 possible types. +// An instance of Either4 is an instance of either T1, T2, T3 or T4. +type Either4[T1 any, T2 any, T3 any, T4 any] struct { + argId int8 + + arg1 T1 + arg2 T2 + arg3 T3 + arg4 T4 +} + +// IsArg1 returns true if Either4 uses the first argument. +func (e Either4[T1, T2, T3, T4]) IsArg1() bool { + return e.argId == either4ArgId1 +} + +// IsArg2 returns true if Either4 uses the second argument. +func (e Either4[T1, T2, T3, T4]) IsArg2() bool { + return e.argId == either4ArgId2 +} + +// IsArg3 returns true if Either4 uses the third argument. +func (e Either4[T1, T2, T3, T4]) IsArg3() bool { + return e.argId == either4ArgId3 +} + +// IsArg4 returns true if Either4 uses the fourth argument. +func (e Either4[T1, T2, T3, T4]) IsArg4() bool { + return e.argId == either4ArgId4 +} + +// Arg1 returns the first argument of a Either4 struct. +func (e Either4[T1, T2, T3, T4]) Arg1() (T1, bool) { + if e.IsArg1() { + return e.arg1, true + } + return empty[T1](), false +} + +// Arg2 returns the second argument of a Either4 struct. +func (e Either4[T1, T2, T3, T4]) Arg2() (T2, bool) { + if e.IsArg2() { + return e.arg2, true + } + return empty[T2](), false +} + +// Arg3 returns the third argument of a Either4 struct. +func (e Either4[T1, T2, T3, T4]) Arg3() (T3, bool) { + if e.IsArg3() { + return e.arg3, true + } + return empty[T3](), false +} + +// Arg4 returns the fourth argument of a Either4 struct. +func (e Either4[T1, T2, T3, T4]) Arg4() (T4, bool) { + if e.IsArg4() { + return e.arg4, true + } + return empty[T4](), false +} + +// MustArg1 returns the first argument of a Either4 struct or panics. +func (e Either4[T1, T2, T3, T4]) MustArg1() T1 { + if !e.IsArg1() { + panic(either4MissingArg1) + } + return e.arg1 +} + +// MustArg2 returns the second argument of a Either4 struct or panics. +func (e Either4[T1, T2, T3, T4]) MustArg2() T2 { + if !e.IsArg2() { + panic(either4MissingArg2) + } + return e.arg2 +} + +// MustArg3 returns the third argument of a Either4 struct or panics. +func (e Either4[T1, T2, T3, T4]) MustArg3() T3 { + if !e.IsArg3() { + panic(either4MissingArg3) + } + return e.arg3 +} + +// MustArg4 returns the fourth argument of a Either4 struct or panics. +func (e Either4[T1, T2, T3, T4]) MustArg4() T4 { + if !e.IsArg4() { + panic(either4MissingArg4) + } + return e.arg4 +} + +// Unpack returns all values +func (e Either4[T1, T2, T3, T4]) Unpack() (T1, T2, T3, T4) { + return e.arg1, e.arg2, e.arg3, e.arg4 +} + +// Arg1OrElse returns the first argument of a Either4 struct or fallback. +func (e Either4[T1, T2, T3, T4]) Arg1OrElse(fallback T1) T1 { + if e.IsArg1() { + return e.arg1 + } + return fallback +} + +// Arg2OrElse returns the second argument of a Either4 struct or fallback. +func (e Either4[T1, T2, T3, T4]) Arg2OrElse(fallback T2) T2 { + if e.IsArg2() { + return e.arg2 + } + return fallback +} + +// Arg3OrElse returns the third argument of a Either4 struct or fallback. +func (e Either4[T1, T2, T3, T4]) Arg3OrElse(fallback T3) T3 { + if e.IsArg3() { + return e.arg3 + } + return fallback +} + +// Arg4OrElse returns the fourth argument of a Either4 struct or fallback. +func (e Either4[T1, T2, T3, T4]) Arg4OrElse(fallback T4) T4 { + if e.IsArg4() { + return e.arg4 + } + return fallback +} + +// Arg1OrEmpty returns the first argument of a Either4 struct or empty value. +func (e Either4[T1, T2, T3, T4]) Arg1OrEmpty() T1 { + if e.IsArg1() { + return e.arg1 + } + return empty[T1]() +} + +// Arg2OrEmpty returns the second argument of a Either4 struct or empty value. +func (e Either4[T1, T2, T3, T4]) Arg2OrEmpty() T2 { + if e.IsArg2() { + return e.arg2 + } + return empty[T2]() +} + +// Arg3OrEmpty returns the third argument of a Either4 struct or empty value. +func (e Either4[T1, T2, T3, T4]) Arg3OrEmpty() T3 { + if e.IsArg3() { + return e.arg3 + } + return empty[T3]() +} + +// Arg4OrEmpty returns the fourth argument of a Either4 struct or empty value. +func (e Either4[T1, T2, T3, T4]) Arg4OrEmpty() T4 { + if e.IsArg4() { + return e.arg4 + } + return empty[T4]() +} + +// ForEach executes the given side-effecting function, depending of the argument set. +func (e Either4[T1, T2, T3, T4]) ForEach(arg1Cb func(T1), arg2Cb func(T2), arg3Cb func(T3), arg4Cb func(T4)) { + switch e.argId { + case either4ArgId1: + arg1Cb(e.arg1) + case either4ArgId2: + arg2Cb(e.arg2) + case either4ArgId3: + arg3Cb(e.arg3) + case either4ArgId4: + arg4Cb(e.arg4) + } +} + +// Match executes the given function, depending of the argument set, and returns result. +func (e Either4[T1, T2, T3, T4]) Match( + onArg1 func(T1) Either4[T1, T2, T3, T4], + onArg2 func(T2) Either4[T1, T2, T3, T4], + onArg3 func(T3) Either4[T1, T2, T3, T4], + onArg4 func(T4) Either4[T1, T2, T3, T4]) Either4[T1, T2, T3, T4] { + + switch e.argId { + case either4ArgId1: + return onArg1(e.arg1) + case either4ArgId2: + return onArg2(e.arg2) + case either4ArgId3: + return onArg3(e.arg3) + case either4ArgId4: + return onArg4(e.arg4) + } + + panic(either4InvalidArgumentId) +} + +// MapArg1 executes the given function, if Either4 use the first argument, and returns result. +func (e Either4[T1, T2, T3, T4]) MapArg1(mapper func(T1) Either4[T1, T2, T3, T4]) Either4[T1, T2, T3, T4] { + if e.IsArg1() { + return mapper(e.arg1) + } + + return e +} + +// MapArg2 executes the given function, if Either4 use the second argument, and returns result. +func (e Either4[T1, T2, T3, T4]) MapArg2(mapper func(T2) Either4[T1, T2, T3, T4]) Either4[T1, T2, T3, T4] { + if e.IsArg2() { + return mapper(e.arg2) + } + + return e +} + +// MapArg3 executes the given function, if Either4 use the third argument, and returns result. +func (e Either4[T1, T2, T3, T4]) MapArg3(mapper func(T3) Either4[T1, T2, T3, T4]) Either4[T1, T2, T3, T4] { + if e.IsArg3() { + return mapper(e.arg3) + } + + return e +} + +// MapArg4 executes the given function, if Either4 use the fourth argument, and returns result. +func (e Either4[T1, T2, T3, T4]) MapArg4(mapper func(T4) Either4[T1, T2, T3, T4]) Either4[T1, T2, T3, T4] { + if e.IsArg4() { + return mapper(e.arg4) + } + + return e +} diff --git a/vendor/github.com/samber/mo/either5.go b/vendor/github.com/samber/mo/either5.go new file mode 100644 index 00000000..26155423 --- /dev/null +++ b/vendor/github.com/samber/mo/either5.go @@ -0,0 +1,347 @@ +package mo + +import "fmt" + +const ( + either5ArgId1 = iota + either5ArgId2 + either5ArgId3 + either5ArgId4 + either5ArgId5 +) + +var ( + either5InvalidArgumentId = fmt.Errorf("either5 argument should be between 1 and 5") + either5MissingArg1 = fmt.Errorf("either5 doesn't contain expected argument 1") + either5MissingArg2 = fmt.Errorf("either5 doesn't contain expected argument 2") + either5MissingArg3 = fmt.Errorf("either5 doesn't contain expected argument 3") + either5MissingArg4 = fmt.Errorf("either5 doesn't contain expected argument 4") + either5MissingArg5 = fmt.Errorf("either5 doesn't contain expected argument 5") +) + +// NewEither5Arg1 builds the first argument of the Either5 struct. +func NewEither5Arg1[T1 any, T2 any, T3 any, T4 any, T5 any](value T1) Either5[T1, T2, T3, T4, T5] { + return Either5[T1, T2, T3, T4, T5]{ + argId: either5ArgId1, + arg1: value, + } +} + +// NewEither5Arg2 builds the second argument of the Either5 struct. +func NewEither5Arg2[T1 any, T2 any, T3 any, T4 any, T5 any](value T2) Either5[T1, T2, T3, T4, T5] { + return Either5[T1, T2, T3, T4, T5]{ + argId: either5ArgId2, + arg2: value, + } +} + +// NewEither5Arg3 builds the third argument of the Either5 struct. +func NewEither5Arg3[T1 any, T2 any, T3 any, T4 any, T5 any](value T3) Either5[T1, T2, T3, T4, T5] { + return Either5[T1, T2, T3, T4, T5]{ + argId: either5ArgId3, + arg3: value, + } +} + +// NewEither5Arg4 builds the fourth argument of the Either5 struct. +func NewEither5Arg4[T1 any, T2 any, T3 any, T4 any, T5 any](value T4) Either5[T1, T2, T3, T4, T5] { + return Either5[T1, T2, T3, T4, T5]{ + argId: either5ArgId4, + arg4: value, + } +} + +// NewEither5Arg5 builds the fith argument of the Either5 struct. +func NewEither5Arg5[T1 any, T2 any, T3 any, T4 any, T5 any](value T5) Either5[T1, T2, T3, T4, T5] { + return Either5[T1, T2, T3, T4, T5]{ + argId: either5ArgId5, + arg5: value, + } +} + +// Either5 respresents a value of 5 possible types. +// An instance of Either5 is an instance of either T1, T2, T3, T4, or T5. +type Either5[T1 any, T2 any, T3 any, T4 any, T5 any] struct { + argId int8 + + arg1 T1 + arg2 T2 + arg3 T3 + arg4 T4 + arg5 T5 +} + +// IsArg1 returns true if Either5 uses the first argument. +func (e Either5[T1, T2, T3, T4, T5]) IsArg1() bool { + return e.argId == either5ArgId1 +} + +// IsArg2 returns true if Either5 uses the second argument. +func (e Either5[T1, T2, T3, T4, T5]) IsArg2() bool { + return e.argId == either5ArgId2 +} + +// IsArg3 returns true if Either5 uses the third argument. +func (e Either5[T1, T2, T3, T4, T5]) IsArg3() bool { + return e.argId == either5ArgId3 +} + +// IsArg4 returns true if Either5 uses the fourth argument. +func (e Either5[T1, T2, T3, T4, T5]) IsArg4() bool { + return e.argId == either5ArgId4 +} + +// IsArg5 returns true if Either5 uses the fith argument. +func (e Either5[T1, T2, T3, T4, T5]) IsArg5() bool { + return e.argId == either5ArgId5 +} + +// Arg1 returns the first argument of a Either5 struct. +func (e Either5[T1, T2, T3, T4, T5]) Arg1() (T1, bool) { + if e.IsArg1() { + return e.arg1, true + } + return empty[T1](), false +} + +// Arg2 returns the second argument of a Either5 struct. +func (e Either5[T1, T2, T3, T4, T5]) Arg2() (T2, bool) { + if e.IsArg2() { + return e.arg2, true + } + return empty[T2](), false +} + +// Arg3 returns the third argument of a Either5 struct. +func (e Either5[T1, T2, T3, T4, T5]) Arg3() (T3, bool) { + if e.IsArg3() { + return e.arg3, true + } + return empty[T3](), false +} + +// Arg4 returns the fourth argument of a Either5 struct. +func (e Either5[T1, T2, T3, T4, T5]) Arg4() (T4, bool) { + if e.IsArg4() { + return e.arg4, true + } + return empty[T4](), false +} + +// Arg5 returns the fith argument of a Either5 struct. +func (e Either5[T1, T2, T3, T4, T5]) Arg5() (T5, bool) { + if e.IsArg5() { + return e.arg5, true + } + return empty[T5](), false +} + +// MustArg1 returns the first argument of a Either5 struct or panics. +func (e Either5[T1, T2, T3, T4, T5]) MustArg1() T1 { + if !e.IsArg1() { + panic(either5MissingArg1) + } + return e.arg1 +} + +// MustArg2 returns the second argument of a Either5 struct or panics. +func (e Either5[T1, T2, T3, T4, T5]) MustArg2() T2 { + if !e.IsArg2() { + panic(either5MissingArg2) + } + return e.arg2 +} + +// MustArg3 returns the third argument of a Either5 struct or panics. +func (e Either5[T1, T2, T3, T4, T5]) MustArg3() T3 { + if !e.IsArg3() { + panic(either5MissingArg3) + } + return e.arg3 +} + +// MustArg4 returns the fourth argument of a Either5 struct or panics. +func (e Either5[T1, T2, T3, T4, T5]) MustArg4() T4 { + if !e.IsArg4() { + panic(either5MissingArg4) + } + return e.arg4 +} + +// MustArg5 returns the fith argument of a Either5 struct or panics. +func (e Either5[T1, T2, T3, T4, T5]) MustArg5() T5 { + if !e.IsArg5() { + panic(either5MissingArg5) + } + return e.arg5 +} + +// Unpack returns all values +func (e Either5[T1, T2, T3, T4, T5]) Unpack() (T1, T2, T3, T4, T5) { + return e.arg1, e.arg2, e.arg3, e.arg4, e.arg5 +} + +// Arg1OrElse returns the first argument of a Either5 struct or fallback. +func (e Either5[T1, T2, T3, T4, T5]) Arg1OrElse(fallback T1) T1 { + if e.IsArg1() { + return e.arg1 + } + return fallback +} + +// Arg2OrElse returns the second argument of a Either5 struct or fallback. +func (e Either5[T1, T2, T3, T4, T5]) Arg2OrElse(fallback T2) T2 { + if e.IsArg2() { + return e.arg2 + } + return fallback +} + +// Arg3OrElse returns the third argument of a Either5 struct or fallback. +func (e Either5[T1, T2, T3, T4, T5]) Arg3OrElse(fallback T3) T3 { + if e.IsArg3() { + return e.arg3 + } + return fallback +} + +// Arg4OrElse returns the fourth argument of a Either5 struct or fallback. +func (e Either5[T1, T2, T3, T4, T5]) Arg4OrElse(fallback T4) T4 { + if e.IsArg4() { + return e.arg4 + } + return fallback +} + +// Arg5OrElse returns the fith argument of a Either5 struct or fallback. +func (e Either5[T1, T2, T3, T4, T5]) Arg5OrElse(fallback T5) T5 { + if e.IsArg5() { + return e.arg5 + } + return fallback +} + +// Arg1OrEmpty returns the first argument of a Either5 struct or empty value. +func (e Either5[T1, T2, T3, T4, T5]) Arg1OrEmpty() T1 { + if e.IsArg1() { + return e.arg1 + } + return empty[T1]() +} + +// Arg2OrEmpty returns the second argument of a Either5 struct or empty value. +func (e Either5[T1, T2, T3, T4, T5]) Arg2OrEmpty() T2 { + if e.IsArg2() { + return e.arg2 + } + return empty[T2]() +} + +// Arg3OrEmpty returns the third argument of a Either5 struct or empty value. +func (e Either5[T1, T2, T3, T4, T5]) Arg3OrEmpty() T3 { + if e.IsArg3() { + return e.arg3 + } + return empty[T3]() +} + +// Arg4OrEmpty returns the fourth argument of a Either5 struct or empty value. +func (e Either5[T1, T2, T3, T4, T5]) Arg4OrEmpty() T4 { + if e.IsArg4() { + return e.arg4 + } + return empty[T4]() +} + +// Arg5OrEmpty returns the fifth argument of a Either5 struct or empty value. +func (e Either5[T1, T2, T3, T4, T5]) Arg5OrEmpty() T5 { + if e.IsArg5() { + return e.arg5 + } + return empty[T5]() +} + +// ForEach executes the given side-effecting function, depending of the argument set. +func (e Either5[T1, T2, T3, T4, T5]) ForEach(arg1Cb func(T1), arg2Cb func(T2), arg3Cb func(T3), arg4Cb func(T4), arg5Cb func(T5)) { + switch e.argId { + case either5ArgId1: + arg1Cb(e.arg1) + case either5ArgId2: + arg2Cb(e.arg2) + case either5ArgId3: + arg3Cb(e.arg3) + case either5ArgId4: + arg4Cb(e.arg4) + case either5ArgId5: + arg5Cb(e.arg5) + } +} + +// Match executes the given function, depending of the argument set, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) Match( + onArg1 func(T1) Either5[T1, T2, T3, T4, T5], + onArg2 func(T2) Either5[T1, T2, T3, T4, T5], + onArg3 func(T3) Either5[T1, T2, T3, T4, T5], + onArg4 func(T4) Either5[T1, T2, T3, T4, T5], + onArg5 func(T5) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + + switch e.argId { + case either5ArgId1: + return onArg1(e.arg1) + case either5ArgId2: + return onArg2(e.arg2) + case either5ArgId3: + return onArg3(e.arg3) + case either5ArgId4: + return onArg4(e.arg4) + case either5ArgId5: + return onArg5(e.arg5) + } + + panic(either5InvalidArgumentId) +} + +// MapArg1 executes the given function, if Either5 use the first argument, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) MapArg1(mapper func(T1) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + if e.IsArg1() { + return mapper(e.arg1) + } + + return e +} + +// MapArg2 executes the given function, if Either5 use the second argument, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) MapArg2(mapper func(T2) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + if e.IsArg2() { + return mapper(e.arg2) + } + + return e +} + +// MapArg3 executes the given function, if Either5 use the third argument, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) MapArg3(mapper func(T3) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + if e.IsArg3() { + return mapper(e.arg3) + } + + return e +} + +// MapArg4 executes the given function, if Either5 use the fourth argument, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) MapArg4(mapper func(T4) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + if e.IsArg4() { + return mapper(e.arg4) + } + + return e +} + +// MapArg5 executes the given function, if Either5 use the fith argument, and returns result. +func (e Either5[T1, T2, T3, T4, T5]) MapArg5(mapper func(T5) Either5[T1, T2, T3, T4, T5]) Either5[T1, T2, T3, T4, T5] { + if e.IsArg5() { + return mapper(e.arg5) + } + + return e +} diff --git a/vendor/github.com/samber/mo/fold.go b/vendor/github.com/samber/mo/fold.go new file mode 100644 index 00000000..63b83d39 --- /dev/null +++ b/vendor/github.com/samber/mo/fold.go @@ -0,0 +1,28 @@ +package mo + +// Foldable represents a type that can be folded into a single value +// based on its state. +// +// - T: the type of the value in the failure state (e.g., an error type). +// - U: the type of the value in the success state. +type Foldable[T any, U any] interface { + leftValue() T + rightValue() U + hasLeftValue() bool +} + +// Fold applies one of the two functions based on the state of the Foldable type, +// and it returns the result of applying either successFunc or failureFunc. +// +// - T: the type of the failure value (e.g., an error type) +// - U: the type of the success value +// - R: the type of the return value from the folding functions +// +// successFunc is applied when the Foldable is in the success state (i.e., isLeft() is false). +// failureFunc is applied when the Foldable is in the failure state (i.e., isLeft() is true). +func Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R { + if f.hasLeftValue() { + return failureFunc(f.leftValue()) + } + return successFunc(f.rightValue()) +} diff --git a/vendor/github.com/samber/mo/future.go b/vendor/github.com/samber/mo/future.go new file mode 100644 index 00000000..f8e14071 --- /dev/null +++ b/vendor/github.com/samber/mo/future.go @@ -0,0 +1,183 @@ +package mo + +import ( + "sync" +) + +// NewFuture instanciate a new future. +func NewFuture[T any](cb func(resolve func(T), reject func(error))) *Future[T] { + future := Future[T]{ + cb: cb, + cancelCb: func() {}, + done: make(chan struct{}), + } + + future.active() + + return &future +} + +// Future represents a value which may or may not currently be available, but will be +// available at some point, or an exception if that value could not be made available. +type Future[T any] struct { + mu sync.Mutex + + cb func(func(T), func(error)) + cancelCb func() + next *Future[T] + done chan struct{} + result Result[T] +} + +func (f *Future[T]) active() { + go f.cb(f.resolve, f.reject) +} + +func (f *Future[T]) activeSync() { + f.cb(f.resolve, f.reject) +} + +func (f *Future[T]) resolve(value T) { + f.mu.Lock() + defer f.mu.Unlock() + + f.result = Ok(value) + if f.next != nil { + f.next.activeSync() + } + close(f.done) +} + +func (f *Future[T]) reject(err error) { + f.mu.Lock() + defer f.mu.Unlock() + + f.result = Err[T](err) + if f.next != nil { + f.next.activeSync() + } + close(f.done) +} + +// Then is called when Future is resolved. It returns a new Future. +func (f *Future[T]) Then(cb func(T) (T, error)) *Future[T] { + f.mu.Lock() + defer f.mu.Unlock() + + f.next = &Future[T]{ + cb: func(resolve func(T), reject func(error)) { + if f.result.IsError() { + reject(f.result.Error()) + return + } + newValue, err := cb(f.result.MustGet()) + if err != nil { + reject(err) + return + } + resolve(newValue) + }, + cancelCb: func() { + f.Cancel() + }, + done: make(chan struct{}), + } + + select { + case <-f.done: + f.next.active() + default: + } + return f.next +} + +// Catch is called when Future is rejected. It returns a new Future. +func (f *Future[T]) Catch(cb func(error) (T, error)) *Future[T] { + f.mu.Lock() + defer f.mu.Unlock() + + f.next = &Future[T]{ + cb: func(resolve func(T), reject func(error)) { + if f.result.IsOk() { + resolve(f.result.MustGet()) + return + } + newValue, err := cb(f.result.Error()) + if err != nil { + reject(err) + return + } + resolve(newValue) + }, + cancelCb: func() { + f.Cancel() + }, + done: make(chan struct{}), + } + + select { + case <-f.done: + f.next.active() + default: + } + return f.next +} + +// Finally is called when Future is processed either resolved or rejected. It returns a new Future. +func (f *Future[T]) Finally(cb func(T, error) (T, error)) *Future[T] { + f.mu.Lock() + defer f.mu.Unlock() + + f.next = &Future[T]{ + cb: func(resolve func(T), reject func(error)) { + newValue, err := cb(f.result.Get()) + if err != nil { + reject(err) + return + } + resolve(newValue) + }, + cancelCb: func() { + f.Cancel() + }, + done: make(chan struct{}), + } + + select { + case <-f.done: + f.next.active() + default: + } + return f.next +} + +// Cancel cancels the Future chain. +func (f *Future[T]) Cancel() { + f.mu.Lock() + defer f.mu.Unlock() + + f.next = nil + if f.cancelCb != nil { + f.cancelCb() + } +} + +// Collect awaits and return result of the Future. +func (f *Future[T]) Collect() (T, error) { + <-f.done + return f.result.Get() +} + +// Result wraps Collect and returns a Result. +func (f *Future[T]) Result() Result[T] { + return TupleToResult(f.Collect()) +} + +// Either wraps Collect and returns a Either. +func (f *Future[T]) Either() Either[error, T] { + v, err := f.Collect() + if err != nil { + return Left[error, T](err) + } + return Right[error, T](v) +} diff --git a/vendor/github.com/samber/mo/io.go b/vendor/github.com/samber/mo/io.go new file mode 100644 index 00000000..6dae4f3b --- /dev/null +++ b/vendor/github.com/samber/mo/io.go @@ -0,0 +1,109 @@ +package mo + +// NewIO instanciates a new IO. +func NewIO[R any](f f0[R]) IO[R] { + return IO[R]{ + unsafePerform: f, + } +} + +// IO represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO[R any] struct { + unsafePerform f0[R] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO[R]) Run() R { + return io.unsafePerform() +} + +// NewIO1 instanciates a new IO1. +func NewIO1[R any, A any](f f1[R, A]) IO1[R, A] { + return IO1[R, A]{ + unsafePerform: f, + } +} + +// IO1 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO1[R any, A any] struct { + unsafePerform f1[R, A] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO1[R, A]) Run(a A) R { + return io.unsafePerform(a) +} + +// NewIO2 instanciates a new IO2. +func NewIO2[R any, A any, B any](f f2[R, A, B]) IO2[R, A, B] { + return IO2[R, A, B]{ + unsafePerform: f, + } +} + +// IO2 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO2[R any, A any, B any] struct { + unsafePerform f2[R, A, B] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO2[R, A, B]) Run(a A, b B) R { + return io.unsafePerform(a, b) +} + +// NewIO3 instanciates a new IO3. +func NewIO3[R any, A any, B any, C any](f f3[R, A, B, C]) IO3[R, A, B, C] { + return IO3[R, A, B, C]{ + unsafePerform: f, + } +} + +// IO3 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO3[R any, A any, B any, C any] struct { + unsafePerform f3[R, A, B, C] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO3[R, A, B, C]) Run(a A, b B, c C) R { + return io.unsafePerform(a, b, c) +} + +// NewIO4 instanciates a new IO4. +func NewIO4[R any, A any, B any, C any, D any](f f4[R, A, B, C, D]) IO4[R, A, B, C, D] { + return IO4[R, A, B, C, D]{ + unsafePerform: f, + } +} + +// IO4 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO4[R any, A any, B any, C any, D any] struct { + unsafePerform f4[R, A, B, C, D] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO4[R, A, B, C, D]) Run(a A, b B, c C, d D) R { + return io.unsafePerform(a, b, c, d) +} + +// NewIO5 instanciates a new IO5. +func NewIO5[R any, A any, B any, C any, D any, E any](f f5[R, A, B, C, D, E]) IO5[R, A, B, C, D, E] { + return IO5[R, A, B, C, D, E]{ + unsafePerform: f, + } +} + +// IO5 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type IO5[R any, A any, B any, C any, D any, E any] struct { + unsafePerform f5[R, A, B, C, D, E] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IO5[R, A, B, C, D, E]) Run(a A, b B, c C, d D, e E) R { + return io.unsafePerform(a, b, c, d, e) +} diff --git a/vendor/github.com/samber/mo/io_either.go b/vendor/github.com/samber/mo/io_either.go new file mode 100644 index 00000000..5aa02bcc --- /dev/null +++ b/vendor/github.com/samber/mo/io_either.go @@ -0,0 +1,139 @@ +package mo + +// NewIOEither instanciates a new IO. +func NewIOEither[R any](f fe0[R]) IOEither[R] { + return IOEither[R]{ + unsafePerform: f, + } +} + +// IOEither represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither[R any] struct { + unsafePerform fe0[R] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither[R]) Run() Either[error, R] { + v, err := io.unsafePerform() + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} + +// NewIOEither1 instanciates a new IO1. +func NewIOEither1[R any, A any](f fe1[R, A]) IOEither1[R, A] { + return IOEither1[R, A]{ + unsafePerform: f, + } +} + +// IOEither1 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither1[R any, A any] struct { + unsafePerform fe1[R, A] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither1[R, A]) Run(a A) Either[error, R] { + v, err := io.unsafePerform(a) + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} + +// NewIOEither2 instanciates a new IO2. +func NewIOEither2[R any, A any, B any](f fe2[R, A, B]) IOEither2[R, A, B] { + return IOEither2[R, A, B]{ + unsafePerform: f, + } +} + +// IOEither2 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither2[R any, A any, B any] struct { + unsafePerform fe2[R, A, B] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither2[R, A, B]) Run(a A, b B) Either[error, R] { + v, err := io.unsafePerform(a, b) + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} + +// NewIOEither3 instanciates a new IO3. +func NewIOEither3[R any, A any, B any, C any](f fe3[R, A, B, C]) IOEither3[R, A, B, C] { + return IOEither3[R, A, B, C]{ + unsafePerform: f, + } +} + +// IOEither3 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither3[R any, A any, B any, C any] struct { + unsafePerform fe3[R, A, B, C] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither3[R, A, B, C]) Run(a A, b B, c C) Either[error, R] { + v, err := io.unsafePerform(a, b, c) + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} + +// NewIOEither4 instanciates a new IO4. +func NewIOEither4[R any, A any, B any, C any, D any](f fe4[R, A, B, C, D]) IOEither4[R, A, B, C, D] { + return IOEither4[R, A, B, C, D]{ + unsafePerform: f, + } +} + +// IOEither4 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither4[R any, A any, B any, C any, D any] struct { + unsafePerform fe4[R, A, B, C, D] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither4[R, A, B, C, D]) Run(a A, b B, c C, d D) Either[error, R] { + v, err := io.unsafePerform(a, b, c, d) + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} + +// NewIOEither5 instanciates a new IO5. +func NewIOEither5[R any, A any, B any, C any, D any, E any](f fe5[R, A, B, C, D, E]) IOEither5[R, A, B, C, D, E] { + return IOEither5[R, A, B, C, D, E]{ + unsafePerform: f, + } +} + +// IOEither5 represents a non-deterministic synchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type IOEither5[R any, A any, B any, C any, D any, E any] struct { + unsafePerform fe5[R, A, B, C, D, E] +} + +// Run execute the non-deterministic synchronous computation, with side effect. +func (io IOEither5[R, A, B, C, D, E]) Run(a A, b B, c C, d D, e E) Either[error, R] { + v, err := io.unsafePerform(a, b, c, d, e) + if err != nil { + return Left[error, R](err) + } + + return Right[error, R](v) +} diff --git a/vendor/github.com/samber/mo/option.go b/vendor/github.com/samber/mo/option.go new file mode 100644 index 00000000..dc91f7a4 --- /dev/null +++ b/vendor/github.com/samber/mo/option.go @@ -0,0 +1,338 @@ +package mo + +import ( + "bytes" + "database/sql" + "database/sql/driver" + "encoding/gob" + "encoding/json" + "errors" + "fmt" + "reflect" +) + +var optionNoSuchElement = fmt.Errorf("no such element") + +// Some builds an Option when value is present. +// Play: https://go.dev/play/p/iqz2n9n0tDM +func Some[T any](value T) Option[T] { + return Option[T]{ + isPresent: true, + value: value, + } +} + +// None builds an Option when value is absent. +// Play: https://go.dev/play/p/yYQPsYCSYlD +func None[T any]() Option[T] { + return Option[T]{ + isPresent: false, + } +} + +// TupleToOption builds a Some Option when second argument is true, or None. +// Play: https://go.dev/play/p/gkrg2pZwOty +func TupleToOption[T any](value T, ok bool) Option[T] { + if ok { + return Some(value) + } + return None[T]() +} + +// EmptyableToOption builds a Some Option when value is not empty, or None. +// Play: https://go.dev/play/p/GSpQQ-q-UES +func EmptyableToOption[T any](value T) Option[T] { + // 🤮 + isZero := reflect.ValueOf(&value).Elem().IsZero() + if isZero { + return None[T]() + } + + return Some(value) +} + +// PointerToOption builds a Some Option when value is not nil, or None. +// Play: https://go.dev/play/p/yPVMj4DUb-I +func PointerToOption[T any](value *T) Option[T] { + if value == nil { + return None[T]() + } + + return Some(*value) +} + +// Option is a container for an optional value of type T. If value exists, Option is +// of type Some. If the value is absent, Option is of type None. +type Option[T any] struct { + isPresent bool + value T +} + +// IsPresent returns false when value is absent. +// Play: https://go.dev/play/p/nDqIaiihyCA +func (o Option[T]) IsPresent() bool { + return o.isPresent +} + +// IsAbsent returns false when value is present. +// Play: https://go.dev/play/p/23e2zqyVOQm +func (o Option[T]) IsAbsent() bool { + return !o.isPresent +} + +// Size returns 1 when value is present or 0 instead. +// Play: https://go.dev/play/p/7ixCNG1E9l7 +func (o Option[T]) Size() int { + if o.isPresent { + return 1 + } + + return 0 +} + +// Get returns value and presence. +// Play: https://go.dev/play/p/0-JBa1usZRT +func (o Option[T]) Get() (T, bool) { + if !o.isPresent { + return empty[T](), false + } + + return o.value, true +} + +// MustGet returns value if present or panics instead. +// Play: https://go.dev/play/p/RVBckjdi5WR +func (o Option[T]) MustGet() T { + if !o.isPresent { + panic(optionNoSuchElement) + } + + return o.value +} + +// OrElse returns value if present or default value. +// Play: https://go.dev/play/p/TrGByFWCzXS +func (o Option[T]) OrElse(fallback T) T { + if !o.isPresent { + return fallback + } + + return o.value +} + +// OrEmpty returns value if present or empty value. +// Play: https://go.dev/play/p/SpSUJcE-tQm +func (o Option[T]) OrEmpty() T { + return o.value +} + +// ForEach executes the given side-effecting function of value is present. +func (o Option[T]) ForEach(onValue func(value T)) { + if o.isPresent { + onValue(o.value) + } +} + +// Match executes the first function if value is present and second function if absent. +// It returns a new Option. +// Play: https://go.dev/play/p/1V6st3LDJsM +func (o Option[T]) Match(onValue func(value T) (T, bool), onNone func() (T, bool)) Option[T] { + if o.isPresent { + return TupleToOption(onValue(o.value)) + } + return TupleToOption(onNone()) +} + +// Map executes the mapper function if value is present or returns None if absent. +// Play: https://go.dev/play/p/mvfP3pcP_eJ +func (o Option[T]) Map(mapper func(value T) (T, bool)) Option[T] { + if o.isPresent { + return TupleToOption(mapper(o.value)) + } + + return None[T]() +} + +// MapNone executes the mapper function if value is absent or returns Option. +// Play: https://go.dev/play/p/_KaHWZ6Q17b +func (o Option[T]) MapNone(mapper func() (T, bool)) Option[T] { + if o.isPresent { + return Some(o.value) + } + + return TupleToOption(mapper()) +} + +// FlatMap executes the mapper function if value is present or returns None if absent. +// Play: https://go.dev/play/p/OXO-zJx6n5r +func (o Option[T]) FlatMap(mapper func(value T) Option[T]) Option[T] { + if o.isPresent { + return mapper(o.value) + } + + return None[T]() +} + +// ToPointer returns value if present or a nil pointer. +// Play: https://go.dev/play/p/N43w92SM-Bs +func (o Option[T]) ToPointer() *T { + if !o.isPresent { + return nil + } + + return &o.value +} + +// MarshalJSON encodes Option into json. +func (o Option[T]) MarshalJSON() ([]byte, error) { + if o.isPresent { + return json.Marshal(o.value) + } + + // if anybody find a way to support `omitempty` param, please contribute! + return json.Marshal(nil) +} + +// UnmarshalJSON decodes Option from json. +func (o *Option[T]) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + o.isPresent = false + return nil + } + + err := json.Unmarshal(b, &o.value) + if err != nil { + return err + } + + o.isPresent = true + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (o Option[T]) MarshalText() ([]byte, error) { + return json.Marshal(o) +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (o *Option[T]) UnmarshalText(data []byte) error { + return json.Unmarshal(data, o) +} + +// MarshalBinary is the interface implemented by an object that can marshal itself into a binary form. +func (o Option[T]) MarshalBinary() ([]byte, error) { + if !o.isPresent { + return []byte{0}, nil + } + + var buf bytes.Buffer + + enc := gob.NewEncoder(&buf) + if err := enc.Encode(o.value); err != nil { + return []byte{}, err + } + + return append([]byte{1}, buf.Bytes()...), nil +} + +// UnmarshalBinary is the interface implemented by an object that can unmarshal a binary representation of itself. +func (o *Option[T]) UnmarshalBinary(data []byte) error { + if len(data) == 0 { + return errors.New("Option[T].UnmarshalBinary: no data") + } + + if data[0] == 0 { + o.isPresent = false + o.value = empty[T]() + return nil + } + + buf := bytes.NewBuffer(data[1:]) + dec := gob.NewDecoder(buf) + err := dec.Decode(&o.value) + if err != nil { + return err + } + + o.isPresent = true + return nil +} + +// GobEncode implements the gob.GobEncoder interface. +func (o Option[T]) GobEncode() ([]byte, error) { + return o.MarshalBinary() +} + +// GobDecode implements the gob.GobDecoder interface. +func (o *Option[T]) GobDecode(data []byte) error { + return o.UnmarshalBinary(data) +} + +// Scan implements the SQL sql.Scanner interface. +func (o *Option[T]) Scan(src any) error { + if src == nil { + o.isPresent = false + o.value = empty[T]() + return nil + } + + // is is only possible to assert interfaces, so convert first + // https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#why-not-permit-type-assertions-on-values-whose-type-is-a-type-parameter + var t T + if tScanner, ok := interface{}(&t).(sql.Scanner); ok { + if err := tScanner.Scan(src); err != nil { + return fmt.Errorf("failed to scan: %w", err) + } + + o.isPresent = true + o.value = t + return nil + } + + if av, err := driver.DefaultParameterConverter.ConvertValue(src); err == nil { + if v, ok := av.(T); ok { + o.isPresent = true + o.value = v + return nil + } + } + + return o.scanConvertValue(src) +} + +// Value implements the driver Valuer interface. +func (o Option[T]) Value() (driver.Value, error) { + if !o.isPresent { + return nil, nil + } + + return driver.DefaultParameterConverter.ConvertValue(o.value) +} + +// leftValue returns an error if the Option is None, otherwise nil +// +//nolint:unused +func (o Option[T]) leftValue() error { + if !o.isPresent { + return optionNoSuchElement + } + return nil +} + +// rightValue returns the value if the Option is Some, otherwise the zero value of T +// +//nolint:unused +func (o Option[T]) rightValue() T { + if !o.isPresent { + var zero T + return zero + } + return o.value +} + +// hasLeftValue returns true if the Option represents a None state +// +//nolint:unused +func (o Option[T]) hasLeftValue() bool { + return !o.isPresent +} diff --git a/vendor/github.com/samber/mo/option_go118.go b/vendor/github.com/samber/mo/option_go118.go new file mode 100644 index 00000000..d1f9d4db --- /dev/null +++ b/vendor/github.com/samber/mo/option_go118.go @@ -0,0 +1,327 @@ +//go:build !go1.22 +// +build !go1.22 + +package mo + +// +// sql.Null[T] has been introduce in go1.22 +// This file is a copy of stdlib and ensure retro-compatibility. +// See https://github.com/samber/mo/pull/49 +// + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "reflect" + "strconv" + "time" +) + +var errNilPtr = errors.New("destination pointer is nil") + +func cloneBytes(b []byte) []byte { + if b == nil { + return nil + } + c := make([]byte, len(b)) + copy(c, b) + return c +} + +func asString(src any) string { + switch v := src.(type) { + case string: + return v + case []byte: + return string(v) + } + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'g', -1, 64) + case reflect.Float32: + return strconv.FormatFloat(rv.Float(), 'g', -1, 32) + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) + } + return fmt.Sprintf("%v", src) +} + +func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.AppendInt(buf, rv.Int(), 10), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.AppendUint(buf, rv.Uint(), 10), true + case reflect.Float32: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true + case reflect.Float64: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true + case reflect.Bool: + return strconv.AppendBool(buf, rv.Bool()), true + case reflect.String: + s := rv.String() + return append(buf, s...), true + } + return +} + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err +} + +// convertAssignRows copies to dest the value in src, converting it if possible. +// An error is returned if the copy would result in loss of information. +// dest should be a pointer type. If rows is passed in, the rows will +// be used as the parent for any cursor values converted from a +// driver.Rows to a *Rows. +func convertAssign(dest, src any) error { + // Common cases, without reflect. + switch s := src.(type) { + case string: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = s + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = append((*d)[:0], s...) + return nil + } + case []byte: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = string(s) + return nil + case *any: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = s + return nil + } + case time.Time: + switch d := dest.(type) { + case *time.Time: + *d = s + return nil + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = s.AppendFormat((*d)[:0], time.RFC3339Nano) + return nil + } + case nil: + switch d := dest.(type) { + case *any: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = nil + return nil + } + } + + var sv reflect.Value + + switch d := dest.(type) { + case *string: + sv = reflect.ValueOf(src) + switch sv.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + *d = asString(src) + return nil + } + case *[]byte: + sv = reflect.ValueOf(src) + if b, ok := asBytes(nil, sv); ok { + *d = b + return nil + } + case *sql.RawBytes: + sv = reflect.ValueOf(src) + if b, ok := asBytes([]byte(*d)[:0], sv); ok { + *d = sql.RawBytes(b) + return nil + } + case *bool: + bv, err := driver.Bool.ConvertValue(src) + if err == nil { + *d = bv.(bool) + } + return err + case *any: + *d = src + return nil + } + + if scanner, ok := dest.(sql.Scanner); ok { + return scanner.Scan(src) + } + + dpv := reflect.ValueOf(dest) + if dpv.Kind() != reflect.Pointer { + return errors.New("destination not a pointer") + } + if dpv.IsNil() { + return errNilPtr + } + + if !sv.IsValid() { + sv = reflect.ValueOf(src) + } + + dv := reflect.Indirect(dpv) + if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { + switch b := src.(type) { + case []byte: + dv.Set(reflect.ValueOf(cloneBytes(b))) + default: + dv.Set(sv) + } + return nil + } + + if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) { + dv.Set(sv.Convert(dv.Type())) + return nil + } + + // The following conversions use a string value as an intermediate representation + // to convert between various numeric types. + // + // This also allows scanning into user defined types such as "type Int int64". + // For symmetry, also check for string destination types. + switch dv.Kind() { + case reflect.Pointer: + if src == nil { + dv.Set(reflect.Zero(dv.Type())) + return nil + } + dv.Set(reflect.New(dv.Type().Elem())) + return convertAssign(dv.Interface(), src) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } + s := asString(src) + i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetInt(i64) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } + s := asString(src) + u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetUint(u64) + return nil + case reflect.Float32, reflect.Float64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } + s := asString(src) + f64, err := strconv.ParseFloat(s, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetFloat(f64) + return nil + case reflect.String: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } + switch v := src.(type) { + case string: + dv.SetString(v) + return nil + case []byte: + dv.SetString(string(v)) + return nil + } + } + + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) +} + +func (o *Option[T]) scanConvertValue(src any) error { + // we try to convertAssign values that we can't directly assign because ConvertValue + // will return immediately for v that is already a Value, even if it is a different + // Value type than the one we expect here. + var dest T + if err := convertAssign(&dest, src); err == nil { + o.isPresent = true + o.value = dest + return nil + } + return fmt.Errorf("failed to scan Option[T]") +} diff --git a/vendor/github.com/samber/mo/option_go122.go b/vendor/github.com/samber/mo/option_go122.go new file mode 100644 index 00000000..4f51d376 --- /dev/null +++ b/vendor/github.com/samber/mo/option_go122.go @@ -0,0 +1,22 @@ +//go:build go1.22 +// +build go1.22 + +package mo + +import ( + "database/sql" + "fmt" +) + +func (o *Option[T]) scanConvertValue(src any) error { + // we try to convertAssign values that we can't directly assign because ConvertValue + // will return immediately for v that is already a Value, even if it is a different + // Value type than the one we expect here. + var st sql.Null[T] + if err := st.Scan(src); err == nil { + o.isPresent = true + o.value = st.V + return nil + } + return fmt.Errorf("failed to scan Option[T]") +} diff --git a/vendor/github.com/samber/mo/result.go b/vendor/github.com/samber/mo/result.go new file mode 100644 index 00000000..55ac3133 --- /dev/null +++ b/vendor/github.com/samber/mo/result.go @@ -0,0 +1,236 @@ +package mo + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Ok builds a Result when value is valid. +// Play: https://go.dev/play/p/PDwADdzNoyZ +func Ok[T any](value T) Result[T] { + return Result[T]{ + value: value, + isErr: false, + } +} + +// Err builds a Result when value is invalid. +// Play: https://go.dev/play/p/PDwADdzNoyZ +func Err[T any](err error) Result[T] { + return Result[T]{ + err: err, + isErr: true, + } +} + +// Errf builds a Result when value is invalid. +// Errf formats according to a format specifier and returns the error as a value that satisfies Result[T]. +// Play: https://go.dev/play/p/N43w92SM-Bs +func Errf[T any](format string, a ...any) Result[T] { + return Err[T](fmt.Errorf(format, a...)) +} + +// TupleToResult convert a pair of T and error into a Result. +// Play: https://go.dev/play/p/KWjfqQDHQwa +func TupleToResult[T any](value T, err error) Result[T] { + if err != nil { + return Err[T](err) + } + return Ok(value) +} + +// Try returns either a Ok or Err object. +// Play: https://go.dev/play/p/ilOlQx-Mx42 +func Try[T any](f func() (T, error)) Result[T] { + return TupleToResult(f()) +} + +// Result represents a result of an action having one +// of the following output: success or failure. +// An instance of Result is an instance of either Ok or Err. +// It could be compared to `Either[error, T]`. +type Result[T any] struct { + isErr bool + value T + err error +} + +// IsOk returns true when value is valid. +// Play: https://go.dev/play/p/sfNvBQyZfgU +func (r Result[T]) IsOk() bool { + return !r.isErr +} + +// IsError returns true when value is invalid. +// Play: https://go.dev/play/p/xkV9d464scV +func (r Result[T]) IsError() bool { + return r.isErr +} + +// Error returns error when value is invalid or nil. +// Play: https://go.dev/play/p/CSkHGTyiXJ5 +func (r Result[T]) Error() error { + return r.err +} + +// Get returns value and error. +// Play: https://go.dev/play/p/8KyX3z6TuNo +func (r Result[T]) Get() (T, error) { + if r.isErr { + return empty[T](), r.err + } + + return r.value, nil +} + +// MustGet returns value when Result is valid or panics. +// Play: https://go.dev/play/p/8LSlndHoTAE +func (r Result[T]) MustGet() T { + if r.isErr { + panic(r.err) + } + + return r.value +} + +// OrElse returns value when Result is valid or default value. +// Play: https://go.dev/play/p/MN_ULx0soi6 +func (r Result[T]) OrElse(fallback T) T { + if r.isErr { + return fallback + } + + return r.value +} + +// OrEmpty returns value when Result is valid or empty value. +// Play: https://go.dev/play/p/rdKtBmOcMLh +func (r Result[T]) OrEmpty() T { + return r.value +} + +// ToEither transforms a Result into an Either type. +// Play: https://go.dev/play/p/Uw1Zz6b952q +func (r Result[T]) ToEither() Either[error, T] { + if r.isErr { + return Left[error, T](r.err) + } + + return Right[error, T](r.value) +} + +// ForEach executes the given side-effecting function if Result is valid. +func (r Result[T]) ForEach(mapper func(value T)) { + if !r.isErr { + mapper(r.value) + } +} + +// Match executes the first function if Result is valid and second function if invalid. +// It returns a new Result. +// Play: https://go.dev/play/p/-_eFaLJ31co +func (r Result[T]) Match(onSuccess func(value T) (T, error), onError func(err error) (T, error)) Result[T] { + if r.isErr { + return TupleToResult(onError(r.err)) + } + return TupleToResult(onSuccess(r.value)) +} + +// Map executes the mapper function if Result is valid. It returns a new Result. +// Play: https://go.dev/play/p/-ndpN_b_OSc +func (r Result[T]) Map(mapper func(value T) (T, error)) Result[T] { + if !r.isErr { + return TupleToResult(mapper(r.value)) + } + + return Err[T](r.err) +} + +// MapErr executes the mapper function if Result is invalid. It returns a new Result. +// Play: https://go.dev/play/p/WraZixg9GGf +func (r Result[T]) MapErr(mapper func(error) (T, error)) Result[T] { + if r.isErr { + return TupleToResult(mapper(r.err)) + } + + return Ok(r.value) +} + +// FlatMap executes the mapper function if Result is valid. It returns a new Result. +// Play: https://go.dev/play/p/Ud5QjZOqg-7 +func (r Result[T]) FlatMap(mapper func(value T) Result[T]) Result[T] { + if !r.isErr { + return mapper(r.value) + } + + return Err[T](r.err) +} + +// MarshalJSON encodes Result into json, following the JSON-RPC specification for results, +// with one exception: when the result is an error, the "code" field is not included. +// Reference: https://www.jsonrpc.org/specification +func (o Result[T]) MarshalJSON() ([]byte, error) { + if o.isErr { + return json.Marshal(map[string]any{ + "error": map[string]any{ + "message": o.err.Error(), + }, + }) + } + + return json.Marshal(map[string]any{ + "result": o.value, + }) +} + +// UnmarshalJSON decodes json into Result. If "error" is set, the result is an +// Err containing the error message as a generic error object. Otherwise, the +// result is an Ok containing the result. If the JSON object contains netiher +// an error nor a result, the result is an Ok containing an empty value. If the +// JSON object contains both an error and a result, the result is an Err. Finally, +// if the JSON object contains an error but is not structured correctly (no message +// field), the unmarshaling fails. +func (o *Result[T]) UnmarshalJSON(data []byte) error { + var result struct { + Result T `json:"result"` + Error struct { + Message string `json:"message"` + } `json:"error"` + } + + if err := json.Unmarshal(data, &result); err != nil { + return err + } + + if result.Error.Message != "" { + o.err = errors.New(result.Error.Message) + o.isErr = true + return nil + } + + o.value = result.Result + o.isErr = false + return nil +} + +// leftValue returns the error if the Result is an error, otherwise nil +// +//nolint:unused +func (r Result[T]) leftValue() error { + return r.err +} + +// rightValue returns the value if the Result is a success, otherwise the zero value of T +// +//nolint:unused +func (r Result[T]) rightValue() T { + return r.value +} + +// hasLeftValue returns true if the Result represents an error state. +// +//nolint:unused +func (r Result[T]) hasLeftValue() bool { + return r.isErr +} diff --git a/vendor/github.com/samber/mo/state.go b/vendor/github.com/samber/mo/state.go new file mode 100644 index 00000000..a1bb0a60 --- /dev/null +++ b/vendor/github.com/samber/mo/state.go @@ -0,0 +1,52 @@ +package mo + +func NewState[S any, A any](f func(state S) (A, S)) State[S, A] { + return State[S, A]{ + run: f, + } +} + +func ReturnState[S any, A any](x A) State[S, A] { + return State[S, A]{ + run: func(state S) (A, S) { + return x, state + }, + } +} + +// State represents a function `(S) -> (A, S)`, where `S` is state, `A` is result. +type State[S any, A any] struct { + run func(state S) (A, S) +} + +// Run executes a computation in the State monad. +func (s State[S, A]) Run(state S) (A, S) { + return s.run(state) +} + +// Get returns the current state. +func (s State[S, A]) Get() State[S, S] { + return State[S, S]{ + run: func(state S) (S, S) { + return state, state + }, + } +} + +// Modify the state by applying a function to the current state. +func (s State[S, A]) Modify(f func(state S) S) State[S, A] { + return State[S, A]{ + run: func(state S) (A, S) { + return empty[A](), f(state) + }, + } +} + +// Put set the state. +func (s State[S, A]) Put(state S) State[S, A] { + return State[S, A]{ + run: func(state S) (A, S) { + return empty[A](), state + }, + } +} diff --git a/vendor/github.com/samber/mo/task.go b/vendor/github.com/samber/mo/task.go new file mode 100644 index 00000000..39a28ef3 --- /dev/null +++ b/vendor/github.com/samber/mo/task.go @@ -0,0 +1,175 @@ +package mo + +// NewTask instanciates a new Task. +func NewTask[R any](f ff0[R]) Task[R] { + return Task[R]{ + unsafePerform: f, + } +} + +// NewTaskFromIO instanciates a new Task from an existing IO. +func NewTaskFromIO[R any](io IO[R]) Task[R] { + return Task[R]{ + unsafePerform: func() *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform()) + }) + }, + } +} + +// Task represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task[R any] struct { + unsafePerform ff0[R] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task[R]) Run() *Future[R] { + return t.unsafePerform() +} + +// NewTask1 instanciates a new Task1. +func NewTask1[R any, A any](f ff1[R, A]) Task1[R, A] { + return Task1[R, A]{ + unsafePerform: f, + } +} + +// NewTaskFromIO1 instanciates a new Task1 from an existing IO1. +func NewTaskFromIO1[R any, A any](io IO1[R, A]) Task1[R, A] { + return Task1[R, A]{ + unsafePerform: func(a A) *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform(a)) + }) + }, + } +} + +// Task1 represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task1[R any, A any] struct { + unsafePerform ff1[R, A] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task1[R, A]) Run(a A) *Future[R] { + return t.unsafePerform(a) +} + +// NewTask2 instanciates a new Task2. +func NewTask2[R any, A any, B any](f ff2[R, A, B]) Task2[R, A, B] { + return Task2[R, A, B]{ + unsafePerform: f, + } +} + +// NewTaskFromIO2 instanciates a new Task2 from an existing IO2. +func NewTaskFromIO2[R any, A any, B any](io IO2[R, A, B]) Task2[R, A, B] { + return Task2[R, A, B]{ + unsafePerform: func(a A, b B) *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform(a, b)) + }) + }, + } +} + +// Task2 represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task2[R any, A any, B any] struct { + unsafePerform ff2[R, A, B] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task2[R, A, B]) Run(a A, b B) *Future[R] { + return t.unsafePerform(a, b) +} + +// NewTask3 instanciates a new Task3. +func NewTask3[R any, A any, B any, C any](f ff3[R, A, B, C]) Task3[R, A, B, C] { + return Task3[R, A, B, C]{ + unsafePerform: f, + } +} + +// NewTaskFromIO3 instanciates a new Task3 from an existing IO3. +func NewTaskFromIO3[R any, A any, B any, C any](io IO3[R, A, B, C]) Task3[R, A, B, C] { + return Task3[R, A, B, C]{ + unsafePerform: func(a A, b B, c C) *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform(a, b, c)) + }) + }, + } +} + +// Task3 represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task3[R any, A any, B any, C any] struct { + unsafePerform ff3[R, A, B, C] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task3[R, A, B, C]) Run(a A, b B, c C) *Future[R] { + return t.unsafePerform(a, b, c) +} + +// NewTask4 instanciates a new Task4. +func NewTask4[R any, A any, B any, C any, D any](f ff4[R, A, B, C, D]) Task4[R, A, B, C, D] { + return Task4[R, A, B, C, D]{ + unsafePerform: f, + } +} + +// NewTaskFromIO4 instanciates a new Task4 from an existing IO4. +func NewTaskFromIO4[R any, A any, B any, C any, D any](io IO4[R, A, B, C, D]) Task4[R, A, B, C, D] { + return Task4[R, A, B, C, D]{ + unsafePerform: func(a A, b B, c C, d D) *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform(a, b, c, d)) + }) + }, + } +} + +// Task4 represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task4[R any, A any, B any, C any, D any] struct { + unsafePerform ff4[R, A, B, C, D] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task4[R, A, B, C, D]) Run(a A, b B, c C, d D) *Future[R] { + return t.unsafePerform(a, b, c, d) +} + +// NewTask5 instanciates a new Task5. +func NewTask5[R any, A any, B any, C any, D any, E any](f ff5[R, A, B, C, D, E]) Task5[R, A, B, C, D, E] { + return Task5[R, A, B, C, D, E]{ + unsafePerform: f, + } +} + +// NewTaskFromIO5 instanciates a new Task5 from an existing IO5. +func NewTaskFromIO5[R any, A any, B any, C any, D any, E any](io IO5[R, A, B, C, D, E]) Task5[R, A, B, C, D, E] { + return Task5[R, A, B, C, D, E]{ + unsafePerform: func(a A, b B, c C, d D, e E) *Future[R] { + return NewFuture[R](func(resolve func(R), reject func(error)) { + resolve(io.unsafePerform(a, b, c, d, e)) + }) + }, + } +} + +// Task5 represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and never fails. +type Task5[R any, A any, B any, C any, D any, E any] struct { + unsafePerform ff5[R, A, B, C, D, E] +} + +// Run execute the non-deterministic asynchronous computation, with side effect. +func (t Task5[R, A, B, C, D, E]) Run(a A, b B, c C, d D, e E) *Future[R] { + return t.unsafePerform(a, b, c, d, e) +} diff --git a/vendor/github.com/samber/mo/task_either.go b/vendor/github.com/samber/mo/task_either.go new file mode 100644 index 00000000..0047258f --- /dev/null +++ b/vendor/github.com/samber/mo/task_either.go @@ -0,0 +1,56 @@ +package mo + +// NewTaskEither instanciates a new TaskEither. +func NewTaskEither[R any](f ff0[R]) TaskEither[R] { + return TaskEither[R]{NewTask[R](f)} +} + +// NewTaskEitherFromIO instanciates a new TaskEither from an existing IO. +func NewTaskEitherFromIO[R any](io IO[R]) TaskEither[R] { + return TaskEither[R]{NewTaskFromIO[R](io)} +} + +// TaskEither represents a non-deterministic asynchronous computation that +// can cause side effects, yields a value of type `R` and can fail. +type TaskEither[R any] struct { + Task[R] +} + +// OrElse returns value if task succeeded or default value. +func (t TaskEither[R]) OrElse(fallback R) R { + either := t.Run().Either() + + right, isRight := either.Right() + if !isRight { + return fallback + } + + return right +} + +// Match executes the first function if task succeeded and second function if task failed. +// It returns a new Option. +func (t TaskEither[R]) Match(onLeft func(error) Either[error, R], onRight func(R) Either[error, R]) Either[error, R] { + either := t.Run().Either() + return either.Match(onLeft, onRight) +} + +// TryCatch is an alias to Match +func (t TaskEither[R]) TryCatch(onLeft func(error) Either[error, R], onRight func(R) Either[error, R]) Either[error, R] { + return t.Match(onLeft, onRight) +} + +// ToTask converts TaskEither to Task +func (t TaskEither[R]) ToTask(fallback R) Task[R] { + return NewTask(func() *Future[R] { + return t.Run(). + Catch(func(err error) (R, error) { + return fallback, nil + }) + }) +} + +// ToEither converts TaskEither to Either. +func (t TaskEither[R]) ToEither() Either[error, R] { + return t.Run().Either() +} diff --git a/vendor/github.com/samber/mo/types.go b/vendor/github.com/samber/mo/types.go new file mode 100644 index 00000000..c85d4039 --- /dev/null +++ b/vendor/github.com/samber/mo/types.go @@ -0,0 +1,22 @@ +package mo + +type f0[R any] func() R +type f1[R any, A any] func(A) R +type f2[R any, A any, B any] func(A, B) R +type f3[R any, A any, B any, C any] func(A, B, C) R +type f4[R any, A any, B any, C any, D any] func(A, B, C, D) R +type f5[R any, A any, B any, C any, D any, E any] func(A, B, C, D, E) R + +type ff0[R any] func() *Future[R] +type ff1[R any, A any] func(A) *Future[R] +type ff2[R any, A any, B any] func(A, B) *Future[R] +type ff3[R any, A any, B any, C any] func(A, B, C) *Future[R] +type ff4[R any, A any, B any, C any, D any] func(A, B, C, D) *Future[R] +type ff5[R any, A any, B any, C any, D any, E any] func(A, B, C, D, E) *Future[R] + +type fe0[R any] func() (R, error) +type fe1[R any, A any] func(A) (R, error) +type fe2[R any, A any, B any] func(A, B) (R, error) +type fe3[R any, A any, B any, C any] func(A, B, C) (R, error) +type fe4[R any, A any, B any, C any, D any] func(A, B, C, D) (R, error) +type fe5[R any, A any, B any, C any, D any, E any] func(A, B, C, D, E) (R, error) diff --git a/vendor/github.com/samber/mo/utils.go b/vendor/github.com/samber/mo/utils.go new file mode 100644 index 00000000..5233f671 --- /dev/null +++ b/vendor/github.com/samber/mo/utils.go @@ -0,0 +1,5 @@ +package mo + +func empty[T any]() (t T) { + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7b04286d..5b74fba9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -115,6 +115,9 @@ github.com/russross/blackfriday/v2 github.com/samber/lo github.com/samber/lo/internal/constraints github.com/samber/lo/internal/rand +# github.com/samber/mo v1.13.0 +## explicit; go 1.18 +github.com/samber/mo # github.com/stretchr/testify v1.8.0 ## explicit; go 1.13 github.com/stretchr/testify/assert