From aa4b787c3f01aa1b8fcff8d91ea9c2fa5ffeb54f Mon Sep 17 00:00:00 2001 From: Maksim Martianov Date: Thu, 5 Oct 2023 02:29:08 -0400 Subject: [PATCH] feat: initial release --- .circleci/config.yml | 26 ++++ .github/workflows/commit-validation.yaml | 13 ++ .github/workflows/golangci-lint.yml | 19 +++ .github/workflows/release.yaml | 36 ++++++ .gitignore | 24 ++++ LICENSE | 21 ++++ README.md | 97 ++++++++++++++ config.go | 1 + go.mod | 11 ++ go.sum | 10 ++ none.go | 28 +++++ none_test.go | 116 +++++++++++++++++ option.go | 48 +++++++ option_test.go | 130 +++++++++++++++++++ release.config.js | 34 +++++ some.go | 33 +++++ some_test.go | 153 +++++++++++++++++++++++ 17 files changed, 800 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .github/workflows/commit-validation.yaml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 none.go create mode 100644 none_test.go create mode 100644 option.go create mode 100644 option_test.go create mode 100644 release.config.js create mode 100644 some.go create mode 100644 some_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..eaa4985 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +jobs: + build: + executor: + name: go/default + tag: '1.21.1' + steps: + - checkout + - go/load-cache + - go/mod-download + - go/save-cache + - go/test: + covermode: atomic + failfast: true + race: true + coverprofile: coverage.txt + - run: + name: Coverage Report + command: | + bash <(curl -s https://codecov.io/bash) +orbs: + go: circleci/go@1.9.0 +version: 2.1 +workflows: + main: + jobs: + - build \ No newline at end of file diff --git a/.github/workflows/commit-validation.yaml b/.github/workflows/commit-validation.yaml new file mode 100644 index 0000000..47969eb --- /dev/null +++ b/.github/workflows/commit-validation.yaml @@ -0,0 +1,13 @@ +name: Commit validation + +on: + [pull_request] + +jobs: + commitsar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Commitsar Action + uses: outillage/commitsar@v0.13.0 \ No newline at end of file diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..155a2b2 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,19 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - master + pull_request: +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. + version: v1.29 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..1972613 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,36 @@ +name: Release + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + release: + name: release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Action For Semantic Release + uses: cycjimmy/semantic-release-action@v2.3.0 + id: semantic + with: + semantic_version: 17 + extra_plugins: | + conventional-changelog-conventionalcommits + @semantic-release/changelog + @semantic-release/git + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Push updates to branch for major version + if: steps.semantic.outputs.new_release_published == 'true' + run: "git push https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git HEAD:refs/heads/v${{steps.semantic.outputs.new_release_major_version}}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6a943f --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# 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 + +coverage.txt +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8b8baf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Maksim Martianov + +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/README.md b/README.md new file mode 100644 index 0000000..21290f1 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/maksimru/go-option/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/maksimru/go-option/tree/master) +[![codecov](https://codecov.io/gh/maksimru/go-option/graph/badge.svg?token=NQICPHBEUQ)](https://codecov.io/gh/maksimru/go-option) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/maksimru/go-option)](https://pkg.go.dev/github.com/maksimru/go-option) +[![Go Report Card](https://goreportcard.com/badge/github.com/maksimru/go-option)](https://goreportcard.com/report/github.com/maksimru/go-option) + +# Go Option + +The idea is based on Scala Option and Java Optional. The package allows to create optional values in Golang + +## Functions + +Set an non-empty value: +``` +option.Some[T](v) +``` + +Set an empty value: +``` +v := option.None[T]() +``` + +Set an empty value if v is nil, otherwise set non-empty value +``` +v := option.NewOption[T](v) +``` + +Remap one option to another option +``` +import "github.com/maksimru/go-option" + +type Car struct { + name string + plateNumber option.Option[string] +} + +carOpt := option.Some[Car]( + Car { + name: "bmw" + }, +) + +// get car name as option +carNameOpt := option.Map[Car, string](carOpt, func(c Car) string { + return c.name +}) +``` + +Option composition +``` +import "github.com/maksimru/go-option" + +type Car struct { + name string + plateNumber option.Option[string] +} + +type User struct { + name string + car option.Option[Car] +} + +u := User{ + name: "jake", + car: option.Some[Car]( + Car{ + name: "bmw", + plateNumber: option.Some[string]("X723"), + }, + ), +} + +// get car plate as option +carPlateOpt := option.FlatMap[Car, string](u.car, func(c Car) option.Option[string] { + return c.plateNumber +}) +``` + +## Methods of Option + +| Method | Description | +|--------|:-----------------------------------------------:| +| Get() | gets underlying value (unsafe*) | +| GetOrElse(fallback) | gets underlying value or returns fallback value | +| OrElse(fallbackOpt) | returns fallback option if option is empty | +| Empty() | checks if value is empty | +| NonEmpty() | checks if value is set | +| String() | string representation | +`* - empty value will panic` + +--- +## Testing + +To run all tests in this module: + +``` +go test ./... +``` diff --git a/config.go b/config.go new file mode 100644 index 0000000..774a651 --- /dev/null +++ b/config.go @@ -0,0 +1 @@ +package option diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b7f4a30 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/maksimru/go-option + +go 1.21.1 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/none.go b/none.go new file mode 100644 index 0000000..6fbe765 --- /dev/null +++ b/none.go @@ -0,0 +1,28 @@ +package option + +type optNone[T any] struct { +} + +func (n optNone[T]) Get() T { + panic("called Get on optNone value") +} + +func (n optNone[T]) GetOrElse(v T) T { + return v +} + +func (n optNone[T]) OrElse(opt Option[T]) Option[T] { + return opt +} + +func (n optNone[T]) Empty() bool { + return true +} + +func (n optNone[T]) NonEmpty() bool { + return false +} + +func (n optNone[T]) String() string { + return "None" +} diff --git a/none_test.go b/none_test.go new file mode 100644 index 0000000..bec4b3b --- /dev/null +++ b/none_test.go @@ -0,0 +1,116 @@ +package option + +import ( + "github.com/stretchr/testify/assert" + "reflect" + "testing" +) + +func TestNone_Empty(t *testing.T) { + tests := []struct { + name string + want bool + }{ + {name: "optNone[T] Empty() returns true", want: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + if got := n.Empty(); got != tt.want { + t.Errorf("Empty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNone_Get(t *testing.T) { + tests := []struct { + name string + want int + }{ + {name: "optNone[T] Get throws an exception"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + assert.Panics(t, func() { n.Get() }) + }) + } +} + +func TestNone_GetOrElse(t *testing.T) { + type args struct { + v int + } + tests := []struct { + name string + args args + want int + }{ + {name: "optNone[T] GetOrElse() returns else value", args: args{v: 2}, want: 2}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + if got := n.GetOrElse(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetOrElse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNone_NonEmpty(t *testing.T) { + tests := []struct { + name string + want bool + }{ + {name: "optNone[T] Empty() returns false", want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + if got := n.NonEmpty(); got != tt.want { + t.Errorf("NonEmpty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNone_OrElse(t *testing.T) { + type args struct { + opt Option[int] + } + tests := []struct { + name string + args args + want Option[int] + }{ + {name: "optNone[T] OrElse() returns optNone if else condition is optNone", args: args{opt: optNone[int]{}}, want: optNone[int]{}}, + {name: "optNone[T] OrElse() returns optSome if else condition is optSome", args: args{opt: optSome[int]{2}}, want: optSome[int]{2}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + if got := n.OrElse(tt.args.opt); !reflect.DeepEqual(got, tt.want) { + t.Errorf("OrElse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNone_String(t *testing.T) { + tests := []struct { + name string + want string + }{ + {name: "optNone[T] String() returns None", want: "None"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := optNone[int]{} + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/option.go b/option.go new file mode 100644 index 0000000..dd304c9 --- /dev/null +++ b/option.go @@ -0,0 +1,48 @@ +package option + +import "reflect" + +type Option[T any] interface { + Get() T + GetOrElse(v T) T + OrElse(opt Option[T]) Option[T] + Empty() bool + NonEmpty() bool + String() string +} + +func None[T any]() Option[T] { + return optNone[T]{} +} + +func Some[T any](o T) Option[T] { + return NewOption(o) +} + +func NewOption[T any](o T) Option[T] { + if v := reflect.ValueOf(o); (v.Kind() == reflect.Ptr || + v.Kind() == reflect.Interface || + v.Kind() == reflect.Slice || + v.Kind() == reflect.Map || + v.Kind() == reflect.Chan || + v.Kind() == reflect.Func) && v.IsNil() { + return optNone[T]{} + } + return optSome[T]{o} +} + +func Map[T1, T2 any](opt Option[T1], mapper func(T1) T2) Option[T2] { + if opt.NonEmpty() { + return optSome[T2]{mapper(opt.Get())} + } else { + return optNone[T2]{} + } +} + +func FlatMap[T1, T2 any](opt Option[T1], mapper func(T1) Option[T2]) Option[T2] { + if opt.NonEmpty() { + return mapper(opt.Get()) + } else { + return optNone[T2]{} + } +} diff --git a/option_test.go b/option_test.go new file mode 100644 index 0000000..12f5188 --- /dev/null +++ b/option_test.go @@ -0,0 +1,130 @@ +package option + +import ( + "reflect" + "strconv" + "testing" +) + +func TestFlatMap(t *testing.T) { + type args struct { + opt Option[Option[bool]] + mapper func(Option[bool]) Option[bool] + } + tests := []struct { + name string + args args + want Option[bool] + }{ + { + name: "FlatMap with optSome[optSome[true]] get converted to optSome[true]", + args: args{ + opt: optSome[Option[bool]]{ + optSome[bool]{true}, + }, + mapper: func(o1 Option[bool]) Option[bool] { + return o1 + }, + }, + want: optSome[bool]{true}, + }, + { + name: "FlatMap with optSome[optNone] get converted to optNone", + args: args{ + opt: optSome[Option[bool]]{ + optNone[bool]{}, + }, + mapper: func(o1 Option[bool]) Option[bool] { + return o1 + }, + }, + want: optNone[bool]{}, + }, + { + name: "FlatMap with optNone[Option[true]] get converted to optNone", + args: args{ + opt: optNone[Option[bool]]{}, + mapper: func(o1 Option[bool]) Option[bool] { + return o1 + }, + }, + want: optNone[bool]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FlatMap(tt.args.opt, tt.args.mapper); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FlatMap() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMap(t *testing.T) { + type args struct { + opt Option[int] + mapper func(int) string + } + tests := []struct { + name string + args args + want Option[string] + }{ + { + name: "Map with optSome[int] get converted to optSome[string]", + args: args{ + opt: optSome[int]{4}, + mapper: func(i int) string { + return strconv.Itoa(i) + }, + }, + want: optSome[string]{"4"}, + }, + { + name: "Map with optNone[int] get converted to optNone[string]", + args: args{ + opt: optNone[int]{}, + mapper: func(i int) string { + return strconv.Itoa(i) + }, + }, + want: optNone[string]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Map(tt.args.opt, tt.args.mapper); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Map() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestToOption(t *testing.T) { + type args struct { + v map[int]string + } + tests := []struct { + name string + args args + want Option[map[int]string] + }{ + { + name: "NewOption with value is converted to optSome(value)", + args: args{v: map[int]string{}}, + want: optSome[map[int]string]{map[int]string{}}, + }, + { + name: "NewOption without value is converted to optNone", + args: args{v: nil}, + want: optNone[map[int]string]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewOption(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewOption() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/release.config.js b/release.config.js new file mode 100644 index 0000000..5ebf4e0 --- /dev/null +++ b/release.config.js @@ -0,0 +1,34 @@ +const releaseNotesGenOptions = { + "preset": "conventionalcommits", +}; + +const gitOptions = { + "assets": [ + ], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" +}; + +module.exports = { + "dryRun": false, + "branches": [ + '+([0-9])?(.{+([0-9]),x}).x', + 'master', + 'next', + 'next-major', + {name: 'beta', prerelease: true}, + {name: 'alpha', prerelease: true} + ], + "plugins": [ + "@semantic-release/commit-analyzer", + [ + "@semantic-release/release-notes-generator", + releaseNotesGenOptions + ], + "@semantic-release/changelog", + "@semantic-release/github", + [ + "@semantic-release/git", + gitOptions + ] + ] +}; diff --git a/some.go b/some.go new file mode 100644 index 0000000..611b022 --- /dev/null +++ b/some.go @@ -0,0 +1,33 @@ +package option + +import ( + "fmt" +) + +type optSome[T any] struct { + underlyingValue T +} + +func (s optSome[T]) Get() T { + return s.underlyingValue +} + +func (s optSome[T]) GetOrElse(v T) T { + return s.underlyingValue +} + +func (s optSome[T]) OrElse(opt Option[T]) Option[T] { + return s +} + +func (s optSome[T]) Empty() bool { + return false +} + +func (s optSome[T]) NonEmpty() bool { + return true +} + +func (s optSome[T]) String() string { + return fmt.Sprintf("Some(%v)", s.underlyingValue) +} diff --git a/some_test.go b/some_test.go new file mode 100644 index 0000000..c68e5d4 --- /dev/null +++ b/some_test.go @@ -0,0 +1,153 @@ +package option + +import ( + "reflect" + "testing" +) + +func TestSome_Empty(t *testing.T) { + type fields struct { + underlyingValue int + } + tests := []struct { + name string + fields fields + want bool + }{ + {name: "optSome[T] Empty() returns false", fields: struct{ underlyingValue int }{underlyingValue: 2}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.Empty(); got != tt.want { + t.Errorf("Empty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSome_Get(t *testing.T) { + type fields struct { + underlyingValue int + } + tests := []struct { + name string + fields fields + want int + }{ + {name: "optSome[T] Get returns underlying value", fields: struct{ underlyingValue int }{underlyingValue: 2}, want: 2}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.Get(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSome_GetOrElse(t *testing.T) { + type fields struct { + underlyingValue int + } + type args struct { + v int + } + tests := []struct { + name string + fields fields + args args + want int + }{ + {name: "optSome[T] GetOrElse() returns underlying value", fields: struct{ underlyingValue int }{underlyingValue: 2}, args: struct{ v int }{v: 3}, want: 2}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.GetOrElse(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetOrElse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSome_NonEmpty(t *testing.T) { + type fields struct { + underlyingValue int + } + tests := []struct { + name string + fields fields + want bool + }{ + {name: "optSome[T] NonEmpty() returns true", fields: struct{ underlyingValue int }{underlyingValue: 2}, want: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.NonEmpty(); got != tt.want { + t.Errorf("NonEmpty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSome_OrElse(t *testing.T) { + type fields struct { + underlyingValue int + } + type args struct { + opt Option[int] + } + tests := []struct { + name string + fields fields + args args + want Option[int] + }{ + {name: "optSome[T] OrElse() returns original value with another optSome", fields: struct{ underlyingValue int }{underlyingValue: 3}, args: args{opt: optSome[int]{2}}, want: optSome[int]{3}}, + {name: "optSome[T] OrElse() returns original value with another optNone", fields: struct{ underlyingValue int }{underlyingValue: 3}, args: args{opt: optNone[int]{}}, want: optSome[int]{3}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.OrElse(tt.args.opt); !reflect.DeepEqual(got, tt.want) { + t.Errorf("OrElse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSome_String(t *testing.T) { + type fields struct { + underlyingValue int + } + tests := []struct { + name string + fields fields + want string + }{ + {name: "optSome[T] String() returns Some(T)", fields: struct{ underlyingValue int }{underlyingValue: 2}, want: "Some(2)"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := optSome[int]{ + underlyingValue: tt.fields.underlyingValue, + } + if got := s.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +}