commit 2c75b203496f0812533868c54aae59d87a8b3f00 Author: Levente Batuska Date: Thu Sep 12 22:16:26 2024 +0200 inital commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a8a630b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/lbatuska/goutils + +go 1.23.0 diff --git a/logger/ConsoleLogger.go b/logger/ConsoleLogger.go new file mode 100644 index 0000000..ea8260a --- /dev/null +++ b/logger/ConsoleLogger.go @@ -0,0 +1,71 @@ +package logger + +import ( + "fmt" + "time" +) + +func (lgr *ConsoleLoggerimpl) init() { + lgr.messages = make(chan string, logbuffersize) +} + +func (logger *ConsoleLoggerimpl) StartLogger() { + fmt.Println("Starting Logger") + loggerlogonce.Do(func() { + for msg := range logger.messages { + fmt.Print(msg) + } + }) +} + +func (logger *ConsoleLoggerimpl) Write(message string) { + logger.messages <- time.Now().Format(time.UnixDate) + " : " + message + "\n" +} + +func (logger *ConsoleLoggerimpl) Write_Request(message string, uuid string) { + logger.Write(uuid + " : " + message) +} + +func (logger *ConsoleLoggerimpl) WriteErr(err error) (errnum int) { + if err != nil { + logger.Write("Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *ConsoleLoggerimpl) WriteErr_Request(err error, uuid string) (errnum int) { + if err != nil { + logger.Write(uuid + " : Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *ConsoleLoggerimpl) Write_DEBUG(message string) { + if DEBUG { + logger.Write(message) + } +} + +func (logger *ConsoleLoggerimpl) Write_Request_DEBUG(message string, uuid string) { + if DEBUG { + logger.Write_Request(message, uuid) + } +} + +func (logger *ConsoleLoggerimpl) WriteErr_DEBUG(err error) (errnum int) { + if err != nil { + logger.Write_DEBUG("Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *ConsoleLoggerimpl) WriteErr_Request_DEBUG(err error, uuid string) (errnum int) { + if err != nil { + logger.Write_DEBUG(uuid + " : Error: " + err.Error()) + errnum = 1 + } + return errnum +} diff --git a/logger/FileLogger.go b/logger/FileLogger.go new file mode 100644 index 0000000..f5cb111 --- /dev/null +++ b/logger/FileLogger.go @@ -0,0 +1,111 @@ +package logger + +import ( + "fmt" + "os" + "sync" + "time" +) + +func (lgr *FileLoggerimpl) init() { + lgr.filepath = "./log" + lgr.messages = make(chan string, logbuffersize) + envfp, envexist := os.LookupEnv("LOGFILE_GO_LOGGER") + if envexist { + if len(envfp) > 0 { + lgr.filepath = envfp + } else { + Logger().Write_DEBUG(fmt.Sprintf("LOGFILE_GO_LOGGER env exist but has an empty value using default value: %s !\n", lgr.filepath)) + } + } else { + Logger().Write_DEBUG(fmt.Sprintf("LOGFILE_GO_LOGGER env doesn't exist using default value: %s !\n", lgr.filepath)) + } + f, err := os.OpenFile(lgr.filepath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) + if err != nil { + // We probably really don't want to continue execution without file backed logging + panic(fmt.Sprintf("Error opening or creating file: %s", err.Error())) + } + lgr.logFile = f + lgr.mutex = &sync.Mutex{} +} + +func (logger *FileLoggerimpl) StartLogger() { + fmt.Println("Starting FileLogger") + loggerlogonce.Do(func() { + for msg := range logger.messages { + + logger.mutex.Lock() + _, err := logger.logFile.WriteString(msg) + if err != nil { + fmt.Println(err.Error()) + logger.logFile.Close() + logger.mutex.Unlock() + panic("Failed to write to file") + } + err = logger.logFile.Sync() + if err != nil { + logger.logFile.Close() + logger.mutex.Unlock() + + panic("Failed to write to file") + } + logger.mutex.Unlock() + } + }) + // Technically we should do this but this will never run + // logger.mutex.Lock() + // logger.logFile.Close() + // logger.mutex.Unlock() +} + +func (logger *FileLoggerimpl) Write(message string) { + logger.messages <- time.Now().Format(time.UnixDate) + " : " + message + "\n" +} + +func (logger *FileLoggerimpl) Write_Request(message string, request string) { + logger.Write(request + " : " + message) +} + +func (logger *FileLoggerimpl) WriteErr(err error) (errnum int) { + if err != nil { + logger.Write("Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *FileLoggerimpl) WriteErr_Request(err error, uuid string) (errnum int) { + if err != nil { + logger.Write(uuid + " : Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *FileLoggerimpl) Write_DEBUG(message string) { + if DEBUG { + logger.Write(message) + } +} + +func (logger *FileLoggerimpl) Write_Request_DEBUG(message string, uuid string) { + if DEBUG { + logger.Write_Request(message, uuid) + } +} + +func (logger *FileLoggerimpl) WriteErr_DEBUG(err error) (errnum int) { + if err != nil { + logger.Write_DEBUG("Error: " + err.Error()) + errnum = 1 + } + return errnum +} + +func (logger *FileLoggerimpl) WriteErr_Request_DEBUG(err error, uuid string) (errnum int) { + if err != nil { + logger.Write_DEBUG(uuid + " : Error: " + err.Error()) + errnum = 1 + } + return errnum +} diff --git a/logger/Logger.go b/logger/Logger.go new file mode 100644 index 0000000..4a39501 --- /dev/null +++ b/logger/Logger.go @@ -0,0 +1,48 @@ +package logger + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "sync" +) + +// Use this in the init() function to initialize the size of the buffered channel +const logbuffersize int32 = 200 + +var DEBUG bool = true + +var ( + loggerInstance LGRImpl + loggeronce sync.Once + loggerlogonce sync.Once +) + +func Create(instance LGRImpl) { + loggeronce.Do(func() { + loggerInstance = instance + loggerInstance.init() + }) +} + +func Logger() LGRImpl { + return loggerInstance +} + +func PrintJson[T any](entity *T) string { + typename := reflect.TypeFor[T]().Name() + outputStringJson, err := json.MarshalIndent((*entity), "", " ") + if err != nil { + return "Error parsing json data" + } else { + return typename + ":\n" + string(outputStringJson) + "\n" + } +} + +func ExampleLogMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Logger().Write_DEBUG(fmt.Sprintf("\tRequest from: %s to: Host: %s URL: %s \tWith HEADERS: %s \tWith BODY: %s", r.RemoteAddr, r.Host, r.URL, r.Header, r.Body)) + next.ServeHTTP(w, r) + }) +} diff --git a/logger/NullLogger.go b/logger/NullLogger.go new file mode 100644 index 0000000..6d0e125 --- /dev/null +++ b/logger/NullLogger.go @@ -0,0 +1,41 @@ +package logger + +func (lgr *NullLoggerimpl) init() {} + +func (logger *NullLoggerimpl) StartLogger() {} + +func (logger *NullLoggerimpl) Write(message string) {} + +func (logger *NullLoggerimpl) Write_Request(message string, request string) {} + +func (logger *NullLoggerimpl) WriteErr(err error) (errnum int) { + if err != nil { + errnum = 1 + } + return errnum +} + +func (logger *NullLoggerimpl) WriteErr_Request(err error, request string) (errnum int) { + if err != nil { + errnum = 1 + } + return errnum +} + +func (logger *NullLoggerimpl) Write_DEBUG(message string) {} + +func (logger *NullLoggerimpl) Write_Request_DEBUG(message string, uuid string) {} + +func (logger *NullLoggerimpl) WriteErr_DEBUG(err error) (errnum int) { + if err != nil { + errnum = 1 + } + return errnum +} + +func (logger *NullLoggerimpl) WriteErr_Request_DEBUG(err error, uuid string) (errnum int) { + if err != nil { + errnum = 1 + } + return errnum +} diff --git a/logger/interfaces.go b/logger/interfaces.go new file mode 100644 index 0000000..aa4bb56 --- /dev/null +++ b/logger/interfaces.go @@ -0,0 +1,47 @@ +package logger + +// Message format(s) +// +// Unixdate : message\n +// +// Unixdate : Error: error\n +// +// Unixdate : uuid : message\n +// +// Unixdate : uuid : Error: error\n +type LGRImpl interface { + LoggerI + DebugLoggerI +} + +type ( + LoggerI interface { + // Private, use it for member initialization etc + init() + // Start an infinite loop to write out messages from the channel + StartLogger() + Write(message string) + Write_Request(message string, uuid string) + // If an error that is not nill passed in it logs the error and returns 1, otherwise 0 + WriteErr(error) int + WriteErr_Request(err error, uuid string) int + } + // Use _DEBUG prints to strip them out of release builds + DebugLoggerI interface { + // Private, use it for member initialization etc + init() + // Start an infinite loop to write out messages from the channel + StartLogger() + Write_DEBUG(message string) + Write_Request_DEBUG(message string, uuid string) + WriteErr_DEBUG(err error) (errnum int) + WriteErr_Request_DEBUG(err error, uuid string) int + } +) + +// Ensure all methods from LGRImpl are implemented ccompile time +var ( + _ LGRImpl = (*NullLoggerimpl)(nil) + _ LGRImpl = (*ConsoleLoggerimpl)(nil) + _ LGRImpl = (*FileLoggerimpl)(nil) +) diff --git a/logger/types.go b/logger/types.go new file mode 100644 index 0000000..f8ea9ac --- /dev/null +++ b/logger/types.go @@ -0,0 +1,21 @@ +package logger + +import ( + "os" + "sync" +) + +// A logger without logging functionality +type NullLoggerimpl struct{} + +// A logger that logs to sdtout +type ConsoleLoggerimpl struct { + messages chan string +} + +type FileLoggerimpl struct { + messages chan string + mutex *sync.Mutex + logFile *os.File + filepath string +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..cf00beb --- /dev/null +++ b/main_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "errors" + "testing" + "time" + + . "github.com/lbatuska/goutils/logger" + . "github.com/lbatuska/goutils/testing" + . "github.com/lbatuska/goutils/typeutils" +) + +func TestTypes(t *testing.T) { + Create(&FileLoggerimpl{}) + go Logger().StartLogger() + Logger().Write("TEST MSG") + time.Sleep(time.Second * 2) + x := Some("Test") + AssertTrue(t, x.Is_some()) + AssertTrue(t, !x.Is_none()) + AssertEqual(t, x.Unwrap(), "Test") + AssertTrue(t, None_t(1).Is_none()) + err := errors.New("Test") + y := Err_t(err, "t") + AssertEqual(t, y.Unwrap_err(), err) + AssertError(t, y.Unwrap_err()) + z := x.Ok_or(err) + AssertTrue(t, z.Is_ok()) + AssertEqual(t, None[string]().Ok_or(err).Unwrap_err(), err) + AssertTrue(t, Has_value(x)) + AssertTrue(t, Has_value(y.Err())) + AssertTrue(t, Has_value(y.Err().Ok_or(err))) + AssertEqual(t, err, y.Err().Ok_or(err).Unwrap()) + AssertEqual(t, err, y.Err().Ok_or(errors.New("")).Unwrap()) + AssertNotEqual(t, err, Err[string](errors.New("asd")).Unwrap_err()) +} diff --git a/testing/testing.go b/testing/testing.go new file mode 100644 index 0000000..bd1adea --- /dev/null +++ b/testing/testing.go @@ -0,0 +1,25 @@ +package testing + +import "testing" + +func AssertEqual[T comparable](t *testing.T, expected T, actual T) { + t.Helper() + if expected == actual { + t.Logf("✅ [%T](%+v) == [%T](%+v)", expected, expected, actual, actual) + return + } + t.Errorf("❌ [%T](%+v) != [%T](%+v)", expected, expected, actual, actual) +} + +func AssertNotEqual[T comparable](t *testing.T, expected T, actual T) { + t.Helper() + if expected != actual { + t.Logf("✅ [%T](%+v) != [%T](%+v)", expected, expected, actual, actual) + return + } + t.Errorf("❌ [%T](%+v) == [%T](%+v)", expected, expected, actual, actual) +} + +func AssertTrue(t *testing.T, expected bool) { AssertEqual(t, true, expected) } + +func AssertError(t *testing.T, expected error) { AssertTrue(t, expected != nil) } diff --git a/typeutils/interfaces.go b/typeutils/interfaces.go new file mode 100644 index 0000000..d8cd06f --- /dev/null +++ b/typeutils/interfaces.go @@ -0,0 +1,43 @@ +package typeutils + +// Created to abstract over Is_some and Is_ok +type ValueContainer interface { + Has_value() bool +} + +type Unwrappable[T any] interface { + Expect(string) T // panics with a provided custom message + Unwrap() T // panics with a generic message + Unwrap_or(T) T // returns the provided default value + Unwrap_or_default() T // returns the default value of the type T + Unwrap_or_else(func() T) T // returns the result of evaluating the provided function +} + +// Both an Optional and Result is an Option +type Option[T any] interface { + Unwrappable[T] +} + +type OptionalI[T any] interface { + Is_some() bool + Is_none() bool + Ok_or(error) Result[T] + Ok_or_else(func() error) Result[T] +} + +type ResultI[T any] interface { + Is_ok() bool + Is_err() bool + Ok() Optional[T] + Err() Optional[error] +} + +// Ensure compile time the interfaces are implemented +var ( + _ Option[any] = (*Optional[any])(nil) + _ Option[any] = (*Result[any])(nil) + _ OptionalI[any] = (*Optional[any])(nil) + _ ResultI[any] = (*Result[any])(nil) + _ ValueContainer = (*Optional[any])(nil) + _ ValueContainer = (*Result[any])(nil) +) diff --git a/typeutils/optional.go b/typeutils/optional.go new file mode 100644 index 0000000..60ee9d9 --- /dev/null +++ b/typeutils/optional.go @@ -0,0 +1,77 @@ +package typeutils + +// CTORS BEGIN +func Some[T any](value T) Optional[T] { + return Optional[T]{value, true} +} + +func None[T any]() Optional[T] { + return Optional[T]{present: false} +} + +// Because None has no type passing a value of desired type might be preferred syntax over providing type on the function call +func None_t[T any](T) Optional[T] { + return Optional[T]{present: false} +} + +// CTORS END + +func (opt Optional[T]) Is_some() bool { return opt.present } +func (opt Optional[T]) Is_none() bool { return !opt.present } + +func (opt Optional[T]) Has_value() bool { return opt.Is_some() } + +// UNWRAPPABLE INTERFACE BEGIN +func (opt Optional[T]) Expect(msg string) T { + if opt.present { + return opt.value + } + panic(msg) +} + +func (opt Optional[T]) Unwrap() T { + if opt.present { + return opt.value + } + panic("Tried unwrapping an Optional that did not have a value!") +} + +func (opt Optional[T]) Unwrap_or(val T) T { + if opt.present { + return opt.value + } + return val +} + +func (opt Optional[T]) Unwrap_or_default() T { + if opt.present { + return opt.value + } + var res T + return res +} + +func (opt Optional[T]) Unwrap_or_else(f func() T) T { + if opt.present { + return opt.value + } + return f() +} + +// UNWRAPPABLE INTERFACE END + +// transforms Some(v) to Ok(v), and None to Err(err) +func (opt Optional[T]) Ok_or(err error) Result[T] { + if opt.present { + return Ok(opt.value) + } + return Err[T](err) +} + +// transforms Some(v) to Ok(v), and None to a value of Err using the provided function +func (opt Optional[T]) Ok_or_else(f func() error) Result[T] { + if opt.present { + return Ok(opt.value) + } + return Err[T](f()) +} diff --git a/typeutils/result.go b/typeutils/result.go new file mode 100644 index 0000000..8365abd --- /dev/null +++ b/typeutils/result.go @@ -0,0 +1,92 @@ +package typeutils + +// CTORS BEGIN +func Ok[T any](value T) Result[T] { + return Result[T]{value: value, err: nil} +} + +func Err[T any](err error) Result[T] { + return Result[T]{err: err} +} + +func Err_t[T any](err error, x T) Result[T] { + return Result[T]{err: err} +} + +// CTORS END + +func (res Result[T]) Is_ok() bool { return res.err == nil } +func (res Result[T]) Is_err() bool { return res.err != nil } + +func (res Result[T]) Has_value() bool { return res.Is_ok() } + +// UNWRAPPABLE INTERFACE +func (res Result[T]) Expect(msg string) T { + if res.err == nil { + return res.value + } + panic(msg) +} + +func (res Result[T]) Unwrap() T { + if res.err == nil { + return res.value + } + panic("Tried unwrapping a Result that had an error a value!") +} + +func (res Result[T]) Unwrap_or(val T) T { + if res.err == nil { + return res.value + } + return val +} + +func (res Result[T]) Unwrap_or_default() T { + if res.err == nil { + return res.value + } + var ret T + return ret +} + +func (res Result[T]) Unwrap_or_else(f func() T) T { + if res.err == nil { + return res.value + } + return f() +} + +// UNWRAPPABLE INTERFACE + +// This function panic on Ok instead of Err +func (res Result[T]) Expect_err(msg string) error { + if res.err != nil { + return res.err + } + panic(msg) +} + +// This function panic on Ok instead of Err +func (res Result[T]) Unwrap_err() error { + if res.err != nil { + return res.err + } + panic("Expect_err was called with an Ok value") +} + +// transforms Result into Option, mapping Ok(v) to Some(v) and Err(e) to None +func (res Result[T]) Ok() Optional[T] { + if res.err == nil { + return Optional[T]{value: res.value, present: true} + } + return Optional[T]{present: false} +} + +// transforms Result into Option, mapping Err(e) to Some(e) and Ok(v) to None +func (res Result[T]) Err() Optional[error] { + if res.err != nil { + return Optional[error]{value: res.err, present: true} + } + return Optional[error]{present: false} +} diff --git a/typeutils/types.go b/typeutils/types.go new file mode 100644 index 0000000..9dfde38 --- /dev/null +++ b/typeutils/types.go @@ -0,0 +1,11 @@ +package typeutils + +type Optional[T any] struct { + value T + present bool +} + +type Result[T any] struct { + value T + err error +} diff --git a/typeutils/utils.go b/typeutils/utils.go new file mode 100644 index 0000000..e7aeed8 --- /dev/null +++ b/typeutils/utils.go @@ -0,0 +1,27 @@ +package typeutils + +func Expect[T any](val Unwrappable[T], msg string) T { + return val.Expect(msg) +} + +func Unwrap[T any](val Unwrappable[T]) T { + return val.Unwrap() +} + +func Unwrap_or[T any](val Unwrappable[T]) (def T) { + return val.Unwrap_or(def) +} + +func Unwrap_or_default[T any](val Unwrappable[T]) T { + return val.Unwrap_or_default() +} + +func Unwrap_or_else[T any](val Unwrappable[T], f func() T) T { + return val.Unwrap_or_else(f) +} + +// T cannot be inferred +// Returns if the underlying data has a Value (false in case of None or Error) +func Has_value(val ValueContainer) bool { + return val.Has_value() +}