diff --git a/README.md b/README.md index d6bfac0..01e2bad 100644 --- a/README.md +++ b/README.md @@ -2,38 +2,398 @@ A small framework to separate logics and data accesses for Golang application. -## Concept +## Concepts -Sabi is a small framework for Golang applications. -This framework separates an application to logic parts and data access parts, and enables to implement each of them independently, then to combine them. +The concept of this framework is separation and reintegration of necessary and +redundant parts based on the perspectives of the whole and the +parts. +The separation of logics and data accesses is the most prominent and +fundamental part of this concept. ### Separation of logics and data accesses -In general, a program consists of procedures and data. -And procedures include data accesses for operating data, and the rest of procedures are logics. -So we can say that a program consists of logics, data accesses and data. +In general, a program consists of procedures and data. And procedures include data accesses for operating data, and the rest of procedures are logics. So we can say that a program consists of logics, data accesses and data. -Furthermore, we often think to separate an application to multiple layers, for example, controller layer, application logic layer, and data access layer. -The logic and data access mentioned in this framework are partially matched those layers, but are not matched in another part. -For example, in the controller layer, there are input data and output data. (In a web application there are request data and response data, and in a command line application there are console input and output.) -Even though all logical processes are moved into the application logic layer, it is remained to transform input data of the controller layer into input data of the application logic layer, and to transform output data of the application logic layer into the output data of the controller layer. -The data accesses mentioned in this framework also includes those data accesses. +We often think to separate an application to multiple layers, for example, controller layer, business logic layer, and data access layer. +The logics and data accesses mentioned in this framework may appear to follow such layering. +However, the controller layer also has data accesses such as transforming user requests and responses for the business logic layer. +Generally, such layers of an application is established as vertical stages of data processing within a data flow. -### Changes composition of data access methods by concerns +In this framework, the relationship between logics and data accesses is not +defined by layers but by lanes. +Their relationship is vertical in terms of invocation, but is horizontal conceptually. +DaxBase serves as an intermediary that connects both of them. -Dax is a collection of data access methods. These methods will be collected/divided by data source from an implementation perspective. On the other hand, they will be collected/divided by logic from a usage perspective +### Separation of data accesses for each logic -In general programming, a developer chooses the necessary methods for their logic from among all available methods. And after programming, those methods will be buried in the program code of the logic, and it will become unclear which methods are used without tracing the logic. +A logic is a function that takes Dax interface as its only one argument. +The type of this Dax is a type parameter of the logic function, and also a type +parameter of the transaction function, Txn, that executes logics. -In applications using the Sabi framework, a logic is implemented as a function that takes only one argument, a dax interface. And this interface can define only the methods required by the logic. -Therefore, a dax interface can make clear which methods are used in a logic. And also, a dax interface can constraint methods available for a logic. +Therefore, since the type of Dax can be changed for each logic or transaction, +it is possible to limit data accesses used by the logic, by declaring only +necessary data access methods from among ones defined in DaxBase instance.. +At the same time, since all data accesses of a logic is done through this sole +Dax interface, this Dax interface serves as a list of data access methods used +by a logic. + +### Separation of data accesses by data sources and reintegration of them + +Data access methods are implemented as methods of some Dax structs that +embedding a DaxBase. +Furthermore these Dax structs can be integrated into a single new DaxBase. + +A Dax struct can be created in any unit, but it is clearer to create it in the +unit of the data source. +By doing so, the definition of a new DaxBase also serves as a list of the data +sources being used. ## Usage -The usage of this framework is described on the overview in the go package document. +### Logic and an interface for its data access + +A logic is implemented as a function. +This function takes only an argument, dax, which is an interface that gathers +only the data access methods needed by this logic function. + +Since dax conceals details of data access procedures, this function only +includes logical procedures. +In this logical part, there is no concern about where the data is input from or where it is output to. + +For example, in the following code, GreetLogic is a logic function and GreetDax +is a dax interface for GreetLogic. + +``` +type ( // possible error reasons + NoName struct {} + FailToGetHour struct {} + FailToOutput struct { Text string } +) + +type GreetDax interface { + sabi.Dax + UserName() (string, errs.Err) + Hour() int + Output(text string) errs.Err +} + +func GreetLogic(dax GreetDax) errs.Err { + hour, err := dax.Hour() + if err.IsNotOk() { + return err + } + + var s string + switch { + case 5 <= hour && hour < 12: + s = "Good morning, " + case 12 <= hour && hour < 16: + s = "Good afternoon, " + case 16 <= hour && hour < 21: + s = "Good evening, " + default: + s = "Hi, " + } + + err = dax.Output(s) + if err.IsNotOk() { + return err + } + + name, err := dax.UserName() + if err.IsNotOk() { + return err + } + + return dax.Output(name) +} +``` + +In GreetLogic, there are no codes for inputting the hour, inputting a user name, +and outputing a greeting. +This function has only concern to create a greeting text. + +### Data accesses for unit testing + +To test a logic function, the simplest dax struct is what using a map. +The following code is an example of a dax struct using a map and having +three methods that are same to GreetDax interface methods above. + +``` +type MapGreetDax struct { + sabi.Dax + m map[string]any +} + +func (dax MapGreetDax) UserName() (string, errs.Err) { + name, exists := dax.m["username"] + if !exists { + return "", errs.New(NoName{}) + } + return name.(string), errs.Ok() +} + +func (dax MapGreetDax) Hour() (int, errs.Err) { + hour, exists := dax.m["hour"] + if !exists { + return 0, errs.New(FailToGetHour{}) + } + return hour.(int), errs.Ok() +} + +func (dax MapGreetDax) Output(text string) errs.Err { + if m["greeting"] == "error" { // for testing the error case. + return errs.New(FailToOutput{Text: text}) + } + dax.m["greeting"] = text + return errs.Ok() +} + +func NewMapGreetDaxBase(m map[string]any) sabi.DaxBase { + base := sabi.NewDaxBase() + return struct { + sabi.DaxBase + MapGreetDax + } { + DaxBase: base, + MapGreetDax: MapGreetDax{m: m}, + } +} +``` + +And the following code is an example of a test case. + +``` +func TestGreetLogic_morning(t *testing.T) { + m := make(map[string]any) + base := NewGreetDaxBase(m) + + m["username"] = "everyone" + m["hour"] = 10 + err := sabi.Txn[GreetDax](base, GreetLogic) + if err.IsNotOk() { + t.Errorf(err.Error()) + } + if m["greeting"] == "Good morning, everyone" { + t.Errorf("Bad greeting: %v\n", m["greeting"]) + } +} +``` + +### Data accesses for actual use + +In actual use, multiple data sources are often used. +In this example, an user name and the hour are input as command line argument, +and greeting is output to console. +Therefore, two dax struct are created and they are integrated into a new struct +based on DaxBase. +Since Golang is structural typing language, this new DaxBase can be casted to +GreetDax. + +The following code is an example of a dax struct which inputs an user name and +the hour from command line argument. + +``` +type CliArgsDax struct { + sabi.Dax +} + +func (dax CliArgsDax) UserName() (string, errs.Err) { + if len(os.Args) <= 1 { + return "", errs.New(NoName{}) + } + return os.Args[1], errs.Ok() +} + +func (dax CliArgsDax) Hour() (string, errs.Err) { + if len(os.Args) <= 2 { + return 0, errs.New(FailToGetHour{}) + } + return os.Args[2], errs.Ok() +} +``` + +The following code is an example of a dax struct which output a text to +console. + +``` +type ConsoleDax struct { + sabi.Dax +} + +func (dax ConsoleDax) Output(text string) errs.Err { + fmt.Println(text) + return errs.Ok() +} +``` + +And the following code is an example of a constructor function of a struct +based on DaxBase into which the above two dax are integrated. +This implementation also serves as a list of the external data sources being +used. + +``` +func NewGreetDaxBase() sabi.DaxBase { + base := sabi.NewDaxBase() + return struct { + sabi.DaxBase + CliArgsDax + ConsoleDax + } { + DaxBase: base, + CliArgsDax: CliArgsDax{Dax: base}, + ConsoleDax: ConsoleDax{Dax: base}, + } +} +``` + +### Executing a logic + +The following code executes the above GreetLogic in a transaction process. + +``` +func main() { + if err := sabi.StartApp(app); err.IsNotOk() { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +func app() errs.Err { + base := NewBase() + defer base.Close() + + return sabi.Txn(base, GreetLogic)) +} +``` + +### Changing to a dax of another data source + +In the above codes, the hour is obtained from command line arguments. +Here, assume that the specification has been changed to retrieve it +from system clock instread. + +In this case, we can solve this by removing the Hour method from CliArgsDax +and creating a new Dax, SystemClockDax, which has Hour method to retrieve +a hour from system clock. + +``` +// func (dax CliArgsDax) Hour() (string, errs.Err) { // Removed +// if len(os.Args) <= 2 { +// return 0, errs.New(FailToGetHour{}) +// } +// return os.Args[2], errs.Ok() +// } + +type SystemClockDax struct { // Added + sabi.Dax +} -See https://pkg.go.dev/github.com/sttk/sabi#pkg-overview. +func (dax SystemClockTimeDax) Hour() (string, errs.Err) { // Added + return time.Now().Hour(), errs.Ok() +} +``` + +And the DaxBase struct, into which multiple dax structs have been integrated, +is modified as follows. + +``` +func NewGreetDaxBase() sabi.DaxBase { + base := sabi.NewDaxBase() + return struct { + sabi.DaxBase + CliArgsDax + SystemClockDax // Added + ConsoleDax + } { + DaxBase: base, + CliArgsDax: CliArgsDax{Dax: base}, + SystemClockDax: SystemClockDax{Dax: base}, // Added + ConsoleDax: ConsoleDax{Dax: base}, + } +} +``` + +### Moving outputs to next transaction process. + +The above codes works normally if no error occurs. +But if an error occurs at getting user name, a incomplete string is being +output to console. +Such behavior is not appropriate for transaction processing. + +So we should change the above codes to store in memory temporarily in the +existing transaction process, and output to console in the next transaction. + +The following code is the implementation of MemoryDax which is memory store +dax and the DaxBase struct after replacing ConsoleDax to MemoryDax. + +``` +type MemoryDax struct { // Added + sabi.Dax + text string +} + +func (dax *MemoryDax) Output(text string) errs.Err { // Added + dax.text = text + return errs.Ok() +} + +func (dax *MemoryDax) GetText() string { // Added + return dax.text +} + +func (dax ConsoleDax) Print(text string) errs.Err { // Changed from Output + fmt.Println(text) + return errs.Ok() +} + +func NewGreetDaxBase() sabi.DaxBase { + base := sabi.NewDaxBase() + return struct { + sabi.DaxBase + CliArgsDax + SystemClockDax + ConsoleDax + MemoryDax // Added + } { + DaxBase: base, + CliArgsDax: CliArgsDax{Dax: base}, + SystemClockDax: SystemClockDax{Dax: base}, + ConsoleDax: ConsoleDax{Dax: base}, + MemoryDax: MemoryDax{Dax: base}, // Added + } +} +``` + +The following code is the logic to output text to console in next transaction +process, the dax interface for the logic, and the execution of logics after +being changed. + +``` +type PrintDax interface { // Added + GetText() string + Print(text string) errs.Err +} + +func PrintLogic(dax PrintDax) errs.Err { // Added + text := dax.GetText() + return dax.Print(text) +} + +func app() errs.Err { + base := NewBase() + defer base.Close() + + return sabi.Txn(base, GreetLogic)). // Changed + IfOk(sabi.Txn_(base, PrintLogic)) // Added +} +``` + +The important point is that the GreetLogic function is not changed. +Since these changes are not related to the existing application logic, it is +limited to the data access part (and the part around the newly added logic) +only. ## Supporting Go versions @@ -46,21 +406,27 @@ This framework supports Go 1.18 or later. % gvm-fav Now using version go1.18.10 go version go1.18.10 darwin/amd64 -ok github.com/sttk/sabi 0.834s coverage: 99.6% of statements +ok github.com/sttk/sabi 0.614s coverage: 100.0% of statements +ok github.com/sttk/sabi/errs 0.793s coverage: 100.0% of statements -Now using version go1.19.5 -go version go1.19.5 darwin/amd64 -ok github.com/sttk/sabi 0.836s coverage: 99.6% of statements +Now using version go1.19.13 +go version go1.19.13 darwin/amd64 +ok github.com/sttk/sabi 0.559s coverage: 100.0% of statements +ok github.com/sttk/sabi/errs 0.755s coverage: 100.0% of statements -Now using version go1.20 -go version go1.20 darwin/amd64 -ok github.com/sttk/sabi 0.843s coverage: 99.6% of statements +Now using version go1.20.8 +go version go1.20.8 darwin/amd64 +ok github.com/sttk/sabi 0.566s coverage: 100.0% of statements +ok github.com/sttk/sabi/errs 0.833s coverage: 100.0% of statements -Back to go1.20 -Now using version go1.20 -% -``` +Now using version go1.21.1 +go version go1.21.1 darwin/amd64 +ok github.com/sttk/sabi 0.572s coverage: 100.0% of statements +ok github.com/sttk/sabi/errs 0.835s coverage: 100.0% of statements +Back to go1.21.1 +Now using version go1.21.1 +``` ## License diff --git a/async-group.go b/async-group.go new file mode 100644 index 0000000..4b642b1 --- /dev/null +++ b/async-group.go @@ -0,0 +1,80 @@ +// Copyright (C) 2023 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +package sabi + +import ( + "sync" + + "github.com/sttk/sabi/errs" +) + +// AsyncGroup is the interface to execute added functions asynchronously. +// The method: Add is to add target functions. +// This interface is used as an argument of DaxSrc#Setup, DaxConn#Commit, and DaxConn#Rollback. +type AsyncGroup interface { + Add(fn func() errs.Err) +} + +type errEntry[N comparable] struct { + name N + err errs.Err + next *errEntry[N] +} + +type asyncGroupAsync[N comparable] struct { + wg sync.WaitGroup + errHead *errEntry[N] + errLast *errEntry[N] + mutex sync.Mutex + name N +} + +func (ag *asyncGroupAsync[N]) Add(fn func() errs.Err) { + ag.wg.Add(1) + go func(name N) { + defer ag.wg.Done() + err := fn() + if err.IsNotOk() { + ag.mutex.Lock() + defer ag.mutex.Unlock() + ag.addErr(name, err) + } + }(ag.name) +} + +func (ag *asyncGroupAsync[N]) wait() { + ag.wg.Wait() +} + +func (ag *asyncGroupAsync[N]) addErr(name N, err errs.Err) { + ent := &errEntry[N]{name: name, err: err} + if ag.errLast == nil { + ag.errHead = ent + ag.errLast = ent + } else { + ag.errLast.next = ent + ag.errLast = ent + } +} + +func (ag *asyncGroupAsync[N]) hasErr() bool { + return (ag.errHead != nil) +} + +func (ag *asyncGroupAsync[N]) makeErrs() map[N]errs.Err { + m := make(map[N]errs.Err) + for ent := ag.errHead; ent != nil; ent = ent.next { + m[ent.name] = ent.err + } + return m +} + +type asyncGroupSync struct { + err errs.Err +} + +func (ag *asyncGroupSync) Add(fn func() errs.Err) { + ag.err = fn() +} diff --git a/async-group_test.go b/async-group_test.go new file mode 100644 index 0000000..4776251 --- /dev/null +++ b/async-group_test.go @@ -0,0 +1,157 @@ +package sabi + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/sttk/sabi/errs" +) + +func TestAsyncGroup_asyncGroupSync_ok(t *testing.T) { + var ag asyncGroupSync + assert.True(t, ag.err.IsOk()) + + exec := false + fn := func() errs.Err { + exec = true + return errs.Ok() + } + + ag.Add(fn) + assert.True(t, ag.err.IsOk()) + assert.True(t, exec) +} + +func TestAsyncGroup_asyncGroupSync_error(t *testing.T) { + var ag asyncGroupSync + assert.True(t, ag.err.IsOk()) + + type FailToDoSomething struct{} + + exec := false + fn := func() errs.Err { + exec = true + return errs.New(FailToDoSomething{}) + } + + ag.Add(fn) + switch ag.err.Reason().(type) { + case FailToDoSomething: + default: + assert.Fail(t, ag.err.Error()) + } + assert.True(t, exec) +} + +func TestAsyncGroup_asyncGroupAsync_ok(t *testing.T) { + var ag asyncGroupAsync[string] + assert.False(t, ag.hasErr()) + + exec := false + fn := func() errs.Err { + time.Sleep(50) + exec = true + return errs.Ok() + } + + ag.name = "foo" + ag.Add(fn) + assert.False(t, ag.hasErr()) + assert.False(t, exec) + + ag.wait() + assert.False(t, ag.hasErr()) + assert.True(t, exec) + + assert.Equal(t, len(ag.makeErrs()), 0) + assert.True(t, exec) +} + +func TestAsyncGroup_asyncGroupAsync_error(t *testing.T) { + var ag asyncGroupAsync[string] + assert.False(t, ag.hasErr()) + + type FailToDoSomething struct{} + + exec := false + fn := func() errs.Err { + time.Sleep(50) + exec = true + return errs.New(FailToDoSomething{}) + } + + ag.name = "foo" + ag.Add(fn) + assert.False(t, ag.hasErr()) + assert.False(t, exec) + + ag.wait() + assert.True(t, ag.hasErr()) + assert.True(t, exec) + + m := ag.makeErrs() + assert.Equal(t, len(m), 1) + switch m["foo"].Reason().(type) { + case FailToDoSomething: + default: + assert.Fail(t, m["foo"].Error()) + } + assert.True(t, exec) +} + +func TestAsyncGroup_asyncGroupAsync_multipleErrors(t *testing.T) { + var ag asyncGroupAsync[string] + assert.False(t, ag.hasErr()) + + type Err0 struct{} + type Err1 struct{} + type Err2 struct{} + + exec0 := false + exec1 := false + exec2 := false + + fn0 := func() errs.Err { + time.Sleep(200) + exec0 = true + return errs.New(Err0{}) + } + fn1 := func() errs.Err { + time.Sleep(400) + exec1 = true + return errs.New(Err1{}) + } + fn2 := func() errs.Err { + time.Sleep(800) + exec2 = true + return errs.New(Err2{}) + } + + ag.name = "foo0" + ag.Add(fn0) + ag.name = "foo1" + ag.Add(fn1) + ag.name = "foo2" + ag.Add(fn2) + assert.False(t, ag.hasErr()) + assert.False(t, exec0) + assert.False(t, exec1) + assert.False(t, exec2) + + ag.wait() + assert.True(t, ag.hasErr()) + assert.True(t, exec0) + assert.True(t, exec1) + assert.True(t, exec2) + + m := ag.makeErrs() + assert.Equal(t, len(m), 3) + assert.Equal(t, m["foo0"].ReasonName(), "Err0") + assert.Equal(t, m["foo1"].ReasonName(), "Err1") + assert.Equal(t, m["foo2"].ReasonName(), "Err2") + assert.True(t, exec0) + assert.True(t, exec1) + assert.True(t, exec2) +} diff --git a/benchmark/dax_src/benchmark_dax_src_test.go b/benchmark/dax_src/benchmark_dax_src_test.go deleted file mode 100644 index a63c502..0000000 --- a/benchmark/dax_src/benchmark_dax_src_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package sabi_test - -import ( - "testing" - - "github.com/sttk/sabi" -) - -type FooDaxConn struct{} - -func (conn FooDaxConn) Commit() sabi.Err { return sabi.Ok() } -func (conn FooDaxConn) Rollback() {} -func (conn FooDaxConn) Close() {} - -type FooDaxSrc struct{} - -func (ds FooDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - return FooDaxConn{}, sabi.Ok() -} -func (ds FooDaxSrc) SetUp() sabi.Err { return sabi.Ok() } -func (ds FooDaxSrc) End() {} - -func BenchmarkDaxSrc_AddGlobalDaxSrc(b *testing.B) { - b.StartTimer() - for i := 0; i < b.N; i++ { - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) - } - b.StopTimer() -} - -func BenchmarkDaxSrc_AddGlobalDaxSrcPointer(b *testing.B) { - b.StartTimer() - for i := 0; i < b.N; i++ { - sabi.AddGlobalDaxSrc("foo", &FooDaxSrc{}) - } - b.StopTimer() -} - -func NewFooDaxSrc() FooDaxSrc { - return FooDaxSrc{} -} - -func BenchmarkDaxSrc_AddGlobalDaxSrc_withNewFunction(b *testing.B) { - b.StartTimer() - for i := 0; i < b.N; i++ { - sabi.AddGlobalDaxSrc("foo", NewFooDaxSrc()) - } - b.StopTimer() -} - -func BenchmarkDaxSrc_StartUpGlobalDaxSrcs(b *testing.B) { - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) - b.StartTimer() - for i := 0; i < b.N; i++ { - sabi.StartUpGlobalDaxSrcs() - } - b.StopTimer() -} diff --git a/benchmark/err/benchmark_err_test.go b/benchmark/err/benchmark_err_test.go deleted file mode 100644 index 0deb5b4..0000000 --- a/benchmark/err/benchmark_err_test.go +++ /dev/null @@ -1,629 +0,0 @@ -package sabi_test - -import ( - "strconv" - "testing" - - "github.com/sttk/sabi" -) - -func unused(v any) {} - -func returnNilError() error { - return nil -} -func BenchmarkError_nil(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnNilError() - err = e - } - b.StopTimer() - unused(err) -} - -func returnOkErr() sabi.Err { - return sabi.Ok() -} -func BenchmarkErr_ok(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOkErr() - err = e - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_nil_isNil(b *testing.B) { - var err error - e := returnNilError() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e == nil { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_ok_isOk(b *testing.B) { - var err sabi.Err - e := returnOkErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e.IsOk() { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_nil_typeSwitch(b *testing.B) { - var err error - e := returnNilError() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.(type) { - case nil: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_ok_typeSwitch(b *testing.B) { - var err sabi.Err - e := returnOkErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.Reason().(type) { - case nil: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_nil_ErrorString(b *testing.B) { - var str string - b.StartTimer() - for i := 0; i < b.N; i++ { - s := "nil" - str = s - } - b.StopTimer() - unused(str) -} - -func BenchmarkErr_ok_ErrorString(b *testing.B) { - var str string - e := returnOkErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -type EmptyError struct { -} - -func returnEmptyError() error { - return EmptyError{} -} -func (e EmptyError) Error() string { - return "EmptyError" -} -func BenchmarkError_empty(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnEmptyError() - err = e - } - b.StopTimer() - unused(err) -} - -type EmptyReason struct { -} - -func returnEmptyReasonedErr() sabi.Err { - return sabi.NewErr(EmptyReason{}) -} -func BenchmarkErr_emptyReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnEmptyReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_empty_isNotNil(b *testing.B) { - var err error - e := returnEmptyError() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e != nil { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_emptyReason_isNotOk(b *testing.B) { - var err sabi.Err - e := returnEmptyReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - if !e.IsOk() { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_empty_typeSwitch(b *testing.B) { - var err error - e := returnEmptyError() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.(type) { - case EmptyError: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_emptyReason_typeSwitch(b *testing.B) { - var err sabi.Err - e := returnEmptyReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.Reason().(type) { - case EmptyReason: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_empty_ErrorString(b *testing.B) { - var str string - e := returnEmptyError() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -func BenchmarkErr_emptyReason_ErrorString(b *testing.B) { - var str string - e := returnEmptyReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) - unused(e) -} - -type OneFieldError struct { - FieldA string -} - -func (e OneFieldError) Error() string { - return "OneFieldError{FieldA:" + e.FieldA + "}" -} -func returnOneFieldError() error { - return OneFieldError{FieldA: "abc"} -} -func BenchmarkError_oneField(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOneFieldError() - err = e - } - b.StopTimer() - unused(err) -} - -type OneFieldReason struct { - FieldA string -} - -func returnOneFieldReasonedErr() sabi.Err { - return sabi.NewErr(OneFieldReason{FieldA: "abc"}) -} -func BenchmarkErr_oneFieldReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOneFieldReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -func returnOneFieldErrorPtr() error { - return &OneFieldError{FieldA: "abc"} -} -func BenchmarkError_oneFieldPtr(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOneFieldErrorPtr() - err = e - } - b.StopTimer() - unused(err) -} - -func returnOneFieldReasonedPtrErr() sabi.Err { - return sabi.NewErr(&OneFieldReason{FieldA: "abc"}) -} -func BenchmarkErr_oneFieldReasonPtr(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOneFieldReasonedPtrErr() - err = e - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_oneField_isNotNil(b *testing.B) { - var err error - e := returnOneFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e != nil { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_oneFieldReason_isNotOk(b *testing.B) { - var err sabi.Err - e := returnOneFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - if !e.IsOk() { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_oneField_typeSwitch(b *testing.B) { - var err error - e := returnOneFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.(type) { - case OneFieldError: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_oneFieldReason_typeSwitch(b *testing.B) { - var err sabi.Err - e := returnOneFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.Reason().(type) { - case OneFieldReason: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_oneField_ErrorString(b *testing.B) { - var str string - e := returnOneFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -func BenchmarkErr_oneFieldReason_ErrorString(b *testing.B) { - var str string - e := returnOneFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -type FiveFieldError struct { - FieldA string - FieldB int - FieldC bool - FieldD string - FieldE string -} - -func (e FiveFieldError) Error() string { - return "FiveFieldError{FieldA:" + e.FieldA + - ",FieldB:" + strconv.Itoa(e.FieldB) + - ",FieldC:" + strconv.FormatBool(e.FieldC) + - ",FieldD:" + e.FieldD + ",FieldE:" + e.FieldE + - "}" -} -func returnFiveFieldError() error { - return FiveFieldError{ - FieldA: "abc", FieldB: 123, FieldC: true, FieldD: "def", FieldE: "ghi", - } -} -func BenchmarkError_fiveField(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnFiveFieldError() - err = e - } - b.StopTimer() - unused(err) -} - -type FiveFieldReason struct { - FieldA string - FieldB int - FieldC bool - FieldD string - FieldE string -} - -func returnFiveFieldReasonedErr() sabi.Err { - return sabi.NewErr(FiveFieldReason{ - FieldA: "abc", FieldB: 123, FieldC: true, FieldD: "def", FieldE: "ghi", - }) -} -func BenchmarkErr_fiveFieldReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnFiveFieldReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_fiveField_isNotNil(b *testing.B) { - var err error - e := returnFiveFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e != nil { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_fiveFieldReason_isNotOk(b *testing.B) { - var err sabi.Err - e := returnFiveFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - if !e.IsOk() { - err = e - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_fiveField_typeSwitch(b *testing.B) { - var err error - e := returnFiveFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.(type) { - case FiveFieldError: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_fiveFieldReason_typeSwitch(b *testing.B) { - var err sabi.Err - e := returnFiveFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - switch e.Reason().(type) { - case FiveFieldReason: - err = e - default: - b.Fail() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_fiveField_ErrorString(b *testing.B) { - var str string - e := returnFiveFieldError() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -func BenchmarkErr_fiveFieldReason_ErrorString(b *testing.B) { - var str string - e := returnFiveFieldReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -type HavingCauseError struct { - Cause error -} - -func (e HavingCauseError) Error() string { - return "HavingCauseError{cause:" + e.Cause.Error() + "}" -} -func (e HavingCauseError) Unwrap() error { - return e.Cause -} -func returnHavingCauseError() error { - return HavingCauseError{Cause: EmptyError{}} -} -func BenchmarkError_havingCause(b *testing.B) { - var err error - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnHavingCauseError() - err = e - } - b.StopTimer() - unused(err) -} - -type HavingCauseReason struct { -} - -func returnHavingCauseReasonedErr() sabi.Err { - return sabi.NewErr(HavingCauseError{}, EmptyError{}) -} -func BenchmarkErr_havingCauseReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnHavingCauseReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -func BenchmarkError_havingCause_ErrorString(b *testing.B) { - var str string - e := returnHavingCauseError() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -func BenchmarkErr_havingCauseReason_ErrorString(b *testing.B) { - var str string - e := returnHavingCauseReasonedErr() - b.StartTimer() - for i := 0; i < b.N; i++ { - s := e.Error() - str = s - } - b.StopTimer() - unused(str) -} - -func fn() sabi.Err { return sabi.Ok() } - -func BenchmarkError_IfStatement(b *testing.B) { - var err sabi.Err - var e error = nil - b.StartTimer() - for i := 0; i < b.N; i++ { - if e == nil { - err = fn() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_IfStatement(b *testing.B) { - var err sabi.Err - e := sabi.Ok() - b.StartTimer() - for i := 0; i < b.N; i++ { - if e.IsOk() { - err = fn() - } - } - b.StopTimer() - unused(err) -} - -func BenchmarkErr_IfOk(b *testing.B) { - var err sabi.Err - e := sabi.Ok() - b.StartTimer() - for i := 0; i < b.N; i++ { - err = e.IfOk(fn) - } - b.StopTimer() - unused(err) -} diff --git a/benchmark/notify/benchmark_notify_test.go b/benchmark/notify/benchmark_notify_test.go deleted file mode 100644 index 8e128a6..0000000 --- a/benchmark/notify/benchmark_notify_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package sabi_test - -import ( - "testing" - - "github.com/sttk/sabi" -) - -func unused(v any) {} - -func returnOkErr() sabi.Err { - return sabi.Ok() -} - -func BenchmarkNotify_addErrHandler(b *testing.B) { - b.StartTimer() - //sabi.AddSyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) {}) - sabi.AddAsyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) {}) - sabi.FixErrCfgs() - b.StopTimer() -} - -func BenchmarkNotify_ok(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOkErr() - err = e - } - b.StopTimer() - unused(err) -} - -type EmptyReason struct { -} - -func returnEmptyReasonedErr() sabi.Err { - return sabi.NewErr(EmptyReason{}) -} - -func BenchmarkNotify_emptyReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnEmptyReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -type OneFieldReason struct { - FieldA string -} - -func returnOneFieldReasonedErr() sabi.Err { - return sabi.NewErr(OneFieldReason{FieldA: "abc"}) -} - -func BenchmarkNotify_oneFieldReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnOneFieldReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -type FiveFieldReason struct { - FieldA string - FieldB int - FieldC bool - FieldD string - FieldE string -} - -func returnFiveFieldReasonedErr() sabi.Err { - return sabi.NewErr(FiveFieldReason{ - FieldA: "abc", FieldB: 123, FieldC: true, FieldD: "def", FieldE: "ghi", - }) -} - -func BenchmarkNotify_fiveFieldReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnFiveFieldReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} - -type EmptyError struct { -} - -func (e EmptyError) Error() string { - return "EmptyError" -} - -type HavingCauseError struct { - Cause error -} - -func (e HavingCauseError) Error() string { - return "HavingCauseError{cause:" + e.Cause.Error() + "}" -} - -func returnHavingCauseReasonedErr() sabi.Err { - return sabi.NewErr(HavingCauseError{}, EmptyError{}) -} - -func BenchmarkNotify_havingCauseReason(b *testing.B) { - var err sabi.Err - b.StartTimer() - for i := 0; i < b.N; i++ { - e := returnHavingCauseReasonedErr() - err = e - } - b.StopTimer() - unused(err) -} diff --git a/dax.go b/dax.go index cae4036..f3a8729 100644 --- a/dax.go +++ b/dax.go @@ -5,354 +5,482 @@ package sabi import ( - "reflect" "sync" + + om "github.com/sttk/orderedmap" + + "github.com/sttk/sabi/errs" ) type /* error reasons */ ( - // FailToStartUpGlobalDaxSrcs is an error reason which indicates that some - // dax sources failed to start up. - // The field: Errors is a map of which keys are the registered names of - // DaxSrc(s) which failed to start up, and of which values are Err having - // their error reasons. - FailToStartUpGlobalDaxSrcs struct { - Errors map[string]Err - } - - // DaxSrcIsNotFound is an error reason which indicates that a specified data - // source is not found. - // The field: Name is a registered name of a DaxSrc not found. + // FailToSetupGlobalDaxSrcs is the error reason which indicates that some + // DaxSrc(s) failed to set up. + // The field Errors is the map of which keys are the registered names of + // DaxSrc(s) that failed, and of which values are errs.Err having their error + // reasons. + FailToSetupGlobalDaxSrcs struct { + Errors map[string]errs.Err + } + + // FailToSetupLocalDaxSrc is the error reason which indicates that a local + // DaxSrc failed to set up. + // The field Name is the registered name of the DaxSrc failed. + FailToSetupLocalDaxSrc struct { + Name string + } + + // DaxSrcIsNotFound is the error reason which indicates that a specified + // DaxSrc is not found. + // The field Name is the registered name of the DaxSrc not found. DaxSrcIsNotFound struct { Name string } - // FailToCreateDaxConn is an error reason which indicates that it's failed to - // create a new connection to a data store. - // The field: Name is a registered name of a DaxSrc which failed to create a + // FailToCreateDaxConn is the error reason which indicates that it is failed + // to create a new connection to a data store. + // The field Name is the registered name of the DaxSrc failed to create a // DaxConn. FailToCreateDaxConn struct { Name string } - // FailToCastConn is an error reason which indicates that it is failed to - // cast type of a DaxConn. - // The field: Name is a registered name of a DaxSrc which created the target + // FailToCommitDaxConn is the error reason which indicates that some + // connections failed to commit. + // The field Errors is the map of which keys are registered names of DaxConn + // which failed to commit, and of which values are errs.Err(s) having their + // error reasons. + FailToCommitDaxConn struct { + Errors map[string]errs.Err + } + + // CreatedDaxConnIsNil is the error reason which indicates that a DaxSrc + // created a DaxConn instance but it is nil. + // The field Name is the registered name of the DaxSrc that created a nil // DaxConn. - // And the fields: FromType and ToType are the types of the source DaxConn - // and the destination DaxConn. + CreatedDaxConnIsNil struct { + Name string + } + + // FailToCastDaxConn is the error reason which indicates that a DaxConn + // instance of the specified name failed to cast to the destination type. + // The field Name and FromType is the name and type name of the DaxConn + // instance, and the field ToType is the type name of the destination type. FailToCastDaxConn struct { Name, FromType, ToType string } - // FailToCommitDaxConn is an error reason which indicates that some - // connections failed to commit. - // The field: Errors is a map of which keys are the registered names of - // DaxConn which failed to commit, and of which values are Err having their - // error reasons. - FailToCommitDaxConn struct { - Errors map[string]Err + // FailToCastDaxBase is the error reason which indicates that a DaxBase instance + // failed to cast to the destination type. + // The field FromType is the type name of the DaxBase instance and the field + // ToType is the type name of the destination type. + FailToCastDaxBase struct { + FromType, ToType string } ) -// DaxConn is an interface which represents a connection to a data store, and -// defines methods: Commit, Rollback and Close to work in a tranaction process. +// DaxConn is the interface that represents a connection to a data store, and +// defines methods: Commit, Rollback and Close to work in a transaction +// process. +// +// Commit is the method for commiting updates in a transaction. +// IsCommitted is the method for check whether updates are already committed. +// Rollback is the method for rollback updates in a transaction. +// ForceBack is the method to revert updates forcely even if updates are +// already commited or this connection ooes not have rollback mechanism. +// If commting and rollbacking procedures are asynchronous, the argument +// AsyncGroup(s) are used to process them. +// Close is the method to close this connecton. type DaxConn interface { - Commit() Err - Rollback() + Commit(ag AsyncGroup) errs.Err + IsCommitted() bool + Rollback(ag AsyncGroup) + ForceBack(ag AsyncGroup) Close() } -// DaxSrc is an interface which represents a data connection source for a data -// store like database, etc., and creates a DaxConn which is a connection to a -// data store. -// This interface defines a method: CreateDaxConn to creates a DaxConn instance -// and returns its pointer. -// This interface also defines methods: SetUp and End, which makes available -// and frees this dax source. +// DaxSrc is the interface that represents a data source which creates +// connections to a data store like database, etc. +// This interface declares three methods: Setup, Close, and CreateDaxConn. +// +// Setup is the method to connect to a data store and to prepare to create +// DaxConn objects which represents a connection to access data in the data +// store. +// If the set up procedure is asynchronous, the Setup method is implemented +// so as to use AsyncGroup. +// Close is the method to disconnect to a data store. +// CreateDaxConn is the method to create a DaxConn object. type DaxSrc interface { - CreateDaxConn() (DaxConn, Err) - SetUp() Err - End() + Setup(ag AsyncGroup) errs.Err + Close() + CreateDaxConn() (DaxConn, errs.Err) } -// Dax is an interface for a set of data access methods. -// This method gets a DaxConn which is a connection to a data store by -// specified name. -// If a DaxConn is found, this method returns it, but not found, creates a new -// one with a local or global DaxSrc associated with same name. -// If there are both local and global DaxSrc with same name, the local DaxSrc -// is used. -type Dax interface { - getDaxConn(name string) (DaxConn, Err) +type daxSrcEntry struct { + name string + ds DaxSrc + local bool + deleted bool + prev *daxSrcEntry + next *daxSrcEntry +} + +type daxSrcEntryList struct { + head *daxSrcEntry + last *daxSrcEntry } var ( - isGlobalDaxSrcsFixed bool = false - globalDaxSrcMap map[string]DaxSrc = make(map[string]DaxSrc) - globalDaxSrcMutex sync.Mutex + isGlobalDaxSrcsFixed bool = false + globalDaxSrcEntryList daxSrcEntryList ) -// AddGlobalDaxSrc registers a global DaxSrc with its name to make enable to -// use DaxSrc in all transactions. -// This method ignores to add a global DaxSrc when its name is already -// registered. -// In addition, this method ignores to add any more global DaxSrc(s) after -// calling FixGlobalDaxSrcs function. -func AddGlobalDaxSrc(name string, ds DaxSrc) { - globalDaxSrcMutex.Lock() - defer globalDaxSrcMutex.Unlock() - - if !isGlobalDaxSrcsFixed { - _, exists := globalDaxSrcMap[name] - if !exists { - globalDaxSrcMap[name] = ds - } +// Uses is the method that registers a global DaxSrc with its name to enable to +// use DaxConn created by the argument DaxSrc in all transactions. +// +// If a DaxSrc is tried to register with a name already registered, it is +// ignored and a DaxSrc registered with the same name first is used. +// And this method ignore adding new DaxSrc(s) after Setup or first beginning +// of Proc or Txn. +func Uses(name string, ds DaxSrc) errs.Err { + if isGlobalDaxSrcsFixed { + return errs.Ok() } -} - -// StartUpGlobalDaxSrcs is a function to forbid adding more global dax sources -// and to make available the registered global dax sources by calling Setup -// method of each DaxSrc. -// If even one DaxSrc fail to execute its SstUp method, this function -// executes Free methods of all global DaxSrc(s) and returns sabi.Err. -func StartUpGlobalDaxSrcs() Err { - isGlobalDaxSrcsFixed = true - ch := make(chan namedErr) + ent := &daxSrcEntry{name: name, ds: ds} - for name, ds := range globalDaxSrcMap { - go func(name string, ds DaxSrc, ch chan namedErr) { - err := ds.SetUp() - ne := namedErr{name: name, err: err} - ch <- ne - }(name, ds, ch) + if globalDaxSrcEntryList.head == nil { + globalDaxSrcEntryList.head = ent + globalDaxSrcEntryList.last = ent + } else { + ent.prev = globalDaxSrcEntryList.last + globalDaxSrcEntryList.last.next = ent + globalDaxSrcEntryList.last = ent } - errs := make(map[string]Err) - n := len(globalDaxSrcMap) - for i := 0; i < n; i++ { - select { - case ne := <-ch: - if !ne.err.IsOk() { - errs[ne.name] = ne.err - } + return errs.Ok() +} + +// Setup is the function that make the globally registered DaxSrc usable. +// +// This function forbids adding more global DaxSrc(s), and calls each Setup +// method of all registered DaxSrc(s). +// If one of DaxSrc(s) fails to execute synchronous Setup, this function stops +// other setting up and returns an errs.Err containing the error reason of +// that failure. +// +// If one of DaxSrc(s) fails to execute asynchronous Setup, this function +// continue to other setting up and returns an errs.Err containing the error +// reason of that failure and other errors if any. +func Setup() errs.Err { + isGlobalDaxSrcsFixed = true + errs.FixCfg() + + var ag asyncGroupAsync[string] + + for ent := globalDaxSrcEntryList.head; ent != nil; ent = ent.next { + ag.name = ent.name + err := ent.ds.Setup(&ag) + if err.IsNotOk() { + ag.wait() + Close() + ag.addErr(ag.name, err) + return errs.New(FailToSetupGlobalDaxSrcs{Errors: ag.makeErrs()}) } } - if len(errs) > 0 { - ShutdownGlobalDaxSrcs() - return NewErr(FailToStartUpGlobalDaxSrcs{Errors: errs}) + ag.wait() + + if ag.hasErr() { + Close() + return errs.New(FailToSetupGlobalDaxSrcs{Errors: ag.makeErrs()}) } - return Ok() + return errs.Ok() } -// ShutdownGlobalDaxSrcs is a function to terminate all global dax sources. -func ShutdownGlobalDaxSrcs() { - var wg sync.WaitGroup - wg.Add(len(globalDaxSrcMap)) +// Close is the function that closes and frees each resource of registered +// global DaxSrc(s). +// This function should always be called before an application ends. +func Close() { + for ent := globalDaxSrcEntryList.head; ent != nil; ent = ent.next { + ent.ds.Close() + } +} - for _, ds := range globalDaxSrcMap { - go func(ds DaxSrc) { - defer wg.Done() - ds.End() - }(ds) +// StartApp is the function that calls Setup function, the argument function, +// and Close function in order. +// If Setup function or the argument function fails, this function stops +// calling other functions and return an errs.Err containing the error +// reaason.. +// +// This function is a macro-like function aimed at reducing the amount of +// coding. +func StartApp(app func() errs.Err) errs.Err { + err := Setup() + if err.IsNotOk() { + return err } + defer Close() - wg.Wait() + return app() } -// DaxBase is an interface which works as a front of an implementation as a -// base of data connection sources, and defines methods: SetUpLocalDaxSrc and -// FreeLocalDaxSrc. +// Dax is the interface for a set of data access methods. // -// SetUpLocalDaxSrc method registered a DaxSrc with a name in this -// implementation, but ignores to add a local DaxSrc when its name is already -// registered. -// In addition, this method ignores to add local DaxSrc(s) while the -// transaction is processing. +// This interface is embedded by Dax implementations for data +// stores, and each Dax implementation defines data access methods to each +// data store. +// In data access methods, DacConn instances connected to data stores can be +// got with GetConn function. // -// This interface inherits Dax interface to get a DaxConn by a name. -// Also, this has unexported methods for a transaction process. +// At the same time, this interface is embedded by Dax interfaces for logics. +// The each Dax interface declares only methods used by each logic. +type Dax interface { + getDaxConn(name string) (DaxConn, errs.Err) +} + +// DaxBase is the interface that declares the methods to manage DaxSrc(s). +// And this interface declarees unexported methods to process a transaction. +// +// Close is the method to close and free all local DaxSrc(s). +// Uses is the method to register and setup a local DaxSrc with an argument +// name. +// Uses_ is the method that creates a runner function which runs #Uses method. +// Disuses is the method to close and remove a local DaxSrc specified by +// an argument name. +// Disuses_ is the method that creates a runner function which runs #Disuses +// method. type DaxBase interface { Dax - SetUpLocalDaxSrc(name string, ds DaxSrc) Err - FreeLocalDaxSrc(name string) - FreeAllLocalDaxSrcs() + + Close() + Uses(name string, ds DaxSrc) errs.Err + Uses_(name string, ds DaxSrc) func() errs.Err + Disuses(name string) + Disuses_(name string) func() errs.Err + begin() - commit() Err + commit() errs.Err rollback() end() } type daxBaseImpl struct { - isLocalDaxSrcsFixed bool - localDaxSrcMap map[string]DaxSrc - daxConnMap map[string]DaxConn - daxConnMutex sync.Mutex + DaxBase + + isLocalDaxSrcsFixed bool + localDaxSrcEntryList daxSrcEntryList + + daxSrcEntryMap map[string]*daxSrcEntry + agSync asyncGroupSync + + daxConnMap om.Map[string, DaxConn] + daxConnMutex sync.Mutex } -// NewDaxBase is a function which creates a new DaxBase. +// NewDaxBase is the function that creates a new DaxBase instance. func NewDaxBase() DaxBase { - return &daxBaseImpl{ - isLocalDaxSrcsFixed: false, - localDaxSrcMap: make(map[string]DaxSrc), - daxConnMap: make(map[string]DaxConn), + isGlobalDaxSrcsFixed = true + errs.FixCfg() + + base := &daxBaseImpl{ + daxSrcEntryMap: make(map[string]*daxSrcEntry), + daxConnMap: om.New[string, DaxConn](), + } + + for ent := globalDaxSrcEntryList.last; ent != nil; ent = ent.prev { + base.daxSrcEntryMap[ent.name] = ent } + + return base } -func (base *daxBaseImpl) SetUpLocalDaxSrc(name string, ds DaxSrc) Err { - base.daxConnMutex.Lock() - defer base.daxConnMutex.Unlock() +func (base *daxBaseImpl) Close() { + if base.isLocalDaxSrcsFixed { + return + } - if !base.isLocalDaxSrcsFixed { - _, exists := base.localDaxSrcMap[name] - if !exists { - err := ds.SetUp() - if !err.IsOk() { - return err - } - base.localDaxSrcMap[name] = ds + for ent := base.localDaxSrcEntryList.head; ent != nil; ent = ent.next { + if !ent.deleted { + ent.deleted = true + ent.ds.Close() } } - return Ok() + base.localDaxSrcEntryList.head = nil + base.localDaxSrcEntryList.last = nil } -func (base *daxBaseImpl) FreeLocalDaxSrc(name string) { - base.daxConnMutex.Lock() - defer base.daxConnMutex.Unlock() +func (base *daxBaseImpl) Uses(name string, ds DaxSrc) errs.Err { + if base.isLocalDaxSrcsFixed { + return errs.Ok() + } - if !base.isLocalDaxSrcsFixed { - ds, exists := base.localDaxSrcMap[name] - if exists { - delete(base.localDaxSrcMap, name) - ds.End() - } + err := ds.Setup(&(base.agSync)) + if err.IsNotOk() { + return errs.New(FailToSetupLocalDaxSrc{Name: name}, err) } -} -func (base *daxBaseImpl) FreeAllLocalDaxSrcs() { - base.daxConnMutex.Lock() - defer base.daxConnMutex.Unlock() + if base.agSync.err.IsNotOk() { + return errs.New(FailToSetupLocalDaxSrc{Name: name}, base.agSync.err) + } - if !base.isLocalDaxSrcsFixed { - for _, ds := range base.localDaxSrcMap { - ds.End() - } + ent := &daxSrcEntry{name: name, ds: ds, local: true} - base.localDaxSrcMap = make(map[string]DaxSrc) + if base.localDaxSrcEntryList.head == nil { + base.localDaxSrcEntryList.head = ent + base.localDaxSrcEntryList.last = ent + } else { + ent.prev = base.localDaxSrcEntryList.last + base.localDaxSrcEntryList.last.next = ent + base.localDaxSrcEntryList.last = ent } + + base.daxSrcEntryMap[ent.name] = ent + + return errs.Ok() } -func (base *daxBaseImpl) getDaxConn(name string) (DaxConn, Err) { - conn := base.daxConnMap[name] - if conn != nil { - return conn, Ok() +func (base *daxBaseImpl) Uses_(name string, ds DaxSrc) func() errs.Err { + return func() errs.Err { + return base.Uses(name, ds) } +} - ds := base.localDaxSrcMap[name] - if ds == nil { - ds = globalDaxSrcMap[name] - } - if ds == nil { - return nil, NewErr(DaxSrcIsNotFound{Name: name}) +func (base *daxBaseImpl) Disuses(name string) { + if base.isLocalDaxSrcsFixed { + return } - base.daxConnMutex.Lock() - defer base.daxConnMutex.Unlock() + ent := base.daxSrcEntryMap[name] + if ent != nil && ent.local { + ent.deleted = true - conn = base.daxConnMap[name] - if conn != nil { - return conn, Ok() - } + if ent.prev != nil { + ent.prev.next = ent.next + } else { + base.localDaxSrcEntryList.head = ent.next + } - var err Err - conn, err = ds.CreateDaxConn() - if !err.IsOk() { - return nil, NewErr(FailToCreateDaxConn{Name: name}, err) - } + if ent.next != nil { + ent.next.prev = ent.prev + } else { + base.localDaxSrcEntryList.last = ent.prev + } - base.daxConnMap[name] = conn + ent.ds.Close() + } +} - return conn, Ok() +func (base *daxBaseImpl) Disuses_(name string) func() errs.Err { + return func() errs.Err { + base.Disuses(name) + return errs.Ok() + } } func (base *daxBaseImpl) begin() { base.isLocalDaxSrcsFixed = true - isGlobalDaxSrcsFixed = true } -type namedErr struct { - name string - err Err -} +func (base *daxBaseImpl) commit() errs.Err { + var ag asyncGroupAsync[string] -func (base *daxBaseImpl) commit() Err { - ch := make(chan namedErr) - - for name, conn := range base.daxConnMap { - go func(name string, conn DaxConn, ch chan namedErr) { - err := conn.Commit() - ne := namedErr{name: name, err: err} - ch <- ne - }(name, conn, ch) - } - - errs := make(map[string]Err) - n := len(base.daxConnMap) - for i := 0; i < n; i++ { - select { - case ne := <-ch: - if !ne.err.IsOk() { - errs[ne.name] = ne.err - } + for ent := base.daxConnMap.Front(); ent != nil; ent = ent.Next() { + ag.name = ent.Key() + err := ent.Value().Commit(&ag) + if err.IsNotOk() { + ag.wait() + ag.addErr(ent.Key(), err) + return errs.New(FailToCommitDaxConn{Errors: ag.makeErrs()}) } } - if len(errs) > 0 { - return NewErr(FailToCommitDaxConn{Errors: errs}) + ag.wait() + + if ag.hasErr() { + return errs.New(FailToCommitDaxConn{Errors: ag.makeErrs()}) } - return Ok() + return errs.Ok() } func (base *daxBaseImpl) rollback() { - var wg sync.WaitGroup - wg.Add(len(base.daxConnMap)) + var ag asyncGroupAsync[string] - for _, conn := range base.daxConnMap { - go func(conn DaxConn) { - defer wg.Done() - conn.Rollback() - }(conn) + for ent := base.daxConnMap.Front(); ent != nil; ent = ent.Next() { + conn := ent.Value() + if conn.IsCommitted() { + ent.Value().ForceBack(&ag) + } else { + ent.Value().Rollback(&ag) + } } - wg.Wait() + ag.wait() } func (base *daxBaseImpl) end() { - var wg sync.WaitGroup - wg.Add(len(base.daxConnMap)) + for { + ent := base.daxConnMap.FrontAndLdelete() + if ent == nil { + break + } + ent.Value().Close() + } + + base.isLocalDaxSrcsFixed = false +} - for _, conn := range base.daxConnMap { - go func(conn DaxConn) { - defer wg.Done() - conn.Close() - }(conn) +func (base *daxBaseImpl) getDaxConn(name string) (DaxConn, errs.Err) { + conn, loaded := base.daxConnMap.Load(name) + if loaded { + return conn, errs.Ok() } - base.daxConnMap = make(map[string]DaxConn) + base.daxConnMutex.Lock() + defer base.daxConnMutex.Unlock() - wg.Wait() + conn, _, e := base.daxConnMap.LoadOrStoreFunc(name, func() (DaxConn, error) { + ent, exists := base.daxSrcEntryMap[name] + if !exists { + return nil, errs.New(DaxSrcIsNotFound{Name: name}) + } - base.isLocalDaxSrcsFixed = false + if ent.deleted && ent.local { + for gEnt := globalDaxSrcEntryList.head; gEnt != nil; gEnt = gEnt.next { + if gEnt.name == name { + base.daxSrcEntryMap[ent.name] = gEnt + ent = gEnt + break + } + } + if ent.deleted { + return nil, errs.New(DaxSrcIsNotFound{Name: name}) + } + } + + conn, err := ent.ds.CreateDaxConn() + if err.IsNotOk() { + return nil, errs.New(FailToCreateDaxConn{Name: name}, err) + } + if conn == nil { + return nil, errs.New(CreatedDaxConnIsNil{Name: name}) + } + return conn, nil + }) + + if e != nil { + return nil, e.(errs.Err) + } + return conn, errs.Ok() } -// GetDaxConn is a function to cast type of DaxConn instance. -// If it's failed to cast to a destination type, this function returns an Err -// of a reason: FailToGetDaxConn. -func GetDaxConn[C DaxConn](dax Dax, name string) (C, Err) { +// GetDaxConn is the function to cast type of DaxConn instance. +// If the cast failed, this function returns an errs.Err of the reason: +// FailToCastDaxConn with the DaxConn name and type names of source and +// destination. +func GetDaxConn[C DaxConn](dax Dax, name string) (C, errs.Err) { conn, err := dax.getDaxConn(name) if err.IsOk() { casted, ok := conn.(C) @@ -360,25 +488,63 @@ func GetDaxConn[C DaxConn](dax Dax, name string) (C, Err) { return casted, err } - var from string - t := reflect.TypeOf(conn) - if t.Kind() == reflect.Ptr { - t = t.Elem() - from = "*" + t.Name() + " (" + t.PkgPath() + ")" - } else { - from = t.Name() + " (" + t.PkgPath() + ")" - } + from := typeNameOf(conn) + to := typeNameOfTypeParam[C]() + err = errs.New(FailToCastDaxConn{Name: name, FromType: from, ToType: to}) + } - var to string - t = reflect.TypeOf(casted) - if t.Kind() == reflect.Ptr { - t = t.Elem() - to = "*" + t.Name() + " (" + t.PkgPath() + ")" - } else { - to = t.Name() + " (" + t.PkgPath() + ")" + return *new(C), err +} + +// Txn is the function that executes logic functions in a transaction. +// +// First, this function casts the argument DaxBase to the type specified with +// the function's type parameter. +// Next, this function begins the transaction, and the argument logic functions +// are executed.. +// Then, if no error occurs, this function commits all updates in the +// transaction, otherwise rollbacks them. +// If there are commit errors after some DaxConn(s) are commited, or there are +// DaxConn(s) which don't have rollback mechanism, this function executes +// ForceBack methods of those DaxConn(s). +// And after that, this function ends the transaction. +// +// During a transaction, it is denied to add or remove any local DaxSrc(s). +func Txn[D Dax](base DaxBase, logics ...func(dax D) errs.Err) errs.Err { + dax, ok := base.(D) + if !ok { + from := typeNameOf(&base)[1:] + to := typeNameOfTypeParam[D]() + return errs.New(FailToCastDaxBase{FromType: from, ToType: to}) + } + + base.begin() + defer base.end() + + err := errs.Ok() + + for _, logic := range logics { + err = logic(dax) + if err.IsNotOk() { + break } - err = NewErr(FailToCastDaxConn{Name: name, FromType: from, ToType: to}) } - return *new(C), err + if err.IsOk() { + err = base.commit() + } + + if err.IsNotOk() { + base.rollback() + } + + return err +} + +// Txn_ is the function that creates a runner function which runs a Txn +// function. +func Txn_[D Dax](base DaxBase, logics ...func(dax D) errs.Err) func() errs.Err { + return func() errs.Err { + return Txn[D](base, logics...) + } } diff --git a/dax_aux_for_test.go b/dax_aux_for_test.go deleted file mode 100644 index bb577fc..0000000 --- a/dax_aux_for_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package sabi - -func ClearGlobalDaxSrcs() { - isGlobalDaxSrcsFixed = false - globalDaxSrcMap = make(map[string]DaxSrc) -} - -func IsGlobalDaxSrcsFixed() bool { - return isGlobalDaxSrcsFixed -} - -func GlobalDaxSrcMap() map[string]DaxSrc { - return globalDaxSrcMap -} - -func IsLocalDaxSrcsFixed(base DaxBase) bool { - return base.(*daxBaseImpl).isLocalDaxSrcsFixed -} - -func LocalDaxSrcMap(base DaxBase) map[string]DaxSrc { - return base.(*daxBaseImpl).localDaxSrcMap -} - -func DaxConnMap(base DaxBase) map[string]DaxConn { - return base.(*daxBaseImpl).daxConnMap -} - -func Begin(base DaxBase) { - base.begin() -} - -func Commit(base DaxBase) Err { - return base.commit() -} - -func Rollback(base DaxBase) { - base.rollback() -} - -func End(base DaxBase) { - base.end() -} diff --git a/dax_dummy_for_test.go b/dax_dummy_for_test.go deleted file mode 100644 index a9a344d..0000000 --- a/dax_dummy_for_test.go +++ /dev/null @@ -1,470 +0,0 @@ -package sabi_test - -import ( - "container/list" - - "github.com/sttk/sabi" -) - -var ( - Logs list.List - - WillFailToSetUpFooDaxSrc bool - WillFailToCommitFooDaxConn bool - WillFailToCreateFooDaxConn bool - - WillFailToCreateBDaxConn = false - WillFailToCommitBDaxConn = false -) - -func ClearDaxBase() { - sabi.ClearGlobalDaxSrcs() - - Logs.Init() - - WillFailToSetUpFooDaxSrc = false - WillFailToCommitFooDaxConn = false - WillFailToCreateFooDaxConn = false - - WillFailToCreateBDaxConn = false - WillFailToCommitBDaxConn = false -} - -type /* error reasons */ ( - FailToDoSomething struct{ Text string } - - FailToCreateBDaxConn struct{} - FailToCommitBDaxConn struct{} - FailToRunLogic struct{} -) - -// FooDaxConn - -type FooDaxConn struct { - Label string - Map map[string]string -} - -func (conn FooDaxConn) Commit() sabi.Err { - if WillFailToCommitFooDaxConn { - return sabi.NewErr(FailToDoSomething{Text: "FailToCommitFooDaxConn"}) - } - Logs.PushBack("FooDaxConn#Commit") - return sabi.Ok() -} - -func (conn FooDaxConn) Rollback() { - Logs.PushBack("FooDaxConn#Rollback") -} - -func (conn FooDaxConn) Close() { - Logs.PushBack("FooDaxConn#Close") -} - -// FooDaxSrc - -type FooDaxSrc struct { - Label string -} - -func (ds FooDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - if WillFailToCreateFooDaxConn { - return nil, sabi.NewErr(FailToDoSomething{Text: "FailToCreateFooDaxConn"}) - } - Logs.PushBack("FooDaxSrc#CreateDaxConn") - return FooDaxConn{Label: ds.Label, Map: make(map[string]string)}, sabi.Ok() -} - -func (ds FooDaxSrc) SetUp() sabi.Err { - if WillFailToSetUpFooDaxSrc { - return sabi.NewErr(FailToDoSomething{Text: "FailToSetUpFooDaxSrc"}) - } - Logs.PushBack("FooDaxSrc#SetUp") - return sabi.Ok() -} - -func (ds FooDaxSrc) End() { - Logs.PushBack("FooDaxSrc#End") -} - -// BarDaxConn - -type BarDaxConn struct { - Label string - Map map[string]string -} - -func (conn *BarDaxConn) Commit() sabi.Err { - Logs.PushBack("BarDaxConn#Commit") - return sabi.Ok() -} - -func (conn *BarDaxConn) Rollback() { - Logs.PushBack("BarDaxConn#Rollback") -} - -func (conn *BarDaxConn) Close() { - Logs.PushBack("BarDaxConn#Close") -} - -// BarDaxSrc - -type BarDaxSrc struct { - Label string -} - -func (ds BarDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - Logs.PushBack("BarDaxSrc#CreateDaxConn") - return &BarDaxConn{Label: ds.Label, Map: make(map[string]string)}, sabi.Ok() -} - -func (ds BarDaxSrc) SetUp() sabi.Err { - Logs.PushBack("BarDaxSrc#SetUp") - return sabi.Ok() -} - -func (ds BarDaxSrc) End() { - Logs.PushBack("BarDaxSrc#End") -} - -// MapDaxSrc - -type MapDaxSrc struct { - dataMap map[string]string -} - -func NewMapDaxSrc() MapDaxSrc { - return MapDaxSrc{dataMap: make(map[string]string)} -} - -func (ds MapDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - return MapDaxConn{dataMap: ds.dataMap}, sabi.Ok() -} - -func (ds MapDaxSrc) SetUp() sabi.Err { - return sabi.Ok() -} - -func (ds MapDaxSrc) End() { -} - -// MapDaxConn - -type MapDaxConn struct { - dataMap map[string]string -} - -func (conn MapDaxConn) Commit() sabi.Err { - return sabi.Ok() -} - -func (conn MapDaxConn) Rollback() { -} - -func (conn MapDaxConn) Close() { -} - -// HogeFugaDax - -type HogeFugaDax interface { - GetHogeData() (string, sabi.Err) - SetFugaData(data string) sabi.Err -} - -// HogeFugaLogic - -func HogeFugaLogic(dax HogeFugaDax) sabi.Err { - data, err := dax.GetHogeData() - if err.IsNotOk() { - return err - } - err = dax.SetFugaData(data) - return err -} - -// FugaPiyoDax - -type FugaPiyoDax interface { - GetFugaData() (string, sabi.Err) - SetPiyoData(data string) sabi.Err -} - -// FugaPiyoLogic - -func FugaPiyoLogic(dax FugaPiyoDax) sabi.Err { - data, err := dax.GetFugaData() - if err.IsNotOk() { - return err - } - err = dax.SetPiyoData(data) - return err -} - -// HogeDax - -type HogeDax struct { - sabi.Dax -} - -func (dax HogeDax) GetHogeData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - if err.IsNotOk() { - return "", err - } - data := conn.dataMap["hogehoge"] - return data, err -} - -func (dax HogeDax) SetHogeData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - if err.IsNotOk() { - return err - } - conn.dataMap["hogehoge"] = data - return err -} - -// FugaDax - -type FugaDax struct { - sabi.Dax -} - -func (dax FugaDax) GetFugaData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "fuga") - if err.IsNotOk() { - return "", err - } - data := conn.dataMap["fugafuga"] - return data, err -} - -func (dax FugaDax) SetFugaData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "fuga") - if err.IsNotOk() { - return err - } - conn.dataMap["fugafuga"] = data - return err -} - -// PiyoDax - -type PiyoDax struct { - sabi.Dax -} - -func (dax PiyoDax) GetPiyoData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "piyo") - if err.IsNotOk() { - return "", err - } - data := conn.dataMap["piyopiyo"] - return data, err -} - -func (dax PiyoDax) SetPiyoData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "piyo") - if err.IsNotOk() { - return err - } - conn.dataMap["piyopiyo"] = data - return err -} - -// HogeFugaPiyoDaxBase - -func NewHogeFugaPiyoDaxBase() sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - HogeDax - FugaDax - PiyoDax - }{ - DaxBase: base, - HogeDax: HogeDax{Dax: base}, - FugaDax: FugaDax{Dax: base}, - PiyoDax: PiyoDax{Dax: base}, - } -} - -// ADaxSrc - -type ADaxSrc struct { - AMap map[string]string -} - -func NewADaxSrc() ADaxSrc { - return ADaxSrc{AMap: make(map[string]string)} -} - -func (ds ADaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - return ADaxConn{AMap: ds.AMap}, sabi.Ok() -} - -func (ds ADaxSrc) SetUp() sabi.Err { - return sabi.Ok() -} - -func (ds ADaxSrc) End() { -} - -// ADaxConn - -type ADaxConn struct { - AMap map[string]string -} - -func (conn ADaxConn) Commit() sabi.Err { - Logs.PushBack("ADaxConn#Commit") - return sabi.Ok() -} - -func (conn ADaxConn) Rollback() { - Logs.PushBack("ADaxConn#Rollback") -} - -func (conn ADaxConn) Close() { - Logs.PushBack("ADaxConn#Close") -} - -// BDaxSrc - -type BDaxSrc struct { - BMap map[string]string -} - -func NewBDaxSrc() BDaxSrc { - return BDaxSrc{BMap: make(map[string]string)} -} - -func (ds BDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - if WillFailToCreateBDaxConn { - return nil, sabi.NewErr(FailToCreateBDaxConn{}) - } - return BDaxConn{BMap: ds.BMap}, sabi.Ok() -} - -func (ds BDaxSrc) SetUp() sabi.Err { - return sabi.Ok() -} - -func (ds BDaxSrc) End() { -} - -// BDaxConn - -type BDaxConn struct { - BMap map[string]string -} - -func (conn BDaxConn) Commit() sabi.Err { - if WillFailToCommitBDaxConn { - return sabi.NewErr(FailToCommitBDaxConn{}) - } - Logs.PushBack("BDaxConn#Commit") - return sabi.Ok() -} - -func (conn BDaxConn) Rollback() { - Logs.PushBack("BDaxConn#Rollback") -} - -func (conn BDaxConn) Close() { - Logs.PushBack("BDaxConn#Close") -} - -// CDaxSrc - -type CDaxSrc struct { - CMap map[string]string -} - -func NewCDaxSrc() CDaxSrc { - return CDaxSrc{CMap: make(map[string]string)} -} - -func (ds CDaxSrc) CreateDaxConn() (sabi.DaxConn, sabi.Err) { - return CDaxConn{CMap: ds.CMap}, sabi.Ok() -} - -func (ds CDaxSrc) SetUp() sabi.Err { - return sabi.Ok() -} - -func (ds CDaxSrc) End() { -} - -// CDaxConn - -type CDaxConn struct { - CMap map[string]string -} - -func (conn CDaxConn) Commit() sabi.Err { - Logs.PushBack("CDaxConn#Commit") - return sabi.Ok() -} - -func (conn CDaxConn) Rollback() { - Logs.PushBack("CDaxConn#Rollback") -} - -func (conn CDaxConn) Close() { - Logs.PushBack("CDaxConn#Close") -} - -// AGetDax - -type AGetDax struct { - sabi.Dax -} - -func (dax AGetDax) GetAData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[ADaxConn](dax, "aaa") - if !err.IsOk() { - return "", err - } - data := conn.AMap["a"] - return data, sabi.Ok() -} - -// BGetSetDax - -type BGetSetDax struct { - sabi.Dax -} - -func (dax BGetSetDax) GetBData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[BDaxConn](dax, "bbb") - if !err.IsOk() { - return "", err - } - data := conn.BMap["b"] - return data, sabi.Ok() -} - -func (dax BGetSetDax) SetBData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[BDaxConn](dax, "bbb") - if !err.IsOk() { - return err - } - conn.BMap["b"] = data - return sabi.Ok() -} - -// CSetDax - -type CSetDax struct { - sabi.Dax -} - -func (dax CSetDax) SetCData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[CDaxConn](dax, "ccc") - if !err.IsOk() { - return err - } - conn.CMap["c"] = data - return sabi.Ok() -} diff --git a/dax_test.go b/dax_test.go index 8a3c01c..1e90950 100644 --- a/dax_test.go +++ b/dax_test.go @@ -1,882 +1,1655 @@ -package sabi_test +package sabi import ( - "fmt" + "container/list" "testing" "github.com/stretchr/testify/assert" - "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) -func TestAddGlobalDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +var ( + Logs list.List + WillFailToSetupFooDaxSrc bool + WillFailToSetupBarDaxSrc bool + WillFailToCreateFooDaxConn bool + WillCreatedFooDaxConnBeNil bool + WillFailToCommitFooDaxConn bool + WillFailToCommitBarDaxConn bool +) - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 0) +func Reset() { + errs.FixCfg() - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) + isGlobalDaxSrcsFixed = false + globalDaxSrcEntryList.head = nil + globalDaxSrcEntryList.last = nil - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) + WillFailToSetupFooDaxSrc = false + WillFailToSetupBarDaxSrc = false - sabi.AddGlobalDaxSrc("bar", &BarDaxSrc{}) + WillFailToCreateFooDaxConn = false + WillCreatedFooDaxConnBeNil = false - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 2) -} + WillFailToCommitFooDaxConn = false + WillFailToCommitBarDaxConn = false -func TestStartUpGlobalDaxSrcs(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() + Logs.Init() +} - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 0) +type ( + FailToSetupFooDaxSrc struct{} + FailToSetupBarDaxSrc struct{} - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) + FailToCreateFooDaxConn struct{} + FailToCommitFooDaxConn struct{} + FailToCommitBarDaxConn struct{} +) - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) +/// - err := sabi.StartUpGlobalDaxSrcs() - assert.True(t, err.IsOk()) +type FooDaxSrc struct{} - assert.True(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) +func (ds FooDaxSrc) Setup(ag AsyncGroup) errs.Err { + if WillFailToSetupFooDaxSrc { + return errs.New(FailToSetupFooDaxSrc{}) + } + Logs.PushBack("FooDaxSrc#Setup") + return errs.Ok() +} - sabi.AddGlobalDaxSrc("bar", &BarDaxSrc{}) +func (ds FooDaxSrc) Close() { + Logs.PushBack("FooDaxSrc#Close") +} - assert.True(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) +func (ds FooDaxSrc) CreateDaxConn() (DaxConn, errs.Err) { + if WillFailToCreateFooDaxConn { + return nil, errs.New(FailToCreateFooDaxConn{}) + } + if WillCreatedFooDaxConnBeNil { + return nil, errs.Ok() + } + Logs.PushBack("FooDaxSrc#CreateDaxConn") + return FooDaxConn{client: &FooClient{}}, errs.Ok() +} - log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") - assert.Nil(t, log.Next()) +type FooClient struct { + committed bool +} +type FooDaxConn struct { + client *FooClient } -func TestStartUpGlobalDaxSrcs_failToSetUpDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func (conn FooDaxConn) Commit(ag AsyncGroup) errs.Err { + if WillFailToCommitFooDaxConn { + return errs.New(FailToCommitFooDaxConn{}) + } + Logs.PushBack("FooDaxConn#Commit") + conn.client.committed = true + return errs.Ok() +} - WillFailToSetUpFooDaxSrc = true +func (conn FooDaxConn) IsCommitted() bool { + return conn.client.committed +} - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 0) +func (conn FooDaxConn) Rollback(ag AsyncGroup) { + Logs.PushBack("FooDaxConn#Rollback") +} - sabi.AddGlobalDaxSrc("bar", &BarDaxSrc{}) +func (conn FooDaxConn) ForceBack(ag AsyncGroup) { + Logs.PushBack("FooDaxConn#ForceBack") +} - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) +func (conn FooDaxConn) Close() { + Logs.PushBack("FooDaxConn#Close") +} - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) +type BarDaxSrc struct{} - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 2) +func (ds *BarDaxSrc) Setup(ag AsyncGroup) errs.Err { + ag.Add(func() errs.Err { + if WillFailToSetupBarDaxSrc { + return errs.New(FailToSetupBarDaxSrc{}) + } + Logs.PushBack("BarDaxSrc#Setup") + return errs.Ok() + }) + return errs.Ok() +} +func (ds *BarDaxSrc) Close() { + Logs.PushBack("BarDaxSrc#Close") +} +func (ds *BarDaxSrc) CreateDaxConn() (DaxConn, errs.Err) { + Logs.PushBack("BarDaxSrc#CreateDaxConn") + return &BarDaxConn{}, errs.Ok() +} - err := sabi.StartUpGlobalDaxSrcs() - assert.False(t, err.IsOk()) - switch err.Reason().(type) { - case sabi.FailToStartUpGlobalDaxSrcs: - errs := err.Reason().(sabi.FailToStartUpGlobalDaxSrcs).Errors - assert.Equal(t, len(errs), 1) - err1 := errs["foo"] - r := err1.Reason().(FailToDoSomething) - assert.Equal(t, r.Text, "FailToSetUpFooDaxSrc") - default: - assert.Fail(t, err.Error()) - } +type BarDaxConn struct { + committed bool +} - log := Logs.Front() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") - log = log.Next() - if log.Value == "FooDaxSrc#End" { - assert.Equal(t, log.Value, "FooDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#End") - } else { - assert.Equal(t, log.Value, "BarDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#End") - } - assert.Nil(t, log.Next()) +func (conn *BarDaxConn) Commit(ag AsyncGroup) errs.Err { + ag.Add(func() errs.Err { + if WillFailToCommitBarDaxConn { + return errs.New(FailToCommitBarDaxConn{}) + } + Logs.PushBack("BarDaxConn#Commit") + conn.committed = true + return errs.Ok() + }) + return errs.Ok() +} +func (conn *BarDaxConn) IsCommitted() bool { + return conn.committed +} +func (conn *BarDaxConn) Rollback(ag AsyncGroup) { + Logs.PushBack("BarDaxConn#Rollback") +} +func (conn *BarDaxConn) ForceBack(ag AsyncGroup) { + Logs.PushBack("BarDaxConn#ForceBack") +} +func (conn *BarDaxConn) Close() { + Logs.PushBack("BarDaxConn#Close") } -func TestShutdownGlobalDaxSrcs(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +/// + +func TestUses_ok(t *testing.T) { + Reset() + defer Reset() + + Uses("cliargs", FooDaxSrc{}) + + ent0 := globalDaxSrcEntryList.head + assert.Equal(t, ent0.name, "cliargs") + assert.IsType(t, ent0.ds, FooDaxSrc{}) + assert.False(t, ent0.local) + assert.Nil(t, ent0.prev) + assert.Nil(t, ent0.next) + + Uses("database", &FooDaxSrc{}) + + ent0 = globalDaxSrcEntryList.head + assert.Equal(t, ent0.name, "cliargs") + assert.IsType(t, ent0.ds, FooDaxSrc{}) + assert.False(t, ent0.local) + assert.Nil(t, ent0.prev) + + ent1 := ent0.next + assert.Equal(t, ent1.name, "database") + assert.IsType(t, ent1.ds, &FooDaxSrc{}) + assert.False(t, ent1.local) + assert.Equal(t, ent1.prev, ent0) + assert.Nil(t, ent1.next) + + Uses("file", &BarDaxSrc{}) + + ent0 = globalDaxSrcEntryList.head + assert.Equal(t, ent0.name, "cliargs") + assert.IsType(t, ent0.ds, FooDaxSrc{}) + assert.False(t, ent0.local) + assert.Nil(t, ent0.prev) + + ent1 = ent0.next + assert.Equal(t, ent1.name, "database") + assert.IsType(t, ent1.ds, &FooDaxSrc{}) + assert.False(t, ent1.local) + assert.Equal(t, ent1.prev, ent0) + + ent2 := ent1.next + assert.Equal(t, ent2.name, "file") + assert.IsType(t, ent2.ds, &BarDaxSrc{}) + assert.False(t, ent2.local) + assert.Equal(t, ent2.prev, ent1) +} - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 0) +func TestUses_nameAlreadyExists(t *testing.T) { + Reset() + defer Reset() + + Uses("database", FooDaxSrc{}) + + ent0 := globalDaxSrcEntryList.head + assert.Equal(t, ent0.name, "database") + assert.IsType(t, ent0.ds, FooDaxSrc{}) + assert.False(t, ent0.local) + assert.Nil(t, ent0.prev) + assert.Nil(t, ent0.next) + + Uses("database", &FooDaxSrc{}) + + ent0 = globalDaxSrcEntryList.head + assert.Equal(t, ent0.name, "database") + assert.IsType(t, ent0.ds, FooDaxSrc{}) + assert.False(t, ent0.local) + assert.Nil(t, ent0.prev) + + ent1 := ent0.next + assert.Equal(t, ent1.name, "database") + assert.IsType(t, ent1.ds, &FooDaxSrc{}) + assert.False(t, ent1.local) + assert.Equal(t, ent1.prev, ent0) + assert.Nil(t, ent1.next) +} - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) +func TestSetup_zeroDs(t *testing.T) { + Reset() + defer Reset() - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 1) + assert.False(t, isGlobalDaxSrcsFixed) + assert.Nil(t, Logs.Front()) - sabi.AddGlobalDaxSrc("bar", &BarDaxSrc{}) + err := Setup() + assert.True(t, err.IsOk()) + defer Close() - assert.False(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 2) + assert.True(t, isGlobalDaxSrcsFixed) + assert.Nil(t, Logs.Front()) +} - err := sabi.StartUpGlobalDaxSrcs() - assert.True(t, err.IsOk()) +func TestSetup_oneDs(t *testing.T) { + Reset() + defer Reset() - assert.True(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 2) + Uses("cliargs", FooDaxSrc{}) - sabi.ShutdownGlobalDaxSrcs() + assert.False(t, isGlobalDaxSrcsFixed) + assert.Nil(t, Logs.Front()) - assert.True(t, sabi.IsGlobalDaxSrcsFixed()) - assert.Equal(t, len(sabi.GlobalDaxSrcMap()), 2) + err := Setup() + assert.True(t, err.IsOk()) + defer Close() + assert.True(t, isGlobalDaxSrcsFixed) log := Logs.Front() - if log.Value == "FooDaxSrc#SetUp" { - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") - } else { - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") - } + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - if log.Value == "FooDaxSrc#End" { - assert.Equal(t, log.Value, "FooDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#End") - } else { - assert.Equal(t, log.Value, "BarDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#End") - } - assert.Nil(t, log.Next()) + assert.Nil(t, log) } -func TestDaxBase_SetUpLocalDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestSetup_multipleDs(t *testing.T) { + Reset() + defer Reset() + + Uses("cliargs", FooDaxSrc{}) + Uses("database", &BarDaxSrc{}) - base := sabi.NewDaxBase() + assert.False(t, isGlobalDaxSrcsFixed) + assert.Nil(t, Logs.Front()) + + err := Setup() + assert.True(t, err.IsOk()) + defer Close() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.True(t, isGlobalDaxSrcsFixed) + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Nil(t, log) +} - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) +func TestSetup_cannotAddAfterSetup(t *testing.T) { + Reset() + defer Reset() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + Uses("cliargs", FooDaxSrc{}) - base.SetUpLocalDaxSrc("bar", &BarDaxSrc{}) + assert.False(t, isGlobalDaxSrcsFixed) + assert.Nil(t, Logs.Front()) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 2) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + err := Setup() + assert.True(t, err.IsOk()) + assert.True(t, isGlobalDaxSrcsFixed) log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Nil(t, log) + + ent := globalDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.Nil(t, ent.next) + + Uses("database", &FooDaxSrc{}) + + assert.True(t, isGlobalDaxSrcsFixed) + log = Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() assert.Nil(t, log) + + ent = globalDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.Nil(t, ent.next) } -func TestDaxBase_SetUpLocalDaxSrc_unableToAddLocalDaxSrcInTxn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestSetup_error_sync(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + WillFailToSetupFooDaxSrc = true - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + Uses("cliargs", FooDaxSrc{}) - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + err := Setup() + assert.True(t, err.IsNotOk()) + assert.IsType(t, err.Reason(), FailToSetupGlobalDaxSrcs{}) + errmap := err.Reason().(FailToSetupGlobalDaxSrcs).Errors + assert.Equal(t, len(errmap), 1) + assert.IsType(t, errmap["cliargs"].Reason(), FailToSetupFooDaxSrc{}) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.True(t, isGlobalDaxSrcsFixed) - sabi.Begin(base) + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} - assert.True(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) +func TestSetup_error_async(t *testing.T) { + Reset() + defer Reset() - base.SetUpLocalDaxSrc("bar", &BarDaxSrc{}) + WillFailToSetupBarDaxSrc = true - assert.True(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + Uses("cliargs", &BarDaxSrc{}) + err := Setup() + assert.True(t, err.IsNotOk()) + assert.IsType(t, err.Reason(), FailToSetupGlobalDaxSrcs{}) + errmap := err.Reason().(FailToSetupGlobalDaxSrcs).Errors + assert.Equal(t, len(errmap), 1) + assert.IsType(t, errmap["cliargs"].Reason(), FailToSetupBarDaxSrc{}) + + assert.True(t, isGlobalDaxSrcsFixed) log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "BarDaxSrc#Close") log = log.Next() assert.Nil(t, log) +} - sabi.End(base) +func TestSetup_error_asyncAndSync(t *testing.T) { + Reset() + defer Reset() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + WillFailToSetupBarDaxSrc = true + WillFailToSetupFooDaxSrc = true - base.SetUpLocalDaxSrc("bar", &BarDaxSrc{}) + Uses("cliargs", &BarDaxSrc{}) + Uses("database", FooDaxSrc{}) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 2) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + err := Setup() + assert.True(t, err.IsNotOk()) + assert.IsType(t, err.Reason(), FailToSetupGlobalDaxSrcs{}) + errmap := err.Reason().(FailToSetupGlobalDaxSrcs).Errors + assert.Equal(t, len(errmap), 2) + assert.IsType(t, errmap["cliargs"].Reason(), FailToSetupBarDaxSrc{}) + assert.IsType(t, errmap["database"].Reason(), FailToSetupFooDaxSrc{}) - log = Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.True(t, isGlobalDaxSrcsFixed) + log := Logs.Front() + assert.Equal(t, log.Value, "BarDaxSrc#Close") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Close") log = log.Next() assert.Nil(t, log) } -func TestDaxBase_SetUpLocalDaxSrc_failToSetUpDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestStartApp_ok(t *testing.T) { + Reset() + defer Reset() - WillFailToSetUpFooDaxSrc = true + Uses("database", FooDaxSrc{}) - base := sabi.NewDaxBase() - - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + app := func() errs.Err { + Logs.PushBack("run app") + return errs.Ok() + } - err := base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) + err := StartApp(app) assert.True(t, err.IsOk()) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "run app") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} - err = base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - assert.False(t, err.IsOk()) +func TestStartApp_error_failToSetup(t *testing.T) { + Reset() + defer Reset() - switch err.Reason().(type) { - case FailToDoSomething: - r := err.Reason().(FailToDoSomething) - assert.Equal(t, r.Text, "FailToSetUpFooDaxSrc") - default: - assert.Fail(t, err.Error()) - } + WillFailToSetupFooDaxSrc = true - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + Uses("database", FooDaxSrc{}) - base.FreeAllLocalDaxSrcs() + app := func() errs.Err { + Logs.PushBack("run app") + return errs.Ok() + } - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + err := StartApp(app) + assert.True(t, err.IsNotOk()) + assert.IsType(t, err.Reason(), FailToSetupGlobalDaxSrcs{}) + errmap := err.Reason().(FailToSetupGlobalDaxSrcs).Errors + assert.Equal(t, len(errmap), 1) + assert.IsType(t, errmap["database"].Reason(), FailToSetupFooDaxSrc{}) log := Logs.Front() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Close") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#End") - assert.Nil(t, log.Next()) + assert.Nil(t, log) } -func TestDaxBase_FreeLocalDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestStartApp_error_appReturnsError(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + Uses("database", FooDaxSrc{}) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + type FailToDoSomething struct{} - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + app := func() errs.Err { + return errs.New(FailToDoSomething{}) + } - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + err := StartApp(app) + assert.True(t, err.IsNotOk()) + assert.IsType(t, err.Reason(), FailToDoSomething{}) - base.FreeLocalDaxSrc("foo") + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) +func TestNewDaxBase_withNoGlobalDs(t *testing.T) { + Reset() + defer Reset() - base.SetUpLocalDaxSrc("bar", &BarDaxSrc{}) + assert.False(t, isGlobalDaxSrcsFixed) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + assert.True(t, isGlobalDaxSrcsFixed) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 2) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) - base.FreeLocalDaxSrc("bar") + log := Logs.Front() + assert.Nil(t, log) +} + +func TestNewDaxBase_withSomeGlobalDs(t *testing.T) { + Reset() + defer Reset() + + assert.False(t, isGlobalDaxSrcsFixed) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + Uses("cliargs", FooDaxSrc{}) + Uses("database", &BarDaxSrc{}) - base.FreeLocalDaxSrc("foo") + func() { + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.True(t, isGlobalDaxSrcsFixed) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.IsType(t, base.daxSrcEntryMap["cliargs"].ds, FooDaxSrc{}) + assert.IsType(t, base.daxSrcEntryMap["database"].ds, &BarDaxSrc{}) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Nil(t, log) +} + +func TestDax_Uses_ok(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 1) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + ent = ent.next + assert.Nil(t, ent) + + base.Close() + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() + + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "BarDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#End") + assert.Equal(t, log.Value, "FooDaxSrc#Close") log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#End") + assert.Equal(t, log.Value, "BarDaxSrc#Close") log = log.Next() assert.Nil(t, log) } -func TestDaxBase_FreeLocalDaxSrc_unableToFreeLocalDaxSrcInTxn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestDax_Uses_doNothingWhileFixed(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + func() { + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + base.begin() - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) - sabi.Begin(base) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) - assert.True(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + base.end() + }() - base.FreeLocalDaxSrc("foo") + log := Logs.Front() + assert.Nil(t, log) +} + +func TestDax_Uses_failToSetupLocalDaxSrc_sync(t *testing.T) { + Reset() + defer Reset() - assert.True(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + func() { + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - sabi.End(base) + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 1) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + WillFailToSetupFooDaxSrc = true - base.FreeLocalDaxSrc("foo") + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsNotOk()) + switch r := err.Reason().(type) { + case FailToSetupLocalDaxSrc: + assert.Equal(t, r.Name, "cliargs") + default: + assert.Fail(t, err.Error()) + } - assert.False(t, sabi.IsLocalDaxSrcsFixed(base)) - assert.Equal(t, len(sabi.LocalDaxSrcMap(base)), 0) - assert.Equal(t, len(sabi.DaxConnMap(base)), 0) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + ent := base.localDaxSrcEntryList.head + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() + + log := Logs.Front() + assert.Nil(t, log) } -func TestDaxBase_GetDaxConn_withLocalDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestDax_Uses_failToSetupLocalDaxSrc_async(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + func() { + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - switch err.Reason().(type) { - case sabi.DaxSrcIsNotFound: - assert.Equal(t, err.Get("Name"), "foo") - default: - assert.Fail(t, err.Error()) - } + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) - err = sabi.StartUpGlobalDaxSrcs() + WillFailToSetupBarDaxSrc = true - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - switch err.Reason().(type) { - case sabi.DaxSrcIsNotFound: - assert.Equal(t, err.Get("Name"), "foo") - default: - assert.Fail(t, err.Error()) - } + err := base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsNotOk()) + switch r := err.Reason().(type) { + case FailToSetupLocalDaxSrc: + assert.Equal(t, r.Name, "database") + default: + assert.Fail(t, err.Error()) + } + + assert.Equal(t, len(base.daxSrcEntryMap), 0) + ent := base.localDaxSrcEntryList.head + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() + + log := Logs.Front() + assert.Nil(t, log) +} + +func TestDax_Uses_createRunner(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + runner := base.Uses_("cliargs", FooDaxSrc{}) + err := runner() + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 1) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + + runner = base.Uses_("database", &BarDaxSrc{}) + err = runner() + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + ent = ent.next + assert.Nil(t, ent) + + base.Close() + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() + + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} - err = base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) +func TestDax_Disuses_ok(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) + + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + err = base.Uses("file", &FooDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + base.Disuses("database") + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + base.Disuses("cliargs") + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + base.Disuses("file") + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.True(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} + +func TestDax_Disuses_doNothingWhileFixed(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 1) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + base.begin() + assert.True(t, base.isLocalDaxSrcsFixed) + + base.Disuses("cliargs") + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + base.Disuses("database") + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + }() + + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Nil(t, log) +} + +func TestDax_Disuses_createRunner(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) + + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + err = base.Uses("file", &FooDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + runner := base.Disuses_("database") + err = runner() + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + runner = base.Disuses_("cliargs") + err = runner() + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.False(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, &FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + runner = base.Disuses_("file") + err = runner() + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 3) + assert.True(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.True(t, base.daxSrcEntryMap["database"].deleted) + assert.True(t, base.daxSrcEntryMap["file"].deleted) + ent = base.localDaxSrcEntryList.head + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, base.daxConnMap.Len(), 0) + }() + + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} + +func TestDax_Close_doNothingWhileFixed(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase().(*daxBaseImpl) + + assert.False(t, base.isLocalDaxSrcsFixed) + assert.Nil(t, base.localDaxSrcEntryList.head) + assert.Nil(t, base.localDaxSrcEntryList.last) + assert.Equal(t, len(base.daxSrcEntryMap), 0) + assert.Equal(t, base.daxConnMap.Len(), 0) + + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 1) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + ent := base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + + assert.False(t, base.isLocalDaxSrcsFixed) + base.begin() + assert.True(t, base.isLocalDaxSrcsFixed) + + base.Close() + + assert.Equal(t, len(base.daxSrcEntryMap), 2) + assert.False(t, base.daxSrcEntryMap["cliargs"].deleted) + assert.False(t, base.daxSrcEntryMap["database"].deleted) + ent = base.localDaxSrcEntryList.head + assert.IsType(t, ent.ds, FooDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.IsType(t, ent.ds, &BarDaxSrc{}) + assert.False(t, ent.deleted) + ent = ent.next + assert.Nil(t, ent) + }() + + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Nil(t, log) +} + +func TestGetDaxConn_ok(t *testing.T) { + Reset() + defer Reset() + + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() + + err := base.Uses("cliargs", FooDaxSrc{}) assert.True(t, err.IsOk()) + err = base.Uses("database", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + err = base.Uses("file", &FooDaxSrc{}) + assert.True(t, err.IsOk()) + + base.begin() + defer base.end() - var conn2 sabi.DaxConn - conn2, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.Equal(t, conn2, conn) + conn1, err := GetDaxConn[FooDaxConn](base, "cliargs") assert.True(t, err.IsOk()) + assert.IsType(t, conn1, FooDaxConn{}) + + conn2, err := GetDaxConn[*BarDaxConn](base, "database") + assert.True(t, err.IsOk()) + assert.IsType(t, conn2, &BarDaxConn{}) + + conn3, err := GetDaxConn[FooDaxConn](base, "file") + assert.True(t, err.IsOk()) + assert.IsType(t, conn3, FooDaxConn{}) } -func TestDaxBase_getDaxConn_withGlobalDaxSrc(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestGetDaxConn_daxConnIsAlreadyCreated(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - switch err.Reason().(type) { - case sabi.DaxSrcIsNotFound: - assert.Equal(t, err.Get("Name"), "foo") - default: - assert.Fail(t, err.Error()) - } + err := base.Uses("cliargs", FooDaxSrc{}) + assert.True(t, err.IsOk()) - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{}) + base.begin() + defer base.end() - err = sabi.StartUpGlobalDaxSrcs() - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) + conn1, err := GetDaxConn[FooDaxConn](base, "cliargs") assert.True(t, err.IsOk()) + assert.IsType(t, conn1, FooDaxConn{}) - var conn2 sabi.DaxConn - conn2, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.Equal(t, conn2, conn) + conn2, err := GetDaxConn[FooDaxConn](base, "cliargs") assert.True(t, err.IsOk()) + assert.IsType(t, conn2, FooDaxConn{}) + assert.Equal(t, &conn1, &conn2) } -func TestDaxBase_getDaxConn_localDsIsTakenPriorityOfGlobalDs(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestGetDaxConn_daxSrcIsNotFound(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - switch err.Reason().(type) { - case sabi.DaxSrcIsNotFound: - assert.Equal(t, err.Get("Name"), "foo") + base.begin() + defer base.end() + + _, err := GetDaxConn[FooDaxConn](base, "cliargs") + switch r := err.Reason().(type) { + case DaxSrcIsNotFound: + assert.Equal(t, r.Name, "cliargs") default: assert.Fail(t, err.Error()) } +} - sabi.AddGlobalDaxSrc("foo", FooDaxSrc{Label: "global"}) +func TestGetDaxConn_daxSrcIsDisused(t *testing.T) { + Reset() + defer Reset() - err = sabi.StartUpGlobalDaxSrcs() - assert.True(t, err.IsOk()) + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - err = base.SetUpLocalDaxSrc("foo", FooDaxSrc{Label: "local"}) + err := base.Uses("cliargs", FooDaxSrc{}) assert.True(t, err.IsOk()) - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.Equal(t, conn.Label, "local") + func() { + base.begin() + defer base.end() + + conn, err := GetDaxConn[FooDaxConn](base, "cliargs") + assert.True(t, err.IsOk()) + assert.IsType(t, conn, FooDaxConn{}) + }() + + base.Disuses("cliargs") + + func() { + base.begin() + defer base.end() + + _, err := GetDaxConn[FooDaxConn](base, "cliargs") + switch r := err.Reason().(type) { + case DaxSrcIsNotFound: + assert.Equal(t, r.Name, "cliargs") + default: + assert.Fail(t, err.Error()) + } + }() +} + +func TestGetDaxConn_localDaxSrcIsDisusedButGlobalDaxSrcExists(t *testing.T) { + Reset() + defer Reset() + + Uses("database", &BarDaxSrc{}) + + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) assert.True(t, err.IsOk()) + + func() { + base.begin() + defer base.end() + + conn, err := GetDaxConn[FooDaxConn](base, "database") + assert.True(t, err.IsOk()) + assert.IsType(t, conn, FooDaxConn{}) + }() + + base.Disuses("database") + + func() { + base.begin() + defer base.end() + + conn, err := GetDaxConn[*BarDaxConn](base, "database") + assert.True(t, err.IsOk()) + assert.IsType(t, conn, &BarDaxConn{}) + }() } -func TestDaxBase_getDaxConn_failToCreateDaxConn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestGetDaxConn_failToCreateDaxConn(t *testing.T) { + Reset() + defer Reset() - WillFailToCreateFooDaxConn = true + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) - base := sabi.NewDaxBase() + WillFailToCreateFooDaxConn = true - err := base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + func() { + base.begin() + defer base.end() - _, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - switch err.Reason().(type) { - case sabi.FailToCreateDaxConn: - assert.Equal(t, err.Get("Name"), "foo") - switch err.Cause().(sabi.Err).Reason().(type) { - case FailToDoSomething: + _, err := GetDaxConn[FooDaxConn](base, "database") + switch r := err.Reason().(type) { + case FailToCreateDaxConn: + assert.Equal(t, r.Name, "database") + assert.IsType(t, err.Cause().(errs.Err).Reason(), FailToCreateFooDaxConn{}) default: assert.Fail(t, err.Error()) } - default: - assert.Fail(t, err.Error()) - } + }() } -func TestDaxBase_getDaxConn_commit(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestGetDaxConn_createdDaxConnIsNil(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() - err := base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + err := base.Uses("database", FooDaxSrc{}) assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - assert.True(t, err.IsOk()) + WillCreatedFooDaxConnBeNil = true - sabi.Begin(base) + func() { + base.begin() + defer base.end() - var conn sabi.DaxConn - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) + _, err := GetDaxConn[FooDaxConn](base, "database") + switch r := err.Reason().(type) { + case CreatedDaxConnIsNil: + assert.Equal(t, r.Name, "database") + default: + assert.Fail(t, err.Error()) + } + }() +} - conn, err = sabi.GetDaxConn[*BarDaxConn](base, "bar") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) +func TestGetDaxConn_failToCastDaxConn(t *testing.T) { + Reset() + defer Reset() - err = sabi.Commit(base) + base := NewDaxBase().(*daxBaseImpl) + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) assert.True(t, err.IsOk()) - log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") - log = log.Next() - if log.Value == "FooDaxConn#Commit" { - assert.Equal(t, log.Value, "FooDaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxConn#Commit") - } else { - assert.Equal(t, log.Value, "BarDaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxConn#Commit") - } - log = log.Next() - assert.Nil(t, log) -} + func() { + base.begin() + defer base.end() -func TestDaxBase_getDaxConn_failToCommit(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() + _, err := GetDaxConn[*BarDaxConn](base, "database") + switch r := err.Reason().(type) { + case FailToCastDaxConn: + assert.Equal(t, r.Name, "database") + assert.Equal(t, r.FromType, "sabi.FooDaxConn") + assert.Equal(t, r.ToType, "*sabi.BarDaxConn") + default: + assert.Fail(t, err.Error()) + } + }() +} - WillFailToCommitFooDaxConn = true +func TestTxn_zeroLogic(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() + base := NewDaxBase() + defer base.Close() - err := base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) + err := Txn[Dax](base) assert.True(t, err.IsOk()) +} - err = base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - assert.True(t, err.IsOk()) +func TestTxn_oneLogic(t *testing.T) { + Reset() + defer Reset() - sabi.Begin(base) + func() { + base := NewDaxBase() + defer base.Close() - var conn sabi.DaxConn - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) - conn, err = sabi.GetDaxConn[*BarDaxConn](base, "bar") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) + err = Txn(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + return errs.Ok() + }) + assert.True(t, err.IsOk()) + }() - err = sabi.Commit(base) - assert.False(t, err.IsOk()) - switch err.Reason().(type) { - case sabi.FailToCommitDaxConn: - m := err.Get("Errors").(map[string]sabi.Err) - assert.Equal(t, m["foo"].ReasonName(), "FailToDoSomething") - default: - assert.Fail(t, err.Error()) - } + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Commit") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) +} + +func TestTxn_twoLogic(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase() + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) + err = err.IfOk(base.Uses_("file", &BarDaxSrc{})) + assert.True(t, err.IsOk()) + + err = Txn(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + return errs.Ok() + }, func(dax Dax) errs.Err { + _, err := GetDaxConn[*BarDaxConn](dax, "file") + assert.True(t, err.IsOk()) + return errs.Ok() + }) + assert.True(t, err.IsOk()) + }() log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Equal(t, log.Value, "BarDaxSrc#Setup") log = log.Next() assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") log = log.Next() assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Commit") + log = log.Next() assert.Equal(t, log.Value, "BarDaxConn#Commit") log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() assert.Nil(t, log) } -func TestDaxBase_getDaxConn_rollback(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - - err := base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - assert.True(t, err.IsOk()) - - sabi.Begin(base) - - var conn sabi.DaxConn - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) +type LogicDax interface { + Dax + getData() string +} - conn, err = sabi.GetDaxConn[*BarDaxConn](base, "bar") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) +func TestTxn_failToCastDaxBase(t *testing.T) { + Reset() + defer Reset() + + func() { + base := NewDaxBase() + defer base.Close() + + err := Txn(base, func(dax LogicDax) errs.Err { + return errs.Ok() + }) + switch r := err.Reason().(type) { + case FailToCastDaxBase: + assert.Equal(t, r.FromType, "sabi.DaxBase") + assert.Equal(t, r.ToType, "sabi.LogicDax") + default: + assert.Fail(t, err.Error()) + } + }() +} - sabi.Rollback(base) +func TestTxn_failToRunLogic(t *testing.T) { + Reset() + defer Reset() + + base := NewDaxBase() + defer base.Close() + + type FailToDoSomething struct{} + + func() { + base := NewDaxBase() + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) + err = err.IfOk(base.Uses_("file", &BarDaxSrc{})) + assert.True(t, err.IsOk()) + + err = Txn(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 1") + return errs.New(FailToDoSomething{}) + }, func(dax Dax) errs.Err { + _, err := GetDaxConn[*BarDaxConn](dax, "file") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 2") + return errs.Ok() + }) + switch err.Reason().(type) { + case FailToDoSomething: + default: + assert.Fail(t, err.Error()) + } + }() log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Equal(t, log.Value, "BarDaxSrc#Setup") log = log.Next() assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") + assert.Equal(t, log.Value, "run logic 1") log = log.Next() - if log.Value == "FooDaxConn#Rollback" { - assert.Equal(t, log.Value, "FooDaxConn#Rollback") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxConn#Rollback") - } else { - assert.Equal(t, log.Value, "BarDaxConn#Rollback") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxConn#Rollback") - } + assert.Equal(t, log.Value, "FooDaxConn#Rollback") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") log = log.Next() assert.Nil(t, log) } -func TestDaxBase_getDaxConn_end(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - - err := base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - assert.True(t, err.IsOk()) - - err = base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - assert.True(t, err.IsOk()) - - sabi.Begin(base) - - var conn sabi.DaxConn - conn, err = sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) - - conn, err = sabi.GetDaxConn[*BarDaxConn](base, "bar") - assert.NotNil(t, conn) - assert.True(t, err.IsOk()) - - err = sabi.Commit(base) - assert.True(t, err.IsOk()) - - sabi.End(base) - assert.True(t, err.IsOk()) - - base.FreeAllLocalDaxSrcs() +func TestTxn_failToCommit_sync(t *testing.T) { + Reset() + defer Reset() + + base := NewDaxBase() + defer base.Close() + + func() { + base := NewDaxBase() + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) + err = base.Uses("file", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + WillFailToCommitFooDaxConn = true + + err = Txn(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 1") + return errs.Ok() + }, func(dax Dax) errs.Err { + _, err := GetDaxConn[*BarDaxConn](dax, "file") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 2") + return errs.Ok() + }) + }() log := Logs.Front() - assert.Equal(t, log.Value, "FooDaxSrc#SetUp") + assert.Equal(t, log.Value, "FooDaxSrc#Setup") log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#SetUp") + assert.Equal(t, log.Value, "BarDaxSrc#Setup") log = log.Next() assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") log = log.Next() + assert.Equal(t, log.Value, "run logic 1") + log = log.Next() assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") log = log.Next() - if log.Value == "FooDaxConn#Commit" { - assert.Equal(t, log.Value, "FooDaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxConn#Commit") - } else { - assert.Equal(t, log.Value, "BarDaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxConn#Commit") - } + assert.Equal(t, log.Value, "run logic 2") log = log.Next() - if log.Value == "FooDaxConn#Close" { - assert.Equal(t, log.Value, "FooDaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxConn#Close") - } else { - assert.Equal(t, log.Value, "BarDaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxConn#Close") - } + assert.Equal(t, log.Value, "FooDaxConn#Rollback") log = log.Next() - if log.Value == "FooDaxSrc#End" { - assert.Equal(t, log.Value, "FooDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "BarDaxSrc#End") - } else { - assert.Equal(t, log.Value, "BarDaxSrc#End") - log = log.Next() - assert.Equal(t, log.Value, "FooDaxSrc#End") - } + assert.Equal(t, log.Value, "BarDaxConn#Rollback") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") log = log.Next() assert.Nil(t, log) } -func TestDax_runTxn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - hogeDs := NewMapDaxSrc() - fugaDs := NewMapDaxSrc() - piyoDs := NewMapDaxSrc() - - base := NewHogeFugaPiyoDaxBase() - - var err sabi.Err - if err = base.SetUpLocalDaxSrc("hoge", hogeDs); err.IsNotOk() { - assert.Fail(t, err.Error()) - return - } - if err = base.SetUpLocalDaxSrc("fuga", fugaDs); err.IsNotOk() { - assert.Fail(t, err.Error()) - return - } - if err = base.SetUpLocalDaxSrc("piyo", piyoDs); err.IsNotOk() { - assert.Fail(t, err.Error()) - return - } - - hogeDs.dataMap["hogehoge"] = "Hello, world" - - if err = sabi.RunTxn(base, HogeFugaLogic); err.IsNotOk() { - assert.Fail(t, err.Error()) - return - } - if err = sabi.RunTxn(base, FugaPiyoLogic); err.IsNotOk() { - assert.Fail(t, err.Error()) - return - } - - assert.Equal(t, piyoDs.dataMap["piyopiyo"], "Hello, world") -} +func TestTxn_failToCommit_async(t *testing.T) { + Reset() + defer Reset() + + base := NewDaxBase() + defer base.Close() + + func() { + base := NewDaxBase() + defer base.Close() + + err := base.Uses("database", FooDaxSrc{}) + assert.True(t, err.IsOk()) + err = base.Uses("file", &BarDaxSrc{}) + assert.True(t, err.IsOk()) + + WillFailToCommitBarDaxConn = true + + err = Txn(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 1") + return errs.Ok() + }, func(dax Dax) errs.Err { + _, err := GetDaxConn[*BarDaxConn](dax, "file") + assert.True(t, err.IsOk()) + Logs.PushBack("run logic 2") + return errs.Ok() + }) + }() -func TestGetDaxConn_whenDaxConnIsValue(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.True(t, err.IsOk()) - assert.NotNil(t, conn) - assert.Equal(t, fmt.Sprintf("%T", conn), "sabi_test.FooDaxConn") + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") + log = log.Next() + assert.Equal(t, log.Value, "run logic 1") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") + log = log.Next() + assert.Equal(t, log.Value, "run logic 2") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Commit") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#ForceBack") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Rollback") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) } -func TestGetDaxConn_whenDaxConnIsValueButError(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() +func TestTxn_runner(t *testing.T) { + Reset() + defer Reset() - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - conn, err := sabi.GetDaxConn[*BarDaxConn](base, "foo") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "FailToCastDaxConn") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "foo") - assert.Equal(t, err.Get("FromType"), - "FooDaxConn (github.com/sttk/sabi_test)") - assert.Equal(t, err.Get("ToType"), - "*BarDaxConn (github.com/sttk/sabi_test)") - assert.Nil(t, conn) -} - -func TestGetDaxConn_whenDaxConnIsValueButPointer(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - conn, err := sabi.GetDaxConn[*FooDaxConn](base, "foo") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "FailToCastDaxConn") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "foo") - assert.Equal(t, err.Get("FromType"), - "FooDaxConn (github.com/sttk/sabi_test)") - assert.Equal(t, err.Get("ToType"), - "*FooDaxConn (github.com/sttk/sabi_test)") - assert.Nil(t, conn) -} - -func TestGetDaxConn_whenDaxConnIsPointer(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - conn, err := sabi.GetDaxConn[*BarDaxConn](base, "bar") - assert.True(t, err.IsOk()) - assert.NotNil(t, conn) - assert.Equal(t, fmt.Sprintf("%T", conn), "*sabi_test.BarDaxConn") -} + Reset() + defer Reset() -func TestGetDaxConn_whenDaxConnIsPointerButError(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() + func() { + base := NewDaxBase() + defer base.Close() - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - conn, err := sabi.GetDaxConn[*FooDaxConn](base, "bar") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "FailToCastDaxConn") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "bar") - assert.Equal(t, err.Get("FromType"), - "*BarDaxConn (github.com/sttk/sabi_test)") - assert.Equal(t, err.Get("ToType"), - "*FooDaxConn (github.com/sttk/sabi_test)") - assert.Nil(t, conn) -} - -/* Raise compile error -func TestGetDaxConn_whenDaxConnIsPointerButValue(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("bar", BarDaxSrc{}) - conn, err := sabi.GetDaxConn[BarDaxConn](base, "bar") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "FailToCastDaxConn") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "bar") - assert.Equal(t, err.Get("FromType"), - "*BarDaxConn (github.com/sttk/sabi_test)") - assert.Equal(t, err.Get("ToType"), - "BarDaxConn (github.com/sttk/sabi_test)") - assert.NotNil(t, conn) -} -*/ - -func TestGetDaxConn_whenDaxConnIsNotFound(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - base := sabi.NewDaxBase() - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "DaxSrcIsNotFound") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "foo") - assert.NotNil(t, conn) -} - -func TestGetDaxConn_whenDaxConnIsFailedToCreate(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() + err := base.Uses("database", FooDaxSrc{}). + IfOk(base.Uses_("file", &BarDaxSrc{})). + IfOk(Txn_(base, func(dax Dax) errs.Err { + _, err := GetDaxConn[FooDaxConn](dax, "database") + assert.True(t, err.IsOk()) + return errs.Ok() + }, func(dax Dax) errs.Err { + _, err := GetDaxConn[*BarDaxConn](dax, "file") + assert.True(t, err.IsOk()) + return errs.Ok() + })) - WillFailToCreateFooDaxConn = true + assert.True(t, err.IsOk()) + }() - base := sabi.NewDaxBase() - base.SetUpLocalDaxSrc("foo", FooDaxSrc{}) - conn, err := sabi.GetDaxConn[FooDaxConn](base, "foo") - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "FailToCreateDaxConn") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi") - assert.Equal(t, err.Get("Name"), "foo") - assert.NotNil(t, conn) + log := Logs.Front() + assert.Equal(t, log.Value, "FooDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Setup") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#CreateDaxConn") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#CreateDaxConn") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Commit") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Commit") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxConn#Close") + log = log.Next() + assert.Equal(t, log.Value, "FooDaxSrc#Close") + log = log.Next() + assert.Equal(t, log.Value, "BarDaxSrc#Close") + log = log.Next() + assert.Nil(t, log) } diff --git a/doc.go b/doc.go index e143464..f417d04 100644 --- a/doc.go +++ b/doc.go @@ -3,224 +3,13 @@ // See the file LICENSE in this distribution for more details. /* -Package github.com/sttk/sabi is a small framework to separate logic parts and data accesses parts for Golang applications. - -# Logic - -A logic is implemented as a function. -This function takes only an argument, dax which is an interface and collects data access methods used in this function. -Also, this function returns only a sabi.Err value which indicates that this function succeeds or not. -Since the dax hides details of data access procedures, only logical procedure appears in this function. -In this logic part, it's no concern where a data comes from or goes to. - -For example, in the following code, GreetLogic is a logic function and GreetDax is a dax interface. - - import "github.com/sttk/sabi" - - type GreetDax interface { - UserName() (string, sabi.Err) - Say(greeting string) sabi.Err - } - - type ( // possible error reasons - NoName struct {} - FailToOutput struct {Text string} - ) - - func GreetLogic(dax GreetDax) sabi.Err { - name, err := dax.UserName() - if !err.IsOk() { - return err - } - return dax.Say("Hello, " + name) - } - -In GreetLogic function, there are no codes for getting a user name and output a greeting text. -In this logic function, it's only concern to create a greeting text from a user name. - -# Dax for unit tests - -To test a logic function, the simplest dax implementation is what using a map. -The following code is an example of a dax implementation using a map and having two methods: UserName and Say which are same to GreetDax interface above. - - type mapGreetDax struct { - m map[string]any - } - - func (dax mapGreetDax) UserName() (string, sabi.Err) { - username, exists := dax.m["username"] - if !exists { - return "", sabi.NewErr(NoName{}) - } - return username.(string), sabi.Ok() - } - - func (dax mapGreetDax) Say(greeting string) sabi.Err { - dax.m["greeting"] = greeting - return sabi.Ok() - } - - func NewMapGreetDaxBase(m map[string]any) sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - mapGreetDax - } { - DaxBase: base, - mapGreetDax: mapGreetDax{m: m}, - } - } - -And the following code is an example of a test case. - - import ( - "github.com/stretchr/testify/assert" - "testing" - ) - - func TestGreetLogic_normal(t *testing.T) { - m := make(map[string]any) - base := NewMapGreetDaxBase(m) - - m["username"] = "World" - err := sabi.RunTxn(base, GreetLogic) - assert.Equal(t, m["greeting"], "Hello, World") - } - -# Dax for real data accesses - -In actual case, multiple data sources are often used. -In this example, an user name is input as command line argument, and greeting is output to standard output (console output). -Therefore, two dax implementations are attached to the single GreetDax interface. - -The following code is an example of a dax implementation which inputs an user name from command line argument. - - import "os" - - type CliArgsUserDax struct { - } - - func (dax CliArgsUserDax) UserName() (string, sabi.Err) { - if len(os.Args) <= 1 { - return "", sabi.NewErr(NoName{}) - } - return os.Args[1], sabi.Ok() - } - -In addition, the following code is an example of a dax implementation which outputs a greeting test to console. - - import "fmt" - - type ConsoleOutputDax struct { - } - - func (dax ConsoleOutputDax) Say(text string) sabi.Err { - _, e := fmt.Println(text) - if e != nil { - return sabi.NewErr(FailToOutput{Text: text}, e) - } - return sabi.Ok() - } - -And these dax implementations are combined to a DaxBase as follows: - - func NewGreetDaxBase() sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - CliArgsUserDax - ConsoleOutputDax - } { - DaxBase: base, - CliArgsUserDax: CliArgsUserDax{}, - ConsoleOutputDax: ConsoleOutputDax{}, - } - } - -# Executing logic - -The following code implements a main function which execute a GreetLogic. -sabi.RunTxn executes the GreetLogic function in a transaction process. - - import "log" - - func main() { - base := NewGreetDaxBase() - err := sabi.RunTxn(base, GreetLogic) - if !err.IsOk() { - log.Fatalln(err.Reason()) - } - } - -# Moving outputs to another transaction process - -sabi.RunTxn executes logic functions in a transaction. If a logic function updates database and causes an error in the transaction, its update is rollbacked. -If console output is executed in the same transaction with database update, the rollbacked result is possible to be output to console. -Therefore, console output is wanted to execute after the transaction of database update is successfully completed. - -What should be done to achieve it are to add a dax interface for next transaction, to change ConsoleOutputDax to hold a greeting text in Say method, to add a new method to output it in next transaction, and to execute the next transaction in the main function. - - type PrintDax interface { // Added. - Print() sabi.Err - } - - type ConsoleOutputDax struct { - text string // Added - } - func (dax *ConsoleOutputDax) Say(text string) sabi.Err { // Changed to pointer - dax.text = text // Changed - return sabi.Ok() - } - func (dax *ConsoleOutputDax) Print() sabi.Err { // Added - _, e := fmt.Println(dax.text) - if e != nil { - return sabi.NewErr(FailToOutput{Text: dax.text}, e) - } - return sabi.Ok() - } - - func NewGreetDaxBase() sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - CliArgsUserDax - *ConsoleOutputDax // Changed - }{ - DaxBase: base, - CliArgsUserDax: CliArgsUserDax{}, - ConsoleOutputDax2: &ConsoleOutputDax2{}, // Changed - } - } - -And the main function is modified as follows: - - func main() { - err := sabi.RunTxn(base, GreetLogic) - if !err.IsOk() { - log.Fatalln(err.Reason()) - } - err = sabi.RunTxn(base, func(dax PrintDax) sabi.Err { - return dax.Print() - }) - if !err.IsOk() { - log.Fatalln(err.Reason()) - } - } - -Or, the main function is able to rewrite as follows: - - func main() { - txn0 := sabi.Txn(base, GreetLogic) - txn1 := sabi.Txn(base, func(dax PrintDax) sabi.Err { - return dax.Print() - }) - err := sabi.RunSeq(txn0, txn1) - if !err.IsOk() { - log.Fatalln(err.Reason()) - } - } - -The important point is that the GreetLogic function is not changed. -Since this change is not related to the application logic, it is confined to the data access part only. +Package github.com/sttk/sabi is a small framework to separate logic parts and +data access parts for Golang applications. + +The concept of this framework is separation and reintegration of necessary and +redundant parts based on the perspectives of the whole and the +parts. +The separation of logics and data accesses is the most prominent and +fundamental part of this concept. */ package sabi diff --git a/doc_test.go b/doc_test.go deleted file mode 100644 index e468504..0000000 --- a/doc_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package sabi_test - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sttk/sabi" -) - -type GreetDax interface { - UserName() (string, sabi.Err) - Say(greeting string) sabi.Err -} - -type ( // possible error reasons - NoName struct{} - FailToOutput struct{ Text string } -) - -func GreetLogic(dax GreetDax) sabi.Err { - name, err := dax.UserName() - if !err.IsOk() { - return err - } - return dax.Say("Hello, " + name) -} - -type mapGreetDax struct { - m map[string]any -} - -func (dax mapGreetDax) UserName() (string, sabi.Err) { - username, exists := dax.m["username"] - if !exists { - return "", sabi.NewErr(NoName{}) - } - return username.(string), sabi.Ok() -} - -func (dax mapGreetDax) Say(greeting string) sabi.Err { - dax.m["greeting"] = greeting - return sabi.Ok() -} - -func NewMapGreetDaxBase(m map[string]any) sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - mapGreetDax - }{ - DaxBase: base, - mapGreetDax: mapGreetDax{m: m}, - } -} - -func TestGreetLogic_unitTest(t *testing.T) { - m := make(map[string]any) - base := NewMapGreetDaxBase(m) - - m["username"] = "World" - err := sabi.RunTxn(base, GreetLogic) - assert.True(t, err.IsOk()) - assert.Equal(t, m["greeting"], "Hello, World") -} - -var osArgs []string - -type CliArgsUserDax struct { -} - -func (dax CliArgsUserDax) UserName() (string, sabi.Err) { - if len(osArgs) <= 1 { - return "", sabi.NewErr(NoName{}) - } - return osArgs[1], sabi.Ok() -} - -type ConsoleOutputDax struct { -} - -func (dax ConsoleOutputDax) Say(text string) sabi.Err { - _, e := fmt.Println(text) - if e != nil { - return sabi.NewErr(FailToOutput{Text: text}, e) - } - return sabi.Ok() -} - -func NewGreetDaxBase() sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - CliArgsUserDax - ConsoleOutputDax - }{ - DaxBase: base, - CliArgsUserDax: CliArgsUserDax{}, - ConsoleOutputDax: ConsoleOutputDax{}, - } -} - -func TestGreetLogic_executingLogic(t *testing.T) { - osArgs = []string{"cmd", "Tom"} - base := NewGreetDaxBase() - err := sabi.RunTxn(base, GreetLogic) - assert.True(t, err.IsOk()) -} - -type PrintDax interface { - Print() sabi.Err -} - -type ConsoleOutputDax2 struct { - text string -} - -func (dax *ConsoleOutputDax2) Say(text string) sabi.Err { - dax.text = text - return sabi.Ok() -} -func (dax *ConsoleOutputDax2) Print() sabi.Err { - _, e := fmt.Println(dax.text) - if e != nil { - return sabi.NewErr(FailToOutput{Text: dax.text}, e) - } - return sabi.Ok() -} - -func NewGreetDaxBase2() sabi.DaxBase { - base := sabi.NewDaxBase() - return struct { - sabi.DaxBase - CliArgsUserDax - *ConsoleOutputDax2 - }{ - DaxBase: base, - CliArgsUserDax: CliArgsUserDax{}, - ConsoleOutputDax2: &ConsoleOutputDax2{}, - } -} - -func TestGreetLogic_MovingOutputs(t *testing.T) { - base := NewGreetDaxBase2() - err := sabi.RunTxn(base, GreetLogic) - assert.True(t, err.IsOk()) - err = sabi.RunTxn(base, func(dax PrintDax) sabi.Err { - return dax.Print() - }) - assert.True(t, err.IsOk()) -} - -func TestGreetLogic_MovingOutputs2(t *testing.T) { - base := NewGreetDaxBase2() - txn0 := sabi.Txn(base, GreetLogic) - txn1 := sabi.Txn(base, func(dax PrintDax) sabi.Err { - return dax.Print() - }) - err := sabi.RunSeq(txn0, txn1) - assert.True(t, err.IsOk()) -} diff --git a/err.go b/err.go deleted file mode 100644 index b3c3dce..0000000 --- a/err.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. -// This program is free software under MIT License. -// See the file LICENSE in this distribution for more details. - -package sabi - -import ( - "fmt" - "reflect" -) - -// Err is a struct which represents an error with a reason. -type Err struct { - reason any - cause error -} - -var ok = Err{} - -// Ok is a function which returns an Err of which reason is nil. -func Ok() Err { - return ok -} - -// NewErr is a function which creates a new Err with a specified reason and -// an optional cause. -// A reason is a struct of which name expresses what is a reason. -func NewErr(reason any, cause ...error) Err { - var err Err - err.reason = reason - - if len(cause) > 0 { - err.cause = cause[0] - } - - notifyErr(err) - - return err -} - -// IsOk method checks whether this Err indicates no error. -func (err Err) IsOk() bool { - return (err.reason == nil) -} - -// IsNotOk method checks whether this Err indicates an error. -func (err Err) IsNotOk() bool { - return (err.reason != nil) -} - -// Reason method returns an err reaason struct. -func (err Err) Reason() any { - return err.reason -} - -// ReasonName method returns a name of a reason struct type. -func (err Err) ReasonName() string { - if err.reason == nil { - return "" - } - t := reflect.TypeOf(err.reason) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t.Name() -} - -// ReasonPackage method returns a package path of a reason struct type. -func (err Err) ReasonPackage() string { - if err.reason == nil { - return "" - } - t := reflect.TypeOf(err.reason) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t.PkgPath() -} - -// Cause method returns a causal error of this Err. -func (err Err) Cause() error { - return err.cause -} - -// Error method returns a string which expresses this error. -func (err Err) Error() string { - if err.reason == nil { - return "{reason=nil}" - } - - v := reflect.ValueOf(err.reason) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - t := v.Type() - - s := "{reason=" + t.Name() - - n := v.NumField() - for i := 0; i < n; i++ { - k := t.Field(i).Name - - f := v.Field(i) - if f.CanInterface() { - s += ", " + k + "=" + fmt.Sprintf("%v", f.Interface()) - } - } - - if err.cause != nil { - s += ", cause=" + err.cause.Error() - } - - s += "}" - return s -} - -// Unwrap method returns an error which is wrapped in this error. -func (err Err) Unwrap() error { - return err.cause -} - -// Get method returns a parameter value of a specified name, which is one of -// fields of the reason struct. -// If the specified named field is not found in this Err and this cause is -// also Err struct, this method digs hierarchically to find the field. -func (err Err) Get(name string) any { - if err.reason == nil { - return nil - } - - v := reflect.ValueOf(err.reason) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - f := v.FieldByName(name) - if f.IsValid() && f.CanInterface() { - return f.Interface() - } - - if err.cause != nil { - t := reflect.TypeOf(err.cause) - _, ok := t.MethodByName("Reason") - if ok { - _, ok := t.MethodByName("Get") - if ok { - return err.cause.(Err).Get(name) - } - } - } - - return nil -} - -// Situation method returns a map containing the field names and values of this -// reason struct and of this cause if it is also Err struct. -func (err Err) Situation() map[string]any { - var m map[string]any - - if err.reason == nil { - return m - } - - v := reflect.ValueOf(err.reason) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - if err.cause != nil { - t := reflect.TypeOf(err.cause) - _, ok := t.MethodByName("Reason") - if ok { - _, ok := t.MethodByName("Situation") - if ok { - m = err.cause.(Err).Situation() - } - } - } - - if m == nil { - m = make(map[string]any) - } - - t := v.Type() - - n := v.NumField() - for i := 0; i < n; i++ { - k := t.Field(i).Name - - f := v.Field(i) - if f.CanInterface() { // false if field is not public - m[k] = f.Interface() - } - } - - return m -} - -// IfOk method executes an argument function if this Err indicates non error. -// If this Err indicates some error, this method just returns this Err. -func (err Err) IfOk(fn func() Err) Err { - if err.IsOk() { - return fn() - } - return err -} diff --git a/err_test.go b/err_test.go deleted file mode 100644 index 2e4fa8d..0000000 --- a/err_test.go +++ /dev/null @@ -1,252 +0,0 @@ -package sabi_test - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sttk/sabi" -) - -type /* error reasons */ ( - InvalidValue struct { - Value string - } - FailToGetValue struct { - Name string - } -) - -type InvalidValueError struct { - Value string -} - -func (e InvalidValueError) Error() string { - return "InvalidValue{Value=" + e.Value + "}" -} - -func TestNewErr_reasonIsValue(t *testing.T) { - err := sabi.NewErr(InvalidValue{Value: "abc"}) - - assert.Equal(t, err.Error(), "{reason=InvalidValue, Value=abc}") - - switch err.Reason().(type) { - case InvalidValue: - default: - assert.Fail(t, err.Error()) - } - - assert.False(t, err.IsOk()) - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "InvalidValue") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi_test") - assert.Equal(t, err.Get("Value"), "abc") - assert.Nil(t, err.Get("value")) - assert.Nil(t, err.Get("Name")) - - m := err.Situation() - assert.Equal(t, len(m), 1) - assert.Equal(t, m["Value"], "abc") - assert.Nil(t, m["value"]) - - assert.Nil(t, err.Cause()) - assert.Nil(t, err.Unwrap()) - assert.Nil(t, errors.Unwrap(err)) - - assert.True(t, errors.Is(err, err)) - assert.True(t, errors.As(err, &err)) - - e := InvalidValueError{Value: "aaa"} - assert.False(t, errors.Is(err, e)) - assert.False(t, errors.As(err, &e)) -} - -func TestNewErr_reasonIsPointer(t *testing.T) { - err := sabi.NewErr(&InvalidValue{Value: "abc"}) - - assert.Equal(t, err.Error(), "{reason=InvalidValue, Value=abc}") - - switch err.Reason().(type) { - case *InvalidValue: - default: - assert.Fail(t, err.Error()) - } - - assert.False(t, err.IsOk()) - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "InvalidValue") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi_test") - assert.Equal(t, err.Get("Value"), "abc") - assert.Nil(t, err.Get("value")) - assert.Nil(t, err.Get("Name")) - - m := err.Situation() - assert.Equal(t, len(m), 1) - assert.Equal(t, m["Value"], "abc") - assert.Nil(t, m["value"]) - - assert.Nil(t, err.Cause()) - assert.Nil(t, err.Unwrap()) - assert.Nil(t, errors.Unwrap(err)) - - assert.True(t, errors.Is(err, err)) - assert.True(t, errors.As(err, &err)) - - e := InvalidValueError{Value: "aaa"} - assert.False(t, errors.Is(err, e)) - assert.False(t, errors.As(err, &e)) -} - -func TestNewErr_withCause(t *testing.T) { - cause := errors.New("def") - err := sabi.NewErr(InvalidValue{Value: "abc"}, cause) - - assert.Equal(t, err.Error(), "{reason=InvalidValue, Value=abc, cause=def}") - - switch err.Reason().(type) { - case InvalidValue: - default: - assert.Fail(t, err.Error()) - } - - assert.False(t, err.IsOk()) - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "InvalidValue") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi_test") - assert.Equal(t, err.Get("Value"), "abc") - assert.Nil(t, err.Get("value")) - assert.Nil(t, err.Get("Name")) - - m := err.Situation() - assert.Equal(t, len(m), 1) - assert.Equal(t, m["Value"], "abc") - - assert.Equal(t, err.Cause(), cause) - assert.Equal(t, err.Unwrap(), cause) - assert.Equal(t, errors.Unwrap(err), cause) - - assert.True(t, errors.Is(err, err)) - assert.True(t, errors.As(err, &err)) - - assert.True(t, errors.Is(err, cause)) - //assert.True(t, errors.As(err, cause)) --> compile error - - e := InvalidValueError{Value: "aaa"} - assert.False(t, errors.Is(err, e)) - assert.False(t, errors.As(err, &e)) -} - -func TestNewErr_causeIsAlsoErr(t *testing.T) { - cause := sabi.NewErr(FailToGetValue{Name: "foo"}) - err := sabi.NewErr(InvalidValue{Value: "abc"}, cause) - - assert.Equal(t, err.Error(), "{reason=InvalidValue, Value=abc, cause={reason=FailToGetValue, Name=foo}}") - - switch err.Reason().(type) { - case InvalidValue: - default: - assert.Fail(t, err.Error()) - } - - assert.False(t, err.IsOk()) - assert.True(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "InvalidValue") - assert.Equal(t, err.ReasonPackage(), "github.com/sttk/sabi_test") - - assert.Equal(t, err.Get("Value"), "abc") - assert.Equal(t, err.Get("Name"), "foo") - assert.Nil(t, err.Get("value")) - - m := err.Situation() - assert.Equal(t, len(m), 2) - assert.Equal(t, m["Value"], "abc") - assert.Equal(t, m["Name"], "foo") - - assert.Equal(t, err.Cause(), cause) - assert.Equal(t, err.Unwrap(), cause) - assert.Equal(t, errors.Unwrap(err), cause) - - assert.True(t, errors.Is(err, err)) - assert.True(t, errors.As(err, &err)) - - assert.True(t, errors.Is(err, cause)) - assert.True(t, errors.As(err, &cause)) - - e := InvalidValueError{Value: "aaa"} - assert.False(t, errors.Is(err, e)) - assert.False(t, errors.As(err, &e)) -} - -func TestOk(t *testing.T) { - err := sabi.Ok() - - assert.Equal(t, err.Error(), "{reason=nil}") - assert.Nil(t, err.Reason()) - - switch err.Reason().(type) { - case nil: - default: - assert.Fail(t, err.Error()) - } - - assert.True(t, err.IsOk()) - assert.False(t, err.IsNotOk()) - assert.Equal(t, err.ReasonName(), "") - assert.Equal(t, err.ReasonPackage(), "") - assert.Nil(t, err.Get("Value")) - assert.Nil(t, err.Get("value")) - assert.Nil(t, err.Get("Name")) - - m := err.Situation() - assert.Equal(t, len(m), 0) - - assert.Nil(t, err.Cause()) - assert.Nil(t, err.Unwrap()) - assert.Nil(t, errors.Unwrap(err)) - - assert.True(t, errors.Is(err, err)) - assert.True(t, errors.As(err, &err)) - - e := InvalidValueError{Value: "aaa"} - assert.False(t, errors.Is(err, e)) - assert.False(t, errors.As(err, &e)) -} - -func TestErr_IfOk_ok(t *testing.T) { - err := sabi.Ok() - - s := "" - err2 := err.IfOk(func() sabi.Err { - s = "executed." - return sabi.NewErr(InvalidValue{Value: "x"}) - }) - - assert.Equal(t, s, "executed.") - assert.True(t, err.IsOk()) - assert.True(t, err2.IsNotOk()) - switch err2.Reason().(type) { - case InvalidValue: - default: - assert.Fail(t, err2.Error()) - } -} - -func TestErr_IfOk_err(t *testing.T) { - err := sabi.NewErr(InvalidValue{Value: "x"}) - - s := "" - err2 := err.IfOk(func() sabi.Err { - s = "executed." - return sabi.Ok() - }) - - assert.Equal(t, s, "") - assert.True(t, err.IsNotOk()) - assert.True(t, err2.IsNotOk()) - switch err2.Reason().(type) { - case InvalidValue: - default: - assert.Fail(t, err2.Error()) - } -} diff --git a/errs/err.go b/errs/err.go new file mode 100644 index 0000000..3e7fbba --- /dev/null +++ b/errs/err.go @@ -0,0 +1,268 @@ +// Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +// errs is the package for error processing for sabi framework. +// This package provides Err struct instead of Go standard error. +// Err takes an any struct which indicates a reason of an error of creating it. +// And Err has some functionalities to make it easier to hold, get, and notify +// error informations. +// +// # Creating an Err +// +// In contract that Go standard error requires to implement a struct with Error +// method to output a string of error content, Err only requires to implement a +// struct with no method. +// +// type FailToDoSomething struct { Name string } +// err := errs.New(FailToDoSomething{Name: name}) +// +// And unlike Go standard error of which value is nil for no error, Err has an +// instance for no error by Ok function. +// +// err := errs.Ok() +// +// In addition, Err provides methods to check whether an Err instance indicates +// an error or not. +// +// if err.IsOk() { ... } // err indicates no error. +// if err.IsNotOk() { ... } // err indicates an error. +// +// Also, Err provides the method to do a next function if it is no error. +// +// func doSomething() errs.Err { ... } +// func doNextThing() errs.Err { ... } +// return doSomething().IfOk(doNextThing) +// +// # Distinction of error kinds +// +// To distinct error kinds, a type switch statement can be applied to a type of +// Err's reason. +// +// switch err.Reason().(type) { +// case nil: +// ... +// case FailToDoSomething: +// reason, ok := err.Reason().(FailToDoSomething) +// ... +// default: +// ... +// } +// +// # Error notifications +// +// This package support notification of error creations. +// Notification handlers can be registered with AddSyncHandler and +// AddAsyncHandler. +// When a Err is created with New function, handlers receives an Err instance +// and a ErrOcc instance. +// ErrOcc is a struct having informations when and where the error is occured. +// +// errs.AddSyncHandler(func(err errs.Err, occ errs.ErrOcc) { +// logger.Printf("%s (%s:%d) %v\n", +// occ.Time().Format("2006-01-02T15:04:05Z"), +// occ.File(), occ.Line(), err) +// }) +// errs.FixCfg() +// +// errs.New(FailToDoSomething{Name: name}) +package errs + +import ( + "fmt" + "reflect" +) + +// Err is a struct type which represents an error with a reason. +// This instance can has an instance or pointer of any struct type of which +// indicates a reason by which this error is caused. +// A reason can has some fields that helps to know error situation where this +// error is caused. +type Err struct { + reason any + cause error +} + +var ok = Err{} + +// Ok is the function which returns an Err instance of which reason is nil. +func Ok() Err { + return ok +} + +// New is the function which creates a new Err with a specified reason and an +// optional cause. +// A reason is a struct type of which name expresses what is a reason. +func New(reason any, cause ...error) Err { + var e Err + e.reason = reason + + if len(cause) > 0 { + e.cause = cause[0] + } + + notifyErr(e) + + return e +} + +// IsOk is the method that checks whether this Err indicates there is no error. +func (e Err) IsOk() bool { + return (e.reason == nil) +} + +// IsNotOk is the method that checks whether this Err indicates there is an error. +func (e Err) IsNotOk() bool { + return (e.reason != nil) +} + +// IfOk is the method that executes an argument function if this Err indicates there is no error. +// If this Err indicates there is an error, this method just returns this Err it self. +func (e Err) IfOk(fn func() Err) Err { + if e.IsOk() { + return fn() + } + return e +} + +// Reason is the method to get the reason of this error. +func (e Err) Reason() any { + return e.reason +} + +// ReasonName is the method to get the name of this reason's struct type. +func (e Err) ReasonName() string { + if e.reason == nil { + return "" + } + t := reflect.TypeOf(e.reason) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Name() +} + +// ReasonPackage is the method to get the package path of this reason's struct +// type. +func (e Err) ReasonPackage() string { + if e.reason == nil { + return "" + } + t := reflect.TypeOf(e.reason) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.PkgPath() +} + +// Error is the method to get a string that expresses the content of this +// error. +func (e Err) Error() string { + if e.reason == nil { + return "{reason=nil}" + } + + v := reflect.ValueOf(e.reason) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + t := v.Type() + + s := "{reason=" + t.Name() + + n := v.NumField() + for i := 0; i < n; i++ { + k := t.Field(i).Name + + f := v.Field(i) + if f.CanInterface() { // false if the field is nor public + s += ", " + k + "=" + fmt.Sprintf("%v", f.Interface()) + } + } + + if e.cause != nil { + s += ", cause=" + e.cause.Error() + } + + s += "}" + return s +} + +// Unwrap is the method to get an error which is wrapped in this error. +func (e Err) Unwrap() error { + return e.cause +} + +// Cause is the method to get the causal error of this Err. +func (e Err) Cause() error { + return e.cause +} + +// Get is the method to get a field value of the reason struct type by the +// specified name. +// If the specified named field is not found in the reason of this Err, this +// method finds a same named field in reasons of cause errors hierarchically. +func (e Err) Get(name string) any { + if e.reason == nil { + return nil + } + + v := reflect.ValueOf(e.reason) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + f := v.FieldByName(name) + if f.IsValid() && f.CanInterface() { + return f.Interface() + } + + if e.cause != nil { + c, ok := e.cause.(Err) + if ok { + return c.Get(name) + } + } + + return nil +} + +// Situation is the method to get a map which contains parameters that +// represents error situation. +func (e Err) Situation() map[string]any { + var m map[string]any + + if e.reason == nil { + return m + } + + v := reflect.ValueOf(e.reason) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if e.cause != nil { + c, ok := e.cause.(Err) + if ok { + m = c.Situation() + } + } + + if m == nil { + m = make(map[string]any) + } + + t := v.Type() + n := v.NumField() + + for i := 0; i < n; i++ { + k := t.Field(i).Name + f := v.Field(i) + if f.CanInterface() { // false if the field is nor public + m[k] = f.Interface() + } + } + + return m +} diff --git a/errs/err_test.go b/errs/err_test.go new file mode 100644 index 0000000..bb1ee14 --- /dev/null +++ b/errs/err_test.go @@ -0,0 +1,283 @@ +package errs_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/sttk/sabi/errs" +) + +type /* error reasons */ ( + InvalidValue struct { + Value string + } + + FailToGetValue struct { + Name string + } +) + +type InvalidValueError struct { + Value string +} + +func (e InvalidValueError) Error() string { + return "InvalidValue{Value=" + e.Value + "}" +} + +func TestErr_New_reasonIsValue(t *testing.T) { + e := errs.New(InvalidValue{Value: "abc"}) + + assert.Equal(t, e.Error(), "{reason=InvalidValue, Value=abc}") + + switch e.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e.Error()) + } + + assert.False(t, e.IsOk()) + assert.True(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "InvalidValue") + assert.Equal(t, e.ReasonPackage(), "github.com/sttk/sabi/errs_test") + assert.Equal(t, e.Get("Value"), "abc") + assert.Nil(t, e.Get("value")) + assert.Nil(t, e.Get("Name")) + + m := e.Situation() + assert.Equal(t, len(m), 1) + assert.Equal(t, m["Value"], "abc") + assert.Nil(t, m["value"]) + + assert.Nil(t, e.Cause()) + assert.Nil(t, e.Unwrap()) + assert.Nil(t, errors.Unwrap(e)) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + er := InvalidValueError{Value: "aaa"} + assert.False(t, errors.Is(e, er)) + assert.False(t, errors.As(e, &er)) +} + +func TestErr_New_reasonIsPointer(t *testing.T) { + e := errs.New(&InvalidValue{Value: "abc"}) + + assert.Equal(t, e.Error(), "{reason=InvalidValue, Value=abc}") + + switch e.Reason().(type) { + case *InvalidValue: + default: + assert.Fail(t, e.Error()) + } + + assert.False(t, e.IsOk()) + assert.True(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "InvalidValue") + assert.Equal(t, e.ReasonPackage(), "github.com/sttk/sabi/errs_test") + assert.Equal(t, e.Get("Value"), "abc") + assert.Nil(t, e.Get("value")) + assert.Nil(t, e.Get("Name")) + + m := e.Situation() + assert.Equal(t, len(m), 1) + assert.Equal(t, m["Value"], "abc") + + assert.Nil(t, e.Cause()) + assert.Nil(t, e.Unwrap()) + assert.Nil(t, errors.Unwrap(e)) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + er := InvalidValueError{Value: "aaa"} + assert.False(t, errors.Is(e, er)) + assert.False(t, errors.As(e, &er)) +} + +func TestErr_New_withCause(t *testing.T) { + cause := errors.New("def") + e := errs.New(InvalidValue{Value: "abc"}, cause) + + assert.Equal(t, e.Error(), "{reason=InvalidValue, Value=abc, cause=def}") + + switch e.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e.Error()) + } + + assert.False(t, e.IsOk()) + assert.True(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "InvalidValue") + assert.Equal(t, e.ReasonPackage(), "github.com/sttk/sabi/errs_test") + assert.Equal(t, e.Get("Value"), "abc") + assert.Nil(t, e.Get("value")) + assert.Nil(t, e.Get("Name")) + + m := e.Situation() + assert.Equal(t, len(m), 1) + assert.Equal(t, m["Value"], "abc") + + assert.Equal(t, e.Cause(), cause) + assert.Equal(t, e.Unwrap(), cause) + assert.Equal(t, errors.Unwrap(e), cause) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + assert.True(t, errors.Is(e, cause)) + //assert.True(t, errors.As(e, &cause)) // --> compile error +} + +func TestErr_New_causeIsCustomError(t *testing.T) { + cause := InvalidValueError{Value: "def"} + e := errs.New(InvalidValue{Value: "abc"}, cause) + + assert.Equal(t, e.Error(), "{reason=InvalidValue, Value=abc, cause=InvalidValue{Value=def}}") + + switch e.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e.Error()) + } + + assert.False(t, e.IsOk()) + assert.True(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "InvalidValue") + assert.Equal(t, e.ReasonPackage(), "github.com/sttk/sabi/errs_test") + assert.Equal(t, e.Get("Value"), "abc") + assert.Nil(t, e.Get("value")) + assert.Nil(t, e.Get("Name")) + + m := e.Situation() + assert.Equal(t, len(m), 1) + assert.Equal(t, m["Value"], "abc") + + assert.Equal(t, e.Cause(), cause) + assert.Equal(t, e.Unwrap(), cause) + assert.Equal(t, errors.Unwrap(e), cause) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + assert.True(t, errors.Is(e, cause)) + assert.True(t, errors.As(e, &cause)) +} + +func TestErr_New_causeIsAlsoErr(t *testing.T) { + cause := errs.New(FailToGetValue{Name: "foo"}) + e := errs.New(InvalidValue{Value: "abc"}, cause) + + assert.Equal(t, e.Error(), "{reason=InvalidValue, Value=abc, cause={reason=FailToGetValue, Name=foo}}") + + switch e.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e.Error()) + } + + assert.False(t, e.IsOk()) + assert.True(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "InvalidValue") + assert.Equal(t, e.ReasonPackage(), "github.com/sttk/sabi/errs_test") + assert.Equal(t, e.Get("Value"), "abc") + assert.Equal(t, e.Get("Name"), "foo") + assert.Nil(t, e.Get("value")) + assert.Nil(t, e.Get("name")) + + m := e.Situation() + assert.Equal(t, len(m), 2) + assert.Equal(t, m["Value"], "abc") + assert.Equal(t, m["Name"], "foo") + + assert.Equal(t, e.Cause(), cause) + assert.Equal(t, e.Unwrap(), cause) + assert.Equal(t, errors.Unwrap(e), cause) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + assert.True(t, errors.Is(e, cause)) + assert.True(t, errors.As(e, &cause)) + + er := InvalidValueError{Value: "aaa"} + assert.False(t, errors.Is(e, er)) + assert.False(t, errors.As(e, &er)) +} + +func TestErr_Ok(t *testing.T) { + e := errs.Ok() + + assert.Equal(t, e.Error(), "{reason=nil}") + assert.Nil(t, e.Reason()) + + switch e.Reason().(type) { + case nil: + default: + assert.Fail(t, e.Error()) + } + + assert.True(t, e.IsOk()) + assert.False(t, e.IsNotOk()) + assert.Equal(t, e.ReasonName(), "") + assert.Equal(t, e.ReasonPackage(), "") + assert.Nil(t, e.Get("Value")) + assert.Nil(t, e.Get("Name")) + + m := e.Situation() + assert.Equal(t, len(m), 0) + + assert.Nil(t, e.Cause()) + assert.Nil(t, e.Unwrap()) + assert.Nil(t, errors.Unwrap(e)) + + assert.True(t, errors.Is(e, e)) + assert.True(t, errors.As(e, &e)) + + er := InvalidValueError{Value: "aaa"} + assert.False(t, errors.Is(e, er)) + assert.False(t, errors.As(e, &er)) +} + +func TestErr_IfOk_ok(t *testing.T) { + e := errs.Ok() + + s := "" + e1 := e.IfOk(func() errs.Err { + s = "executed." + return errs.New(InvalidValue{Value: "x"}) + }) + + assert.Equal(t, s, "executed.") + assert.True(t, e.IsOk()) + assert.True(t, e1.IsNotOk()) + + switch e1.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e1.Error()) + } +} + +func TestErr_IfOk_error(t *testing.T) { + e := errs.New(InvalidValue{Value: "x"}) + + s := "" + e1 := e.IfOk(func() errs.Err { + s = "executed." + return errs.Ok() + }) + + assert.Equal(t, s, "") + assert.True(t, e.IsNotOk()) + assert.True(t, e1.IsNotOk()) + + switch e1.Reason().(type) { + case InvalidValue: + default: + assert.Fail(t, e1.Error()) + } +} diff --git a/example_err_test.go b/errs/example_err_test.go similarity index 75% rename from example_err_test.go rename to errs/example_err_test.go index e70d0c3..f59e015 100644 --- a/example_err_test.go +++ b/errs/example_err_test.go @@ -1,27 +1,27 @@ -package sabi_test +package errs_test import ( "errors" "fmt" - "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) -func ExampleNewErr() { +func ExampleNew() { type /* error reasons */ ( - FailToDoSomething struct{} - FailToDoSomethingWithParams struct { + FailToDoSomething struct{} + FailToDoWithParams struct { Param1 string Param2 int } ) // (1) Creates an Err with no situation parameter. - err := sabi.NewErr(FailToDoSomething{}) + err := errs.New(FailToDoSomething{}) fmt.Printf("(1) %v\n", err) // (2) Creates an Err with situation parameters. - err = sabi.NewErr(FailToDoSomethingWithParams{ + err = errs.New(FailToDoWithParams{ Param1: "ABC", Param2: 123, }) @@ -30,24 +30,24 @@ func ExampleNewErr() { cause := errors.New("Causal error") // (3) Creates an Err with a causal error. - err = sabi.NewErr(FailToDoSomething{}, cause) + err = errs.New(FailToDoSomething{}, cause) fmt.Printf("(3) %v\n", err) // (4) Creates an Err with situation parameters and a causal error. - err = sabi.NewErr(FailToDoSomethingWithParams{ + err = errs.New(FailToDoWithParams{ Param1: "ABC", Param2: 123, }, cause) fmt.Printf("(4) %v\n", err) // Output: // (1) {reason=FailToDoSomething} - // (2) {reason=FailToDoSomethingWithParams, Param1=ABC, Param2=123} + // (2) {reason=FailToDoWithParams, Param1=ABC, Param2=123} // (3) {reason=FailToDoSomething, cause=Causal error} - // (4) {reason=FailToDoSomethingWithParams, Param1=ABC, Param2=123, cause=Causal error} + // (4) {reason=FailToDoWithParams, Param1=ABC, Param2=123, cause=Causal error} } func ExampleOk() { - err := sabi.Ok() + err := errs.Ok() fmt.Printf("err = %v\n", err) fmt.Printf("err.IsOk() = %v\n", err.IsOk()) // Output: @@ -60,7 +60,7 @@ func ExampleErr_Cause() { cause := errors.New("Causal error") - err := sabi.NewErr(FailToDoSomething{}, cause) + err := errs.New(FailToDoSomething{}, cause) fmt.Printf("%v\n", err.Cause()) // Output: // Causal error @@ -74,7 +74,7 @@ func ExampleErr_Error() { cause := errors.New("Causal error") - err := sabi.NewErr(FailToDoSomething{ + err := errs.New(FailToDoSomething{ Param1: "ABC", Param2: 123, }, cause) @@ -89,7 +89,7 @@ func ExampleErr_Get() { Param2 int } - err := sabi.NewErr(FailToDoSomething{ + err := errs.New(FailToDoSomething{ Param1: "ABC", Param2: 123, }) @@ -103,23 +103,35 @@ func ExampleErr_Get() { } func ExampleErr_IsOk() { - err := sabi.Ok() + err := errs.Ok() fmt.Printf("%v\n", err.IsOk()) type FailToDoSomething struct{} - err = sabi.NewErr(FailToDoSomething{}) + err = errs.New(FailToDoSomething{}) fmt.Printf("%v\n", err.IsOk()) // Output: // true // false } +func ExampleErr_IsNotOk() { + err := errs.Ok() + fmt.Printf("%v\n", err.IsNotOk()) + + type FailToDoSomething struct{} + err = errs.New(FailToDoSomething{}) + fmt.Printf("%v\n", err.IsNotOk()) + // Output: + // false + // true +} + func ExampleErr_Reason() { type FailToDoSomething struct { Param1 string } - err := sabi.NewErr(FailToDoSomething{Param1: "value1"}) + err := errs.New(FailToDoSomething{Param1: "value1"}) switch err.Reason().(type) { case FailToDoSomething: fmt.Println("The reason of the error is: FailToDoSomething") @@ -127,7 +139,7 @@ func ExampleErr_Reason() { fmt.Printf("The value of reason.Param1 is: %v\n", reason.Param1) } - err = sabi.NewErr(&FailToDoSomething{Param1: "value2"}) + err = errs.New(&FailToDoSomething{Param1: "value2"}) switch err.Reason().(type) { case *FailToDoSomething: fmt.Println("The reason of the error is: *FailToDoSomething") @@ -144,7 +156,7 @@ func ExampleErr_Reason() { func ExampleErr_ReasonName() { type FailToDoSomething struct{} - err := sabi.NewErr(FailToDoSomething{}) + err := errs.New(FailToDoSomething{}) fmt.Printf("%v\n", err.ReasonName()) // Output: // FailToDoSomething @@ -153,10 +165,10 @@ func ExampleErr_ReasonName() { func ExampleErr_ReasonPackage() { type FailToDoSomething struct{} - err := sabi.NewErr(FailToDoSomething{}) + err := errs.New(FailToDoSomething{}) fmt.Printf("%v\n", err.ReasonPackage()) // Output: - // github.com/sttk/sabi_test + // github.com/sttk/sabi/errs_test } func ExampleErr_Situation() { @@ -165,7 +177,7 @@ func ExampleErr_Situation() { Param2 int } - err := sabi.NewErr(FailToDoSomething{ + err := errs.New(FailToDoSomething{ Param1: "ABC", Param2: 123, }) @@ -180,7 +192,7 @@ func ExampleErr_Unwrap() { cause1 := errors.New("Causal error 1") cause2 := errors.New("Causal error 2") - err := sabi.NewErr(FailToDoSomething{}, cause1) + err := errs.New(FailToDoSomething{}, cause1) fmt.Printf("err.Unwrap() = %v\n", err.Unwrap()) fmt.Printf("errors.Unwrap(err) = %v\n", errors.Unwrap(err)) @@ -200,16 +212,16 @@ func ExampleErr_Unwrap() { func ExampleErr_IfOk() { type FailToDoSomething struct{} - err := sabi.Ok() - err.IfOk(func() sabi.Err { + err := errs.Ok() + err.IfOk(func() errs.Err { fmt.Println("execute if non error.") - return sabi.Ok() + return errs.Ok() }) - err = sabi.NewErr(FailToDoSomething{}) - err.IfOk(func() sabi.Err { + err = errs.New(FailToDoSomething{}) + err.IfOk(func() errs.Err { fmt.Println("not execute if some error.") - return sabi.Ok() + return errs.Ok() }) // Output: // execute if non error. diff --git a/example_notify_test.go b/errs/example_notify_test.go similarity index 52% rename from example_notify_test.go rename to errs/example_notify_test.go index a442ef6..33b6c21 100644 --- a/example_notify_test.go +++ b/errs/example_notify_test.go @@ -1,61 +1,61 @@ -package sabi_test +package errs_test import ( "fmt" "strconv" "time" - "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) -func ExampleAddAsyncErrHandler() { - sabi.AddAsyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) { +func ExampleAddAsyncHandler() { + errs.AddAsyncHandler(func(err errs.Err, occ errs.ErrOcc) { fmt.Println("Asynchronous error handling: " + err.Error()) }) - sabi.FixErrCfgs() + errs.FixCfg() type FailToDoSomething struct{ Name string } - sabi.NewErr(FailToDoSomething{Name: "abc"}) + errs.New(FailToDoSomething{Name: "abc"}) // Output: // Asynchronous error handling: {reason=FailToDoSomething, Name=abc} time.Sleep(100 * time.Millisecond) - sabi.ClearErrHandlers() + errs.ClearErrHandlers() } -func ExampleAddSyncErrHandler() { - sabi.AddSyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) { +func ExampleAddSyncHandler() { + errs.AddSyncHandler(func(err errs.Err, occ errs.ErrOcc) { fmt.Println("Synchronous error handling: " + err.Error()) }) - sabi.FixErrCfgs() + errs.FixCfg() type FailToDoSomething struct{ Name string } - sabi.NewErr(FailToDoSomething{Name: "abc"}) + errs.New(FailToDoSomething{Name: "abc"}) // Output: // Synchronous error handling: {reason=FailToDoSomething, Name=abc} - sabi.ClearErrHandlers() + errs.ClearErrHandlers() } -func ExampleFixErrCfgs() { - sabi.AddSyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) { +func ExampleFixCfg() { + errs.AddSyncHandler(func(err errs.Err, occ errs.ErrOcc) { fmt.Println("This handler is registered at " + occ.File() + ":" + strconv.Itoa(occ.Line())) }) - sabi.FixErrCfgs() + errs.FixCfg() - sabi.AddSyncErrHandler(func(err sabi.Err, occ sabi.ErrOccasion) { + errs.AddSyncHandler(func(err errs.Err, occ errs.ErrOcc) { fmt.Println("This handler is not registered") }) type FailToDoSomething struct{ Name string } - sabi.NewErr(FailToDoSomething{Name: "abc"}) + errs.New(FailToDoSomething{Name: "abc"}) // Output: // This handler is registered at example_notify_test.go:56 - sabi.ClearErrHandlers() + errs.ClearErrHandlers() } diff --git a/errs/notify.go b/errs/notify.go new file mode 100644 index 0000000..cd23593 --- /dev/null +++ b/errs/notify.go @@ -0,0 +1,123 @@ +// Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +package errs + +import ( + "path/filepath" + "runtime" + "time" +) + +// ErrOcc is the struct type that contains time and position in a source +// file when an Err occured. +type ErrOcc struct { + time time.Time + file string + line int +} + +// Time is the method to get time when this Err occured. +func (e ErrOcc) Time() time.Time { + return e.time +} + +// Line is the method to get the line number where this Err occured. +func (e ErrOcc) Line() int { + return e.line +} + +// File is the method to get the file name where this Err occured. +func (e ErrOcc) File() string { + return e.file +} + +type handlerListEntry struct { + handler func(Err, ErrOcc) + next *handlerListEntry +} + +type handlerList struct { + head *handlerListEntry + last *handlerListEntry +} + +var ( + syncErrHandlers = handlerList{nil, nil} + asyncErrHandlers = handlerList{nil, nil} + isErrCfgFixed = false +) + +// AddSyncHandler is the function that adds an Err creation event handler. +// Handlers added with this method are executed synchronously in the order of +// addition. +func AddSyncHandler(handler func(Err, ErrOcc)) { + if isErrCfgFixed { + return + } + + last := syncErrHandlers.last + syncErrHandlers.last = &handlerListEntry{handler, nil} + + if last != nil { + last.next = syncErrHandlers.last + } + + if syncErrHandlers.head == nil { + syncErrHandlers.head = syncErrHandlers.last + } +} + +// AddAsyncHandler is the function that adds an Err creation event handler. +// Handlers added with this method are executed asynchronously. +func AddAsyncHandler(handler func(Err, ErrOcc)) { + if isErrCfgFixed { + return + } + + last := asyncErrHandlers.last + asyncErrHandlers.last = &handlerListEntry{handler, nil} + + if last != nil { + last.next = asyncErrHandlers.last + } + + if asyncErrHandlers.head == nil { + asyncErrHandlers.head = asyncErrHandlers.last + } +} + +// FixCfg is the function to fix the configuration of error processing. +// After calling this function, handlers cannot be added any more and the +// notification becomes effective. +func FixCfg() { + isErrCfgFixed = true +} + +func notifyErr(err Err) { + if !isErrCfgFixed { + return + } + + if syncErrHandlers.head == nil && asyncErrHandlers.head == nil { + return + } + + var occ ErrOcc + occ.time = time.Now() + + _, file, line, ok := runtime.Caller(2) + if ok { + occ.file = filepath.Base(file) + occ.line = line + } + + for el := syncErrHandlers.head; el != nil; el = el.next { + el.handler(err, occ) + } + + for el := asyncErrHandlers.head; el != nil; el = el.next { + go el.handler(err, occ) + } +} diff --git a/notify_test.go b/errs/notify_test.go similarity index 57% rename from notify_test.go rename to errs/notify_test.go index ec8d58d..f0f26ce 100644 --- a/notify_test.go +++ b/errs/notify_test.go @@ -1,30 +1,28 @@ -package sabi +package errs import ( "container/list" + "fmt" "reflect" - "strconv" "testing" "time" "github.com/stretchr/testify/assert" ) -type ReasonForNotification struct{} - func ClearErrHandlers() { syncErrHandlers.head = nil syncErrHandlers.last = nil asyncErrHandlers.head = nil asyncErrHandlers.last = nil - isErrCfgsFixed = false + isErrCfgFixed = false } -func TestAddErrSyncHandler_oneHandler(t *testing.T) { +func TestAddSyncHandler_oneHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - AddSyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddSyncHandler(func(e Err, o ErrOcc) {}) assert.NotNil(t, syncErrHandlers.head) assert.NotNil(t, syncErrHandlers.last) @@ -34,15 +32,15 @@ func TestAddErrSyncHandler_oneHandler(t *testing.T) { assert.Nil(t, syncErrHandlers.head.next) assert.NotNil(t, syncErrHandlers.head.handler) - assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") + assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.handler).String(), "func(errs.Err, errs.ErrOcc)") } -func TestAddErrSyncHandler_twoHandlers(t *testing.T) { +func TestAddSyncHandler_twoHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - AddSyncErrHandler(func(err Err, occ ErrOccasion) {}) - AddSyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddSyncHandler(func(e Err, o ErrOcc) {}) + AddSyncHandler(func(e Err, o ErrOcc) {}) assert.NotNil(t, syncErrHandlers.head) assert.NotNil(t, syncErrHandlers.last) @@ -52,25 +50,17 @@ func TestAddErrSyncHandler_twoHandlers(t *testing.T) { assert.Nil(t, syncErrHandlers.last.next) assert.NotNil(t, syncErrHandlers.head.handler) - assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") + assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.handler).String(), "func(errs.Err, errs.ErrOcc)") assert.NotNil(t, syncErrHandlers.head.next.handler) - assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.next.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") -} - -func TestAddErrAsyncHandler_zeroHandler(t *testing.T) { - ClearErrHandlers() - defer ClearErrHandlers() - - assert.Nil(t, asyncErrHandlers.head) - assert.Nil(t, asyncErrHandlers.last) + assert.Equal(t, reflect.TypeOf(syncErrHandlers.head.next.handler).String(), "func(errs.Err, errs.ErrOcc)") } -func TestAddErrAsyncHandler_oneHandler(t *testing.T) { +func TestAddAsyncHandler_oneHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - AddAsyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddAsyncHandler(func(e Err, o ErrOcc) {}) assert.NotNil(t, asyncErrHandlers.head) assert.NotNil(t, asyncErrHandlers.last) @@ -80,15 +70,15 @@ func TestAddErrAsyncHandler_oneHandler(t *testing.T) { assert.Nil(t, asyncErrHandlers.head.next) assert.NotNil(t, asyncErrHandlers.head.handler) - assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") + assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.handler).String(), "func(errs.Err, errs.ErrOcc)") } -func TestAddErrAsyncHandler_twoHandlers(t *testing.T) { +func TestAddAsyncHandler_twoHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - AddAsyncErrHandler(func(err Err, occ ErrOccasion) {}) - AddAsyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddAsyncHandler(func(e Err, o ErrOcc) {}) + AddAsyncHandler(func(e Err, o ErrOcc) {}) assert.NotNil(t, asyncErrHandlers.head) assert.NotNil(t, asyncErrHandlers.last) @@ -98,18 +88,18 @@ func TestAddErrAsyncHandler_twoHandlers(t *testing.T) { assert.Nil(t, asyncErrHandlers.last.next) assert.NotNil(t, asyncErrHandlers.head.handler) - assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") + assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.handler).String(), "func(errs.Err, errs.ErrOcc)") assert.NotNil(t, asyncErrHandlers.head.next.handler) - assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.next.handler).String(), "func(sabi.Err, sabi.ErrOccasion)") + assert.Equal(t, reflect.TypeOf(asyncErrHandlers.head.next.handler).String(), "func(errs.Err, errs.ErrOcc)") } -func TestFixErrCfgs(t *testing.T) { +func TestFixCfg(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - AddSyncErrHandler(func(err Err, occ ErrOccasion) {}) - AddAsyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddSyncHandler(func(err Err, occ ErrOcc) {}) + AddAsyncHandler(func(err Err, occ ErrOcc) {}) assert.NotNil(t, syncErrHandlers.head) assert.NotNil(t, syncErrHandlers.last) @@ -125,14 +115,14 @@ func TestFixErrCfgs(t *testing.T) { assert.Nil(t, asyncErrHandlers.head.next) assert.Nil(t, asyncErrHandlers.last.next) - assert.False(t, isErrCfgsFixed) + assert.False(t, isErrCfgFixed) - FixErrCfgs() + FixCfg() - assert.True(t, isErrCfgsFixed) + assert.True(t, isErrCfgFixed) - AddSyncErrHandler(func(err Err, occ ErrOccasion) {}) - AddAsyncErrHandler(func(err Err, occ ErrOccasion) {}) + AddSyncHandler(func(err Err, occ ErrOcc) {}) + AddAsyncHandler(func(err Err, occ ErrOcc) {}) assert.NotNil(t, syncErrHandlers.head) assert.NotNil(t, syncErrHandlers.last) @@ -153,62 +143,75 @@ func TestNotifyErr_withNoErrHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() - NewErr(ReasonForNotification{}) + type ReasonForNotification struct{} - assert.False(t, isErrCfgsFixed) + New(ReasonForNotification{}) - FixErrCfgs() + assert.False(t, isErrCfgFixed) - assert.True(t, isErrCfgsFixed) + FixCfg() - NewErr(ReasonForNotification{}) + assert.True(t, isErrCfgFixed) + + New(ReasonForNotification{}) } -func TestNotifyErr_withHandlers(t *testing.T) { +func TestNotifyErr_withErrHandler(t *testing.T) { ClearErrHandlers() defer ClearErrHandlers() syncLogs := list.New() asyncLogs := list.New() - AddSyncErrHandler(func(err Err, occ ErrOccasion) { - syncLogs.PushBack( - err.ReasonName() + "-1:" + occ.File() + ":" + strconv.Itoa(occ.Line())) - occ.Time() + type ReasonForNotification struct{} + + AddSyncHandler(func(e Err, o ErrOcc) { + syncLogs.PushBack(fmt.Sprintf("%s-1:%s:%d:%s", + e.ReasonName(), o.File(), o.Line(), o.Time().String())) + }) + AddSyncHandler(func(e Err, o ErrOcc) { + syncLogs.PushBack(fmt.Sprintf("%s-2:%s:%d:%s", + e.ReasonName(), o.File(), o.Line(), o.Time().String())) }) - AddSyncErrHandler(func(err Err, occ ErrOccasion) { - syncLogs.PushBack( - err.ReasonName() + "-2:" + occ.File() + ":" + strconv.Itoa(occ.Line())) - occ.Time() + AddAsyncHandler(func(e Err, o ErrOcc) { + time.Sleep(100 * time.Millisecond) + asyncLogs.PushBack(fmt.Sprintf("%s-3:%s:%d:%s", + e.ReasonName(), o.File(), o.Line(), o.Time().String())) }) - AddAsyncErrHandler(func(err Err, occ ErrOccasion) { - asyncLogs.PushBack( - err.ReasonName() + "-3:" + occ.File() + ":" + strconv.Itoa(occ.Line())) - occ.Time() + AddAsyncHandler(func(e Err, o ErrOcc) { + time.Sleep(10 * time.Millisecond) + asyncLogs.PushBack(fmt.Sprintf("%s-4:%s:%d:%s", + e.ReasonName(), o.File(), o.Line(), o.Time().String())) }) - NewErr(ReasonForNotification{}) + assert.False(t, isErrCfgFixed) - assert.False(t, isErrCfgsFixed) + New(ReasonForNotification{}) assert.Equal(t, syncLogs.Len(), 0) assert.Equal(t, asyncLogs.Len(), 0) - FixErrCfgs() + FixCfg() - NewErr(ReasonForNotification{}) + assert.True(t, isErrCfgFixed) - assert.True(t, isErrCfgsFixed) + New(ReasonForNotification{}) assert.Equal(t, syncLogs.Len(), 2) - assert.Equal(t, syncLogs.Front().Value, - "ReasonForNotification-1:notify_test.go:199") - assert.Equal(t, syncLogs.Front().Next().Value, - "ReasonForNotification-2:notify_test.go:199") - - time.Sleep(100 * time.Millisecond) - - assert.Equal(t, asyncLogs.Len(), 1) - assert.Equal(t, asyncLogs.Front().Value, - "ReasonForNotification-3:notify_test.go:199") + log := syncLogs.Front() + assert.Contains(t, log.Value, "ReasonForNotification-1:notify_test.go:198:") + log = log.Next() + assert.Contains(t, log.Value, "ReasonForNotification-2:notify_test.go:198:") + log = log.Next() + assert.Nil(t, log) + + time.Sleep(500 * time.Millisecond) + + assert.Equal(t, asyncLogs.Len(), 2) + log = asyncLogs.Front() + assert.Contains(t, log.Value, "ReasonForNotification-4:notify_test.go:198:") + log = log.Next() + assert.Contains(t, log.Value, "ReasonForNotification-3:notify_test.go:198:") + log = log.Next() + assert.Nil(t, log) } diff --git a/example_async-group_test.go b/example_async-group_test.go new file mode 100644 index 0000000..3aa84d7 --- /dev/null +++ b/example_async-group_test.go @@ -0,0 +1,43 @@ +package sabi_test + +import ( + "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" +) + +type SyncDaxSrc struct{} + +func (ds SyncDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + // ... + return errs.Ok() +} + +func (ds SyncDaxSrc) Close() {} +func (ds SyncDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() +} + +type AsyncDaxSrc struct{} + +func (ds AsyncDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + ag.Add(func() errs.Err { + // ... + return errs.Ok() + }) + return errs.Ok() +} + +func (ds AsyncDaxSrc) Close() {} +func (ds AsyncDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() +} + +func ExampleAsyncGroup() { + sabi.Uses("sync", SyncDaxSrc{}) + sabi.Uses("async", AsyncDaxSrc{}) + + err := sabi.Setup() + if err.IsNotOk() { + return + } +} diff --git a/example_dax_test.go b/example_dax_test.go index 131d787..6c8f113 100644 --- a/example_dax_test.go +++ b/example_dax_test.go @@ -1,190 +1,264 @@ package sabi_test import ( - "fmt" - "reflect" + "os" "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) -func ExampleAddGlobalDaxSrc() { - sabi.AddGlobalDaxSrc("hoge", NewMapDaxSrc()) +/// - if sabi.StartUpGlobalDaxSrcs().IsNotOk() { - return - } - defer sabi.ShutdownGlobalDaxSrcs() +type CliArgDaxSrc struct{} - base := sabi.NewDaxBase() +func (ds CliArgDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + return errs.Ok() +} +func (ds CliArgDaxSrc) Close() {} +func (ds CliArgDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() +} - type MyDax struct { - sabi.Dax - } +func NewCliArgDaxSrc(osArgs []string) CliArgDaxSrc { + return CliArgDaxSrc{} +} - dax := MyDax{Dax: base} +type DatabaseDaxSrc struct{} - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - fmt.Printf("conn = %v\n", reflect.TypeOf(conn)) - fmt.Printf("err.IsOk() = %t\n", err.IsOk()) +func (ds DatabaseDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + return errs.Ok() +} +func (ds DatabaseDaxSrc) Close() {} +func (ds DatabaseDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() +} - // Output: - // conn = sabi_test.MapDaxConn - // err.IsOk() = true +func NewDatabaseDaxSrc(driverName, dataSourceName string) DatabaseDaxSrc { + return DatabaseDaxSrc{} +} + +var ( + driverName string + dataSourceName string +) - ClearDaxBase() +type HttpRequestDaxSrc struct{} + +func (ds HttpRequestDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + return errs.Ok() +} +func (ds HttpRequestDaxSrc) Close() {} +func (ds HttpRequestDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() } -func ExampleStartUpGlobalDaxSrcs() { - sabi.AddGlobalDaxSrc("hoge", NewMapDaxSrc()) +func NewHttpRequestDaxSrc(req any) HttpRequestDaxSrc { + return HttpRequestDaxSrc{} +} - if err := sabi.StartUpGlobalDaxSrcs(); err.IsNotOk() { - return - } - defer sabi.ShutdownGlobalDaxSrcs() +var req any - sabi.AddGlobalDaxSrc("fuga", NewMapDaxSrc()) +type HttpResponseDaxSrc struct{} - base := sabi.NewDaxBase() +func (ds HttpResponseDaxSrc) Setup(ag sabi.AsyncGroup) errs.Err { + return errs.Ok() +} +func (ds HttpResponseDaxSrc) Close() {} +func (ds HttpResponseDaxSrc) CreateDaxConn() (sabi.DaxConn, errs.Err) { + return nil, errs.Ok() +} - type MyDax struct { - sabi.Dax - } +func NewHttpResponseDaxSrc(resp any) HttpResponseDaxSrc { + return HttpResponseDaxSrc{} +} - dax := MyDax{Dax: base} +var resp any - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - fmt.Printf("conn = %v\n", reflect.TypeOf(conn)) - fmt.Printf("err.IsOk() = %v\n", err.IsOk()) +type CliArgOptionDax struct { + sabi.Dax +} - conn, err = sabi.GetDaxConn[MapDaxConn](dax, "fuga") - fmt.Printf("conn = %v\n", reflect.TypeOf(conn)) - fmt.Printf("err.IsOk() = %t\n", err.IsOk()) - fmt.Printf("err.Error() = %s\n", err.Error()) +type DatabaseSetDax struct { + sabi.Dax +} - // Output: - // conn = sabi_test.MapDaxConn - // err.IsOk() = true - // conn = sabi_test.MapDaxConn - // err.IsOk() = false - // err.Error() = {reason=DaxSrcIsNotFound, Name=fuga} +type HttpReqParamDax struct { + sabi.Dax +} - ClearDaxBase() +type HttpRespOutputDax struct { + sabi.Dax } -func ExampleDaxBase_SetUpLocalDaxSrc() { - base := sabi.NewDaxBase() - defer base.FreeAllLocalDaxSrcs() +/// - base.SetUpLocalDaxSrc("hoge", NewMapDaxSrc()) +func ExampleClose() { + sabi.Uses("cliargs", NewCliArgDaxSrc(os.Args)) - type MyDax struct { - sabi.Dax + err := sabi.Setup() + if err.IsNotOk() { + // ... } + defer sabi.Close() - dax := MyDax{Dax: base} + // ... +} - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - fmt.Printf("conn = %v\n", reflect.TypeOf(conn)) - fmt.Printf("err.IsOk() = %v\n", err.IsOk()) +func ExampleSetup() { + sabi.Uses("cliargs", NewCliArgDaxSrc(os.Args)) + sabi.Uses("database", NewDatabaseDaxSrc(driverName, dataSourceName)) - // Output: - // conn = sabi_test.MapDaxConn - // err.IsOk() = true + err := sabi.Setup() + if err.IsNotOk() { + // ... + } + defer sabi.Close() - ClearDaxBase() + // ... } -type GettingDax struct { - sabi.Dax -} +func ExampleStartApp() { + sabi.Uses("cliargs", NewCliArgDaxSrc(os.Args)) + sabi.Uses("database", NewDatabaseDaxSrc(driverName, dataSourceName)) -func (dax GettingDax) GetData() (string, sabi.Err) { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "hoge") - if !err.IsOk() { - return "", err + app := func() errs.Err { + // ... + return errs.Ok() + } + + err := sabi.StartApp(app) + if err.IsNotOk() { + // ... } - data := conn.dataMap["hogehoge"] - return data, err } -type SettingDax struct { - sabi.Dax +func ExampleUses() { + sabi.Uses("cliargs", NewCliArgDaxSrc(os.Args)) + sabi.Uses("database", NewDatabaseDaxSrc(driverName, dataSourceName)) + + err := sabi.StartApp(func() errs.Err { + // ... + return errs.Ok() + }) + if err.IsNotOk() { + // ... + } + // ... } -func (dax SettingDax) SetData(data string) sabi.Err { - conn, err := sabi.GetDaxConn[MapDaxConn](dax, "fuga") - if !err.IsOk() { - return err - } - conn.dataMap["fugafuga"] = data - return err -} - -func ExampleDax() { - // type GettingDax struct { - // sabi.Dax - // } - // func (dax GettingDax) GetData() (string, sabi.Err) { - // conn, err := dax.getDaxConn("hoge") - // if !err.IsOk() { - // return nil, err - // } - // return conn.(MapDaxConn).dataMap["hogehoge"], err - // } - // - // type SettingDax struct { - // sabi.Dax - // } - // func (dax SettingDax) SetData(data string) sabi.Err { - // conn, err := dax.getDaxConn("fuga") - // if !err.IsOk() { - // return nil, err - // } - // conn.(MapDaxConn).dataMap["fugafuga"] = data - // return err - // } - - hogeDs := NewMapDaxSrc() - fugaDs := NewMapDaxSrc() +func ExampleDaxBase() { + sabi.Uses("cliargs", NewCliArgDaxSrc(os.Args)) + sabi.Uses("database", NewDatabaseDaxSrc(driverName, dataSourceName)) - base := sabi.NewDaxBase() - defer base.FreeAllLocalDaxSrcs() + err := sabi.Setup() + if err.IsNotOk() { + // ... + } + defer sabi.Close() + + NewMyBase := func() sabi.DaxBase { + base := sabi.NewDaxBase() + return &struct { + sabi.DaxBase + CliArgOptionDax + DatabaseSetDax + HttpReqParamDax + HttpRespOutputDax + }{ + DaxBase: base, + CliArgOptionDax: CliArgOptionDax{Dax: base}, + DatabaseSetDax: DatabaseSetDax{Dax: base}, + HttpReqParamDax: HttpReqParamDax{Dax: base}, + HttpRespOutputDax: HttpRespOutputDax{Dax: base}, + } + } - if err := base.SetUpLocalDaxSrc("hoge", hogeDs); err.IsNotOk() { - return + type GetSetDax struct { + sabi.Dax + // ... + } + + GetSetLogic := func(dax GetSetDax) errs.Err { + // ... + return errs.Ok() } - if err := base.SetUpLocalDaxSrc("fuga", fugaDs); err.IsNotOk() { - return + + type OutputDax struct { + sabi.Dax + // ... } - base = struct { + OutputLogic := func(dax OutputDax) errs.Err { + // ... + return errs.Ok() + } + + base := NewMyBase() + defer base.Close() + + err = base.Uses("httpReq", NewHttpRequestDaxSrc(req)). + IfOk(sabi.Txn_(base, GetSetLogic)). + IfOk(base.Disuses_("httpReq")). + IfOk(base.Uses_("httpResp", NewHttpResponseDaxSrc(resp))). + IfOk(sabi.Txn_(base, OutputLogic)) + if err.IsNotOk() { + // ... + } +} + +func ExampleNewDaxBase() { + base0 := sabi.NewDaxBase() + + base := &struct { + sabi.DaxBase + CliArgOptionDax + DatabaseSetDax + HttpReqParamDax + HttpRespOutputDax + }{ + DaxBase: base0, + CliArgOptionDax: CliArgOptionDax{Dax: base0}, + DatabaseSetDax: DatabaseSetDax{Dax: base0}, + HttpReqParamDax: HttpReqParamDax{Dax: base0}, + HttpRespOutputDax: HttpRespOutputDax{Dax: base0}, + } + // Output: + _ = base +} + +func NewMyBase() sabi.DaxBase { + base := sabi.NewDaxBase() + return &struct { sabi.DaxBase - GettingDax - SettingDax + CliArgOptionDax + DatabaseSetDax + HttpReqParamDax + HttpRespOutputDax }{ - DaxBase: base, - GettingDax: GettingDax{Dax: base}, - SettingDax: SettingDax{Dax: base}, + DaxBase: base, + CliArgOptionDax: CliArgOptionDax{Dax: base}, + DatabaseSetDax: DatabaseSetDax{Dax: base}, + HttpReqParamDax: HttpReqParamDax{Dax: base}, + HttpRespOutputDax: HttpRespOutputDax{Dax: base}, } +} + +func ExampleTxn() { + base := NewMyBase() + defer base.Close() - type DaxForLogic interface { - GetData() (string, sabi.Err) - SetData(data string) sabi.Err + type GetSetDax struct { + sabi.Dax + // ... } - hogeDs.dataMap["hogehoge"] = "hello" - err := sabi.RunTxn(base, func(dax DaxForLogic) sabi.Err { - data, err := dax.GetData() - if !err.IsOk() { - return err - } - err = dax.SetData(data) - return err - }) - fmt.Printf("%t\n", err.IsOk()) - fmt.Printf("%s\n", fugaDs.dataMap["fugafuga"]) + GetSetLogic := func(dax GetSetDax) errs.Err { + // ... + return errs.Ok() + } - // Output: - // true - // hello + err := sabi.Txn(base, GetSetLogic) + if err.IsNotOk() { + // ... + } } diff --git a/example_runner_test.go b/example_runner_test.go index fafd13c..4738dc2 100644 --- a/example_runner_test.go +++ b/example_runner_test.go @@ -2,78 +2,67 @@ package sabi_test import ( "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) -func unused(v any) {} - -type BazDax interface { - GetData() string - SetData(data string) -} - -type bazDaxImpl struct { -} - -func (dax bazDaxImpl) GetData() string { - return "" -} -func (dax bazDaxImpl) SetData(data string) { -} - -var base0 = sabi.NewDaxBase() - -var base = struct { - sabi.DaxBase - bazDaxImpl -}{ - DaxBase: base0, - bazDaxImpl: bazDaxImpl{}, -} - -func ExamplePara() { - txn1 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - txn2 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - - paraRunner := sabi.Para(txn1, txn2) - - err := paraRunner() - - // Output: - - unused(err) +func NewMyDaxBase() sabi.DaxBase { + return sabi.NewDaxBase() } func ExampleSeq() { - txn1 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - txn2 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - - seqRunner := sabi.Seq(txn1, txn2) - - err := seqRunner() - - // Output: - - unused(err) -} - -func ExampleRunPara() { - txn1 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - txn2 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - - err := sabi.RunPara(txn1, txn2) - - // Output: - - unused(err) + type FooDax interface { + sabi.Dax + // ... + } + + type BarDax interface { + sabi.Dax + // ... + } + + base := NewMyDaxBase() + + txn1 := sabi.Txn_[FooDax](base, func(dax FooDax) errs.Err { + // ... + return errs.Ok() + }) + + txn2 := sabi.Txn_[BarDax](base, func(dax BarDax) errs.Err { + // ... + return errs.Ok() + }) + + err := sabi.Seq(txn1, txn2) + if err.IsNotOk() { + // ... + } } -func ExampleRunSeq() { - txn1 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - txn2 := sabi.Txn(base, func(dax BazDax) sabi.Err { return sabi.Ok() }) - - err := sabi.RunSeq(txn1, txn2) - - // Output: - - unused(err) +func ExamplePara() { + type FooDax interface { + sabi.Dax + // ... + } + + type BarDax interface { + sabi.Dax + // ... + } + + base := NewMyDaxBase() + + txn1 := sabi.Txn_[FooDax](base, func(dax FooDax) errs.Err { + // ... + return errs.Ok() + }) + + txn2 := sabi.Txn_[BarDax](base, func(dax BarDax) errs.Err { + // ... + return errs.Ok() + }) + + err := sabi.Para(txn1, txn2) + if err.IsNotOk() { + // ... + } } diff --git a/go.mod b/go.mod index f00827e..553d7a9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/sttk/sabi go 1.18 -require github.com/stretchr/testify v1.8.4 +require ( + github.com/stretchr/testify v1.8.4 + github.com/sttk/orderedmap v1.0.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index fa4b6e6..4eb1534 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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= +github.com/sttk/orderedmap v1.0.0 h1:AiCoq2yLwRRGvjOWpjqVW1KUQqY9xg0rIRXvvhElkqY= +github.com/sttk/orderedmap v1.0.0/go.mod h1:EThpedtCCo30avJK4uZ6YBolOuT11DE+OJoHr8Vi0yU= 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= diff --git a/notify.go b/notify.go deleted file mode 100644 index 8822770..0000000 --- a/notify.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. -// This program is free software under MIT License. -// See the file LICENSE in this distribution for more details. - -package sabi - -import ( - "path/filepath" - "runtime" - "sync" - "time" -) - -// ErrOccasion is a struct which contains time and posision in a source file -// when an Err occured. -type ErrOccasion struct { - time time.Time - file string - line int -} - -// Time is a method which returns time when this Err occured. -func (e ErrOccasion) Time() time.Time { - return e.time -} - -// File is a method which returns the file name where this Err occured. -func (e ErrOccasion) File() string { - return e.file -} - -// Line is a method which returns the line number where this Err occured. -func (e ErrOccasion) Line() int { - return e.line -} - -type handlerListElem struct { - handler func(Err, ErrOccasion) - next *handlerListElem -} - -type handlerList struct { - head *handlerListElem - last *handlerListElem -} - -var ( - syncErrHandlers = handlerList{nil, nil} - asyncErrHandlers = handlerList{nil, nil} - isErrCfgsFixed = false - errCfgMutex = sync.Mutex{} -) - -// Adds an Err creation event handler which is executed synchronously. -// Handlers added with this method are executed in the order of addition. -func AddSyncErrHandler(handler func(Err, ErrOccasion)) { - errCfgMutex.Lock() - defer errCfgMutex.Unlock() - - if isErrCfgsFixed { - return - } - - last := syncErrHandlers.last - syncErrHandlers.last = &handlerListElem{handler, nil} - - if last != nil { - last.next = syncErrHandlers.last - } - - if syncErrHandlers.head == nil { - syncErrHandlers.head = syncErrHandlers.last - } -} - -// Adds a Err creation event handlers which is executed asynchronously. -func AddAsyncErrHandler(handler func(Err, ErrOccasion)) { - errCfgMutex.Lock() - defer errCfgMutex.Unlock() - - if isErrCfgsFixed { - return - } - - last := asyncErrHandlers.last - asyncErrHandlers.last = &handlerListElem{handler, nil} - - if last != nil { - last.next = asyncErrHandlers.last - } - - if asyncErrHandlers.head == nil { - asyncErrHandlers.head = asyncErrHandlers.last - } -} - -// Fixes configuration for Err creation event handlers. -// After calling this function, handlers cannot be registered any more and the -// notification becomes effective. -func FixErrCfgs() { - isErrCfgsFixed = true -} - -func notifyErr(err Err) { - if !isErrCfgsFixed { - return - } - - if syncErrHandlers.head == nil && asyncErrHandlers.head == nil { - return - } - - var occ ErrOccasion - occ.time = time.Now() - - _, file, line, ok := runtime.Caller(2) - if ok { - occ.file = filepath.Base(file) - occ.line = line - } - - for el := syncErrHandlers.head; el != nil; el = el.next { - el.handler(err, occ) - } - - if asyncErrHandlers.head != nil { - for el := asyncErrHandlers.head; el != nil; el = el.next { - go el.handler(err, occ) - } - } -} diff --git a/runner.go b/runner.go index ecec2bf..e2e0ccd 100644 --- a/runner.go +++ b/runner.go @@ -1,74 +1,63 @@ -// Copyright (C) 2022 Takayuki Sato. All Rights Reserved. +// Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. // This program is free software under MIT License. // See the file LICENSE in this distribution for more details. package sabi +import ( + "github.com/sttk/sabi/errs" +) + type /* error reasons */ ( - // FailToRunInParallel is an error reason which indicates some runner - // functions which run in parallel failed. + // FailToRunInParallel is an error reason which indicates that some of runner + // functions running in parallel failed. FailToRunInParallel struct { - Errors map[int]Err + Errors map[int]errs.Err } ) -// RunSeq is a function which runs specified runner functions sequencially. -func RunSeq(runners ...func() Err) Err { +// Seq is the function which runs argument functions sequencially. +func Seq(runners ...func() errs.Err) errs.Err { for _, runner := range runners { err := runner() if err.IsNotOk() { return err } } - return Ok() -} -// Seq is a function which creates a runner function which runs multiple -// runner functions specified as arguments sequencially. -func Seq(runners ...func() Err) func() Err { - return func() Err { - return RunSeq(runners...) - } + return errs.Ok() } -type indexedErr struct { - index int - err Err +// Seq_ is the function which creates a runner function which runs Seq +// function. +func Seq_(runners ...func() errs.Err) func() errs.Err { + return func() errs.Err { + return Seq(runners...) + } } -func RunPara(runners ...func() Err) Err { - ch := make(chan indexedErr) +// Para is the function which runs argument functions in parallel. +func Para(runners ...func() errs.Err) errs.Err { + var ag asyncGroupAsync[int] - for i, r := range runners { - go func(index int, runner func() Err, ch chan indexedErr) { - err := runner() - ie := indexedErr{index: index, err: err} - ch <- ie - }(i, r, ch) + for i, runner := range runners { + ag.name = i + ag.Add(runner) } - errs := make(map[int]Err) - n := len(runners) - for i := 0; i < n; i++ { - select { - case ie := <-ch: - if ie.err.IsNotOk() { - errs[ie.index] = ie.err - } - } - } + ag.wait() - if len(errs) > 0 { - return NewErr(FailToRunInParallel{Errors: errs}) + if ag.hasErr() { + return errs.New(FailToRunInParallel{Errors: ag.makeErrs()}) } - return Ok() + return errs.Ok() } -// Para is a function which creates a runner function which runs multiple -// runner functions specified as arguments in parallel. -func Para(runners ...func() Err) func() Err { - return func() Err { - return RunPara(runners...) +// Para_ is the function which creates a runner function which runs Para +// function. +func Para_(runners ...func() errs.Err) func() errs.Err { + return func() errs.Err { + return Para(runners...) } } diff --git a/runner_test.go b/runner_test.go index c3b8050..f4f0d44 100644 --- a/runner_test.go +++ b/runner_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/sttk/sabi" + "github.com/sttk/sabi/errs" ) var runnerLogs list.List @@ -16,23 +17,23 @@ func clearRunnerLogs() { runnerLogs.Init() } -func TestRunSeq(t *testing.T) { +func TestSeq(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - err := sabi.RunSeq(slowerRunner, fasterRunner) + err := sabi.Seq(slowerRunner, fasterRunner) assert.True(t, err.IsOk()) log := runnerLogs.Front() @@ -43,24 +44,24 @@ func TestRunSeq(t *testing.T) { assert.Nil(t, log) } -func TestRunSeq_failToRunFormerRunner(t *testing.T) { +func TestSeq_failToRunFormerRunner(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() type FailToRun struct{} - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) - return sabi.NewErr(FailToRun{}) + return errs.New(FailToRun{}) } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - err := sabi.RunSeq(slowerRunner, fasterRunner) + err := sabi.Seq(slowerRunner, fasterRunner) assert.True(t, err.IsNotOk()) switch err.Reason().(type) { case FailToRun: @@ -78,18 +79,18 @@ func TestRunSeq_failToRunLatterRunner(t *testing.T) { type FailToRun struct{} - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) - return sabi.NewErr(FailToRun{}) + return errs.New(FailToRun{}) } - err := sabi.RunSeq(slowerRunner, fasterRunner) + err := sabi.Seq(slowerRunner, fasterRunner) assert.True(t, err.IsNotOk()) switch err.Reason().(type) { case FailToRun: @@ -103,23 +104,23 @@ func TestRunSeq_failToRunLatterRunner(t *testing.T) { assert.Nil(t, log) } -func TestSeq(t *testing.T) { +func TestSeq_runner(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - runner := sabi.Seq(slowerRunner, fasterRunner) + runner := sabi.Seq_(slowerRunner, fasterRunner) err := runner() assert.True(t, err.IsOk()) @@ -131,85 +132,23 @@ func TestSeq(t *testing.T) { assert.Nil(t, log) } -func TestSeq_failToRunFormerRunner(t *testing.T) { - clearRunnerLogs() - defer clearRunnerLogs() - - type FailToRun struct{} - - slowerRunner := func() sabi.Err { - time.Sleep(50 * time.Millisecond) - return sabi.NewErr(FailToRun{}) - } - - fasterRunner := func() sabi.Err { - time.Sleep(10 * time.Millisecond) - runnerLogs.PushBack("faster runner.") - return sabi.Ok() - } - - runner := sabi.Seq(slowerRunner, fasterRunner) - err := runner() - assert.True(t, err.IsNotOk()) - switch err.Reason().(type) { - case FailToRun: - default: - assert.Fail(t, err.Error()) - } - - log := runnerLogs.Front() - assert.Nil(t, log) -} - -func TestSeq_failToRunLatterRunner(t *testing.T) { - clearRunnerLogs() - defer clearRunnerLogs() - - type FailToRun struct{} - - slowerRunner := func() sabi.Err { - time.Sleep(50 * time.Millisecond) - runnerLogs.PushBack("slower runner.") - return sabi.Ok() - } - - fasterRunner := func() sabi.Err { - time.Sleep(10 * time.Millisecond) - return sabi.NewErr(FailToRun{}) - } - - runner := sabi.Seq(slowerRunner, fasterRunner) - err := runner() - assert.True(t, err.IsNotOk()) - switch err.Reason().(type) { - case FailToRun: - default: - assert.Fail(t, err.Error()) - } - - log := runnerLogs.Front() - assert.Equal(t, log.Value, "slower runner.") - log = log.Next() - assert.Nil(t, log) -} - -func TestRunPara(t *testing.T) { +func TestPara(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - err := sabi.RunPara(slowerRunner, fasterRunner) + err := sabi.Para(slowerRunner, fasterRunner) assert.True(t, err.IsOk()) log := runnerLogs.Front() @@ -220,24 +159,24 @@ func TestRunPara(t *testing.T) { assert.Nil(t, log) } -func TestRunPara_failToRunFormerRunner(t *testing.T) { +func TestPara_failToRunFormerRunner(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() type FailToRun struct{} - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) - return sabi.NewErr(FailToRun{}) + return errs.New(FailToRun{}) } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - err := sabi.RunPara(slowerRunner, fasterRunner) + err := sabi.Para(slowerRunner, fasterRunner) assert.True(t, err.IsNotOk()) switch err.Reason().(type) { case sabi.FailToRunInParallel: @@ -258,24 +197,24 @@ func TestRunPara_failToRunFormerRunner(t *testing.T) { assert.Nil(t, log) } -func TestRunPara_failToRunLatterRunner(t *testing.T) { +func TestPara_failToRunLatterRunner(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() type FailToRun struct{} - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) - return sabi.NewErr(FailToRun{}) + return errs.New(FailToRun{}) } - err := sabi.RunPara(slowerRunner, fasterRunner) + err := sabi.Para(slowerRunner, fasterRunner) assert.True(t, err.IsNotOk()) switch err.Reason().(type) { case sabi.FailToRunInParallel: @@ -296,23 +235,23 @@ func TestRunPara_failToRunLatterRunner(t *testing.T) { assert.Nil(t, log) } -func TestPara(t *testing.T) { +func TestPara_runner(t *testing.T) { clearRunnerLogs() defer clearRunnerLogs() - slowerRunner := func() sabi.Err { + slowerRunner := func() errs.Err { time.Sleep(50 * time.Millisecond) runnerLogs.PushBack("slower runner.") - return sabi.Ok() + return errs.Ok() } - fasterRunner := func() sabi.Err { + fasterRunner := func() errs.Err { time.Sleep(10 * time.Millisecond) runnerLogs.PushBack("faster runner.") - return sabi.Ok() + return errs.Ok() } - runner := sabi.Para(slowerRunner, fasterRunner) + runner := sabi.Para_(slowerRunner, fasterRunner) err := runner() assert.True(t, err.IsOk()) @@ -323,108 +262,3 @@ func TestPara(t *testing.T) { log = log.Next() assert.Nil(t, log) } - -func TestPara_failToRunSlowerRunner(t *testing.T) { - clearRunnerLogs() - defer clearRunnerLogs() - - type FailToRun struct{} - - slowerRunner := func() sabi.Err { - time.Sleep(50 * time.Millisecond) - return sabi.NewErr(FailToRun{}) - } - - fasterRunner := func() sabi.Err { - time.Sleep(10 * time.Millisecond) - runnerLogs.PushBack("faster runner.") - return sabi.Ok() - } - - runner := sabi.Para(slowerRunner, fasterRunner) - err := runner() - assert.True(t, err.IsNotOk()) - switch err.Reason().(type) { - case sabi.FailToRunInParallel: - errs := err.Reason().(sabi.FailToRunInParallel).Errors - assert.Equal(t, len(errs), 1) - switch errs[0].Reason().(type) { - case FailToRun: - default: - assert.Fail(t, errs[1].Error()) - } - default: - assert.Fail(t, err.Error()) - } - - log := runnerLogs.Front() - assert.Equal(t, log.Value, "faster runner.") - log = log.Next() - assert.Nil(t, log) -} - -func TestPara_failToRunFasterRunner(t *testing.T) { - clearRunnerLogs() - defer clearRunnerLogs() - - type FailToRun struct{} - - slowerRunner := func() sabi.Err { - time.Sleep(50 * time.Millisecond) - runnerLogs.PushBack("slower runner.") - return sabi.Ok() - } - - fasterRunner := func() sabi.Err { - time.Sleep(10 * time.Millisecond) - return sabi.NewErr(FailToRun{}) - } - - runner := sabi.Para(slowerRunner, fasterRunner) - err := runner() - assert.True(t, err.IsNotOk()) - switch err.Reason().(type) { - case sabi.FailToRunInParallel: - errs := err.Reason().(sabi.FailToRunInParallel).Errors - assert.Equal(t, len(errs), 1) - switch errs[1].Reason().(type) { - case FailToRun: - default: - assert.Fail(t, errs[1].Error()) - } - default: - assert.Fail(t, err.Error()) - } - - log := runnerLogs.Front() - assert.Equal(t, log.Value, "slower runner.") - log = log.Next() - assert.Nil(t, log) -} - -func TestRunner_ifOk(t *testing.T) { - slowerRunner := func() sabi.Err { - time.Sleep(50 * time.Millisecond) - runnerLogs.PushBack("slower runner.") - return sabi.Ok() - } - - fasterRunner := func() sabi.Err { - time.Sleep(10 * time.Millisecond) - runnerLogs.PushBack("faster runner.") - return sabi.Ok() - } - - seq0 := sabi.Seq(slowerRunner) - seq1 := sabi.Seq(fasterRunner) - - err := seq0().IfOk(seq1) - assert.True(t, err.IsOk()) - - log := runnerLogs.Front() - assert.Equal(t, log.Value, "slower runner.") - log = log.Next() - assert.Equal(t, log.Value, "faster runner.") - log = log.Next() - assert.Nil(t, log) -} diff --git a/txn.go b/txn.go deleted file mode 100644 index 24f103d..0000000 --- a/txn.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2023 Takayuki Sato. All Rights Reserved. -// This program is free software under MIT License. -// See the file LICENSE in this distribution for more details. - -package sabi - -// RunTxn is a function which runs logic functions specified as arguments in a -// transaction. -func RunTxn[D any](base DaxBase, logics ...func(dax D) Err) Err { - base.begin() - - dax := base.(D) - err := Ok() - - for _, logic := range logics { - err = logic(dax) - if !err.IsOk() { - break - } - } - - if err.IsOk() { - err = base.commit() - } - - if !err.IsOk() { - base.rollback() - } - - base.end() - - return err -} - -// Txn is a function which creates a transaction having specified logic -// functions. -func Txn[D any](base DaxBase, logics ...func(dax D) Err) func() Err { - return func() Err { - return RunTxn[D](base, logics...) - } -} diff --git a/txn_test.go b/txn_test.go deleted file mode 100644 index 7a32938..0000000 --- a/txn_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package sabi_test - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sttk/sabi" -) - -func TestRunTxn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - type ABDax interface { - GetAData() (string, sabi.Err) - SetBData(data string) sabi.Err - } - - base := sabi.NewDaxBase() - base = struct { - sabi.DaxBase - AGetDax - BGetSetDax - }{ - DaxBase: base, - AGetDax: AGetDax{Dax: base}, - BGetSetDax: BGetSetDax{Dax: base}, - } - - aDs := NewADaxSrc() - bDs := NewBDaxSrc() - - err := base.SetUpLocalDaxSrc("aaa", aDs) - assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bbb", bDs) - assert.True(t, err.IsOk()) - - aDs.AMap["a"] = "hello" - err = sabi.RunTxn(base, func(dax ABDax) sabi.Err { - data, err := dax.GetAData() - if !err.IsOk() { - return err - } - data = strings.ToUpper(data) - err = dax.SetBData(data) - return err - }) - assert.True(t, err.IsOk()) - assert.Equal(t, bDs.BMap["b"], "HELLO") - - log := Logs.Front() - if log.Value == "ADaxConn#Commit" { - assert.Equal(t, log.Value, "ADaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "BDaxConn#Commit") - } else { - assert.Equal(t, log.Value, "BDaxConn#Commit") - log = log.Next() - assert.Equal(t, log.Value, "ADaxConn#Commit") - } - log = log.Next() - if log.Value == "ADaxConn#Close" { - assert.Equal(t, log.Value, "ADaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "BDaxConn#Close") - } else { - assert.Equal(t, log.Value, "BDaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "ADaxConn#Close") - } - log = log.Next() - assert.Nil(t, log) -} - -func TestRunTxn_failToCreateDaxConn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - WillFailToCreateBDaxConn = true - - type ABDax interface { - GetAData() (string, sabi.Err) - SetBData(data string) sabi.Err - } - - base := sabi.NewDaxBase() - base = struct { - sabi.DaxBase - AGetDax - BGetSetDax - }{ - DaxBase: base, - AGetDax: AGetDax{Dax: base}, - BGetSetDax: BGetSetDax{Dax: base}, - } - - aDs := NewADaxSrc() - bDs := NewBDaxSrc() - - err := base.SetUpLocalDaxSrc("aaa", aDs) - assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bbb", bDs) - assert.True(t, err.IsOk()) - - aDs.AMap["a"] = "hello" - err = sabi.RunTxn(base, func(dax ABDax) sabi.Err { - data, err := dax.GetAData() - if !err.IsOk() { - return err - } - data = strings.ToUpper(data) - err = dax.SetBData(data) - return err - }) - assert.False(t, err.IsOk()) - switch err.Reason().(type) { - case sabi.FailToCreateDaxConn: - reason := err.Reason().(sabi.FailToCreateDaxConn) - assert.Equal(t, reason.Name, "bbb") - cause := err.Cause().(sabi.Err) - switch cause.Reason().(type) { - case FailToCreateBDaxConn: - default: - assert.Fail(t, err.Error()) - } - default: - assert.Fail(t, err.Error()) - } - assert.Equal(t, bDs.BMap["b"], "") - - assert.Equal(t, Logs.Front().Value, "ADaxConn#Rollback") - assert.Equal(t, Logs.Front().Next().Value, "ADaxConn#Close") -} - -func TestRunTxn_failToCommitDaxConn(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - WillFailToCommitBDaxConn = true - - type ABDax interface { - GetAData() (string, sabi.Err) - SetBData(data string) sabi.Err - } - - base := sabi.NewDaxBase() - base = struct { - sabi.DaxBase - AGetDax - BGetSetDax - }{ - DaxBase: base, - AGetDax: AGetDax{Dax: base}, - BGetSetDax: BGetSetDax{Dax: base}, - } - - aDs := NewADaxSrc() - bDs := NewBDaxSrc() - - err := base.SetUpLocalDaxSrc("aaa", aDs) - assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bbb", bDs) - assert.True(t, err.IsOk()) - - aDs.AMap["a"] = "hello" - err = sabi.RunTxn(base, func(dax ABDax) sabi.Err { - data, err := dax.GetAData() - if !err.IsOk() { - return err - } - data = strings.ToUpper(data) - err = dax.SetBData(data) - return err - }) - assert.False(t, err.IsOk()) - - switch err.Reason().(type) { - case sabi.FailToCommitDaxConn: - reason := err.Reason().(sabi.FailToCommitDaxConn) - assert.Equal(t, len(reason.Errors), 1) - err = reason.Errors["bbb"] - switch err.Reason().(type) { - case FailToCommitBDaxConn: - default: - assert.Fail(t, err.Error()) - } - default: - assert.Fail(t, err.Error()) - } - - log := Logs.Front() - assert.Equal(t, log.Value, "ADaxConn#Commit") - log = log.Next() - if log.Value == "ADaxConn#Rollback" { - assert.Equal(t, log.Value, "ADaxConn#Rollback") - log = log.Next() - assert.Equal(t, log.Value, "BDaxConn#Rollback") - } else { - assert.Equal(t, log.Value, "BDaxConn#Rollback") - log = log.Next() - assert.Equal(t, log.Value, "ADaxConn#Rollback") - } - log = log.Next() - if log.Value == "ADaxConn#Close" { - assert.Equal(t, log.Value, "ADaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "BDaxConn#Close") - } else { - assert.Equal(t, log.Value, "BDaxConn#Close") - log = log.Next() - assert.Equal(t, log.Value, "ADaxConn#Close") - } - log = log.Next() - assert.Nil(t, log) -} - -func TestRunTxn_Run_errorInLogic(t *testing.T) { - ClearDaxBase() - defer ClearDaxBase() - - WillFailToCommitBDaxConn = true - - type ABDax interface { - GetAData() (string, sabi.Err) - SetBData(data string) sabi.Err - } - - base := sabi.NewDaxBase() - base = struct { - sabi.DaxBase - AGetDax - BGetSetDax - }{ - DaxBase: base, - AGetDax: AGetDax{Dax: base}, - BGetSetDax: BGetSetDax{Dax: base}, - } - - aDs := NewADaxSrc() - bDs := NewBDaxSrc() - - err := base.SetUpLocalDaxSrc("aaa", aDs) - assert.True(t, err.IsOk()) - err = base.SetUpLocalDaxSrc("bbb", bDs) - assert.True(t, err.IsOk()) - - aDs.AMap["a"] = "hello" - err = sabi.RunTxn(base, func(dax ABDax) sabi.Err { - _, err = dax.GetAData() - if !err.IsOk() { - return err - } - return sabi.NewErr(FailToRunLogic{}) - }) - assert.False(t, err.IsOk()) - - switch err.Reason().(type) { - case FailToRunLogic: - default: - assert.Fail(t, err.Error()) - } - - log := Logs.Front() - assert.Equal(t, log.Value, "ADaxConn#Rollback") - log = log.Next() - assert.Equal(t, log.Value, "ADaxConn#Close") -} diff --git a/typename.go b/typename.go new file mode 100644 index 0000000..3adb9fd --- /dev/null +++ b/typename.go @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Takayuki Sato. All Rights Reserved. +// This program is free software under MIT License. +// See the file LICENSE in this distribution for more details. + +package sabi + +import ( + "fmt" +) + +func typeNameOf(v any) string { + return fmt.Sprintf("%T", v) +} + +func typeNameOfTypeParam[T any]() string { + return fmt.Sprintf("%T", new(T))[1:] +} diff --git a/typename_test.go b/typename_test.go new file mode 100644 index 0000000..354ae8f --- /dev/null +++ b/typename_test.go @@ -0,0 +1,189 @@ +package sabi + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type V struct { + A string + B int +} + +func (v V) GetA() string { return v.A } +func (v V) GetB() int { return v.B } + +type I interface { + GetA() string + GetB() int +} + +func TestTypeNameOf_values(t *testing.T) { + b := true + n := 123 + n8 := int8(123) + n16 := int16(123) + n32 := int32(123) + n64 := int64(123) + u := uint(123) + u8 := uint8(123) + u16 := uint16(123) + u32 := uint32(123) + u64 := uint64(123) + f32 := float32(1.23) + f64 := float64(1.23) + c64 := complex64(1 + 2i) + c128 := complex128(1 + 2i) + str := "ABC" + arr := [2]int{1, 2} + slc := arr[:] + objV := V{A: "a", B: 1} + ptrV := &objV + var if1 I = objV + var if2 I = &objV + m := make(map[string]string) + fn := func() {} + chN := make(chan int) + chA := make(chan V) + + assert.Equal(t, typeNameOf(b), "bool") + assert.Equal(t, typeNameOf(n), "int") + assert.Equal(t, typeNameOf(n8), "int8") + assert.Equal(t, typeNameOf(n16), "int16") + assert.Equal(t, typeNameOf(n32), "int32") + assert.Equal(t, typeNameOf(n64), "int64") + assert.Equal(t, typeNameOf(u), "uint") + assert.Equal(t, typeNameOf(u8), "uint8") + assert.Equal(t, typeNameOf(u16), "uint16") + assert.Equal(t, typeNameOf(u32), "uint32") + assert.Equal(t, typeNameOf(u64), "uint64") + assert.Equal(t, typeNameOf(f32), "float32") + assert.Equal(t, typeNameOf(f64), "float64") + assert.Equal(t, typeNameOf(c64), "complex64") + assert.Equal(t, typeNameOf(c128), "complex128") + assert.Equal(t, typeNameOf(str), "string") + assert.Equal(t, typeNameOf(arr), "[2]int") + assert.Equal(t, typeNameOf(slc), "[]int") + assert.Equal(t, typeNameOf(objV), "sabi.V") + assert.Equal(t, typeNameOf(ptrV), "*sabi.V") + assert.Equal(t, typeNameOf(if1), "sabi.V") + assert.Equal(t, typeNameOf(if2), "*sabi.V") + assert.Equal(t, typeNameOf(m), "map[string]string") + assert.Equal(t, typeNameOf(fn), "func()") + assert.Equal(t, typeNameOf(chN), "chan int") + assert.Equal(t, typeNameOf(chA), "chan sabi.V") +} + +func TestTypeNameOf_pointers(t *testing.T) { + b := true + n := 123 + n8 := int8(123) + n16 := int16(123) + n32 := int32(123) + n64 := int64(123) + u := uint(123) + u8 := uint8(123) + u16 := uint16(123) + u32 := uint32(123) + u64 := uint64(123) + f32 := float32(1.23) + f64 := float64(1.23) + c64 := complex64(1 + 2i) + c128 := complex128(1 + 2i) + str := "ABC" + arr := [2]int{1, 2} + slc := arr[:] + objV := V{A: "a", B: 1} + ptrV := &objV + var if1 I = objV + var if2 I = &objV + m := make(map[string]string) + fn := func() {} + chN := make(chan int) + chA := make(chan V) + + assert.Equal(t, typeNameOf(&b), "*bool") + assert.Equal(t, typeNameOf(&n), "*int") + assert.Equal(t, typeNameOf(&n8), "*int8") + assert.Equal(t, typeNameOf(&n16), "*int16") + assert.Equal(t, typeNameOf(&n32), "*int32") + assert.Equal(t, typeNameOf(&n64), "*int64") + assert.Equal(t, typeNameOf(&u), "*uint") + assert.Equal(t, typeNameOf(&u8), "*uint8") + assert.Equal(t, typeNameOf(&u16), "*uint16") + assert.Equal(t, typeNameOf(&u32), "*uint32") + assert.Equal(t, typeNameOf(&u64), "*uint64") + assert.Equal(t, typeNameOf(&f32), "*float32") + assert.Equal(t, typeNameOf(&f64), "*float64") + assert.Equal(t, typeNameOf(&c64), "*complex64") + assert.Equal(t, typeNameOf(&c128), "*complex128") + assert.Equal(t, typeNameOf(&str), "*string") + assert.Equal(t, typeNameOf(&arr), "*[2]int") + assert.Equal(t, typeNameOf(&slc), "*[]int") + assert.Equal(t, typeNameOf(&objV), "*sabi.V") + assert.Equal(t, typeNameOf(&ptrV), "**sabi.V") + assert.Equal(t, typeNameOf(&if1), "*sabi.I") + assert.Equal(t, typeNameOf(&if2), "*sabi.I") + assert.Equal(t, typeNameOf(&m), "*map[string]string") + assert.Equal(t, typeNameOf(&fn), "*func()") + assert.Equal(t, typeNameOf(&chN), "*chan int") + assert.Equal(t, typeNameOf(&chA), "*chan sabi.V") +} + +func TestTypeOfTypeParam_value(t *testing.T) { + assert.Equal(t, typeNameOfTypeParam[bool](), "bool") + assert.Equal(t, typeNameOfTypeParam[int](), "int") + assert.Equal(t, typeNameOfTypeParam[int8](), "int8") + assert.Equal(t, typeNameOfTypeParam[int16](), "int16") + assert.Equal(t, typeNameOfTypeParam[int32](), "int32") + assert.Equal(t, typeNameOfTypeParam[int64](), "int64") + assert.Equal(t, typeNameOfTypeParam[uint](), "uint") + assert.Equal(t, typeNameOfTypeParam[uint8](), "uint8") + assert.Equal(t, typeNameOfTypeParam[uint16](), "uint16") + assert.Equal(t, typeNameOfTypeParam[uint32](), "uint32") + assert.Equal(t, typeNameOfTypeParam[uint64](), "uint64") + assert.Equal(t, typeNameOfTypeParam[float32](), "float32") + assert.Equal(t, typeNameOfTypeParam[float64](), "float64") + assert.Equal(t, typeNameOfTypeParam[complex64](), "complex64") + assert.Equal(t, typeNameOfTypeParam[complex128](), "complex128") + assert.Equal(t, typeNameOfTypeParam[string](), "string") + assert.Equal(t, typeNameOfTypeParam[[2]int](), "[2]int") + assert.Equal(t, typeNameOfTypeParam[[]int](), "[]int") + assert.Equal(t, typeNameOfTypeParam[V](), "sabi.V") + assert.Equal(t, typeNameOfTypeParam[*V](), "*sabi.V") + assert.Equal(t, typeNameOfTypeParam[I](), "sabi.I") + assert.Equal(t, typeNameOfTypeParam[map[string]string](), + "map[string]string") + assert.Equal(t, typeNameOfTypeParam[func()](), "func()") + assert.Equal(t, typeNameOfTypeParam[chan int](), "chan int") + assert.Equal(t, typeNameOfTypeParam[chan V](), "chan sabi.V") +} + +func TestTypeOfTypeParam_pointer(t *testing.T) { + assert.Equal(t, typeNameOfTypeParam[*bool](), "*bool") + assert.Equal(t, typeNameOfTypeParam[*int](), "*int") + assert.Equal(t, typeNameOfTypeParam[*int8](), "*int8") + assert.Equal(t, typeNameOfTypeParam[*int16](), "*int16") + assert.Equal(t, typeNameOfTypeParam[*int32](), "*int32") + assert.Equal(t, typeNameOfTypeParam[*int64](), "*int64") + assert.Equal(t, typeNameOfTypeParam[*uint](), "*uint") + assert.Equal(t, typeNameOfTypeParam[*uint8](), "*uint8") + assert.Equal(t, typeNameOfTypeParam[*uint16](), "*uint16") + assert.Equal(t, typeNameOfTypeParam[*uint32](), "*uint32") + assert.Equal(t, typeNameOfTypeParam[*uint64](), "*uint64") + assert.Equal(t, typeNameOfTypeParam[*float32](), "*float32") + assert.Equal(t, typeNameOfTypeParam[*float64](), "*float64") + assert.Equal(t, typeNameOfTypeParam[*complex64](), "*complex64") + assert.Equal(t, typeNameOfTypeParam[*complex128](), "*complex128") + assert.Equal(t, typeNameOfTypeParam[*string](), "*string") + assert.Equal(t, typeNameOfTypeParam[*[2]int](), "*[2]int") + assert.Equal(t, typeNameOfTypeParam[*[]int](), "*[]int") + assert.Equal(t, typeNameOfTypeParam[*V](), "*sabi.V") + assert.Equal(t, typeNameOfTypeParam[**V](), "**sabi.V") + assert.Equal(t, typeNameOfTypeParam[*I](), "*sabi.I") + assert.Equal(t, typeNameOfTypeParam[*map[string]string](), + "*map[string]string") + assert.Equal(t, typeNameOfTypeParam[*func()](), "*func()") + assert.Equal(t, typeNameOfTypeParam[*chan int](), "*chan int") + assert.Equal(t, typeNameOfTypeParam[*chan V](), "*chan sabi.V") +}