Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3d413a8605
|
|||
|
46a47e7cd5
|
|||
|
cb32017224
|
|||
|
286ae7f28b
|
|||
|
3f7ef2e0c2
|
|||
|
b820a75f5e
|
|||
|
4e4c43930a
|
|||
|
5b240e0457
|
|||
|
4855e19fb3
|
|||
|
7014f8d0ff
|
|||
|
cb7ea4cf3f
|
|||
|
4ac0a09b85
|
|||
|
7b2c247538
|
|||
|
80fa0d5279
|
|||
|
1fb6970d5a
|
|||
|
e23765a571
|
|||
|
10983035db
|
|||
|
29270855f8
|
|||
|
864953076a
|
|||
|
b5fc0df93b
|
|||
|
1ab0570883
|
@@ -26,7 +26,7 @@ func file_func_line() (string, string, int) {
|
||||
func IsNillable(kind reflect.Kind) bool {
|
||||
switch kind {
|
||||
// based on reflect/type.go -> Kind
|
||||
case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice, reflect.UnsafePointer:
|
||||
case reflect.Pointer, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice, reflect.UnsafePointer:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -48,6 +48,14 @@ func (logger *ConsoleLoggerImpl) WriteErrRequest(err error, uuid string) (errnum
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *ConsoleLoggerImpl) WriteErrMsgRequest(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
logger.Write(uuid + " " + message + ": Error: " + err.Error())
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *ConsoleLoggerImpl) WriteDebug(message string) {
|
||||
if DEBUG {
|
||||
logger.Write(message)
|
||||
|
||||
@@ -76,8 +76,8 @@ func (logger *FileLoggerImpl) Write(message string) {
|
||||
logger.messages <- time.Now().Format(time.UnixDate) + " : " + message + "\n"
|
||||
}
|
||||
|
||||
func (logger *FileLoggerImpl) WriteRequest(message string, request string) {
|
||||
logger.Write(request + " : " + message)
|
||||
func (logger *FileLoggerImpl) WriteRequest(message string, uuid string) {
|
||||
logger.Write(uuid + " : " + message)
|
||||
}
|
||||
|
||||
func (logger *FileLoggerImpl) WriteErr(err error) (errnum int) {
|
||||
@@ -96,6 +96,14 @@ func (logger *FileLoggerImpl) WriteErrRequest(err error, uuid string) (errnum in
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *FileLoggerImpl) WriteErrMsgRequest(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
logger.Write(uuid + " " + message + ": Error: " + err.Error())
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *FileLoggerImpl) WriteDebug(message string) {
|
||||
if DEBUG {
|
||||
logger.Write(message)
|
||||
@@ -123,3 +131,23 @@ func (logger *FileLoggerImpl) WriteErrRequestDebug(err error, uuid string) (errn
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *FileLoggerImpl) WriteErrMsgRequestDebug(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
if DEBUG {
|
||||
logger.WriteErrMsgRequest(err, message, uuid)
|
||||
}
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *ConsoleLoggerImpl) WriteErrMsgRequestDebug(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
if DEBUG {
|
||||
logger.WriteErrMsgRequest(err, message, uuid)
|
||||
}
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ type (
|
||||
// If an error that is not nill passed in it logs the error and returns 1, otherwise 0
|
||||
WriteErr(error) int
|
||||
WriteErrRequest(err error, uuid string) int
|
||||
|
||||
WriteErrMsgRequest(err error, message string, uuid string) int
|
||||
}
|
||||
// Use _DEBUG prints to strip them out of release builds
|
||||
DebugLogger interface {
|
||||
@@ -38,6 +40,8 @@ type (
|
||||
WriteRequestDebug(message string, uuid string)
|
||||
WriteErrDebug(err error) (errnum int)
|
||||
WriteErrRequestDebug(err error, uuid string) int
|
||||
|
||||
WriteErrMsgRequestDebug(err error, message string, uuid string) int
|
||||
}
|
||||
)
|
||||
|
||||
@@ -46,4 +50,5 @@ var (
|
||||
_ Logger = (*NullLoggerImpl)(nil)
|
||||
_ Logger = (*ConsoleLoggerImpl)(nil)
|
||||
_ Logger = (*FileLoggerImpl)(nil)
|
||||
_ Logger = (*SlogLoggerImpl)(nil)
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ func (logger *NullLoggerImpl) StopLogger() {}
|
||||
|
||||
func (logger *NullLoggerImpl) Write(message string) {}
|
||||
|
||||
func (logger *NullLoggerImpl) WriteRequest(message string, request string) {}
|
||||
func (logger *NullLoggerImpl) WriteRequest(message string, uuid string) {}
|
||||
|
||||
func (logger *NullLoggerImpl) WriteErr(err error) (errnum int) {
|
||||
if err != nil {
|
||||
@@ -17,7 +17,14 @@ func (logger *NullLoggerImpl) WriteErr(err error) (errnum int) {
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *NullLoggerImpl) WriteErrRequest(err error, request string) (errnum int) {
|
||||
func (logger *NullLoggerImpl) WriteErrRequest(err error, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *NullLoggerImpl) WriteErrMsgRequest(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
errnum = 1
|
||||
}
|
||||
@@ -41,3 +48,10 @@ func (logger *NullLoggerImpl) WriteErrRequestDebug(err error, uuid string) (errn
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *NullLoggerImpl) WriteErrMsgRequestDebug(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
83
logger/sloglogger.go
Normal file
83
logger/sloglogger.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package Logger
|
||||
|
||||
import "log/slog"
|
||||
|
||||
func (lgr *SlogLoggerImpl) init() {}
|
||||
|
||||
func (logger *SlogLoggerImpl) StartLogger() {}
|
||||
|
||||
func (logger *SlogLoggerImpl) StopLogger() {}
|
||||
|
||||
func (logger *SlogLoggerImpl) Write(message string) {
|
||||
slog.Info(message)
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteRequest(message string, uuid string) {
|
||||
slog.Info(message, "UUID", uuid)
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErr(err error) (errnum int) {
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErrRequest(err error, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
slog.Error(err.Error(), "UUID", uuid)
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErrMsgRequest(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
slog.Error(message+err.Error(), "UUID", uuid)
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteDebug(message string) {
|
||||
if DEBUG {
|
||||
logger.Write(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteRequestDebug(message string, uuid string) {
|
||||
if DEBUG {
|
||||
logger.WriteRequest(message, uuid)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErrDebug(err error) (errnum int) {
|
||||
if err != nil {
|
||||
if DEBUG {
|
||||
logger.WriteErr(err)
|
||||
}
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErrRequestDebug(err error, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
if DEBUG {
|
||||
logger.WriteErrRequest(err, uuid)
|
||||
}
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
|
||||
func (logger *SlogLoggerImpl) WriteErrMsgRequestDebug(err error, message string, uuid string) (errnum int) {
|
||||
if err != nil {
|
||||
if DEBUG {
|
||||
logger.WriteErrMsgRequest(err, message, uuid)
|
||||
}
|
||||
errnum = 1
|
||||
}
|
||||
return errnum
|
||||
}
|
||||
@@ -20,3 +20,5 @@ type FileLoggerImpl struct {
|
||||
filepath string
|
||||
initfilepath string
|
||||
}
|
||||
|
||||
type SlogLoggerImpl struct{}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
package Type
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Created to abstract over Is_some and Is_ok
|
||||
type ValueContainer interface {
|
||||
HasValue() bool
|
||||
}
|
||||
|
||||
type Recoverable[T any] interface {
|
||||
CatchUnwrap(T)
|
||||
}
|
||||
|
||||
type Unwrappable[T any] interface {
|
||||
Expect(string) T // panics with a provided custom message
|
||||
Unwrap() T // panics with a generic message
|
||||
@@ -24,7 +34,8 @@ type Optionaler[T any] interface {
|
||||
IsNone() bool
|
||||
OkOr(error) Result[T]
|
||||
OkOrElse(func() error) Result[T]
|
||||
Unwrappable[T]
|
||||
Optioner[T]
|
||||
OptionalerMarker
|
||||
}
|
||||
|
||||
type Resulter[T any] interface {
|
||||
@@ -32,15 +43,36 @@ type Resulter[T any] interface {
|
||||
IsErr() bool
|
||||
Ok() Optional[T]
|
||||
Err() Optional[error]
|
||||
Unwrappable[T]
|
||||
Optioner[T]
|
||||
ResulterMarker
|
||||
}
|
||||
|
||||
// Marker interfaces to help type matching
|
||||
type (
|
||||
ResulterMarker interface {
|
||||
Result()
|
||||
}
|
||||
OptionalerMarker interface {
|
||||
Optional()
|
||||
}
|
||||
)
|
||||
|
||||
// Ensure compile time the interfaces are implemented
|
||||
var (
|
||||
_ Optioner[any] = (*Optional[any])(nil)
|
||||
_ Optioner[any] = (*Result[any])(nil)
|
||||
_ Optionaler[any] = (*Optional[any])(nil)
|
||||
_ Resulter[any] = (*Result[any])(nil)
|
||||
_ ValueContainer = (*Optional[any])(nil)
|
||||
_ ValueContainer = (*Result[any])(nil)
|
||||
_ OptionalerMarker = (*Optional[any])(nil)
|
||||
_ ResulterMarker = (*Result[any])(nil)
|
||||
_ Optioner[any] = (*Optional[any])(nil)
|
||||
_ Optioner[any] = (*Result[any])(nil)
|
||||
_ Optionaler[any] = (*Optional[any])(nil)
|
||||
_ Resulter[any] = (*Result[any])(nil)
|
||||
_ ValueContainer = (*Optional[any])(nil)
|
||||
_ ValueContainer = (*Result[any])(nil)
|
||||
_ sql.Scanner = (*Optional[any])(nil)
|
||||
_ sql.Scanner = (*Result[any])(nil)
|
||||
_ driver.Valuer = (*Optional[any])(nil)
|
||||
_ driver.Valuer = (*Result[any])(nil)
|
||||
_ json.Marshaler = (*Optional[any])(nil)
|
||||
_ json.Marshaler = (*Result[any])(nil)
|
||||
_ json.Unmarshaler = (*Optional[any])(nil)
|
||||
_ json.Unmarshaler = (*Result[any])(nil)
|
||||
)
|
||||
|
||||
235
type/optional.go
235
type/optional.go
@@ -1,6 +1,19 @@
|
||||
package Type
|
||||
|
||||
import Assert "github.com/lbatuska/goutils/assert"
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
Assert "github.com/lbatuska/goutils/assert"
|
||||
)
|
||||
|
||||
// Marker interface impl
|
||||
func (opt Optional[T]) Optional() {}
|
||||
|
||||
// CTORS BEGIN
|
||||
func Some[T any](value T) Optional[T] {
|
||||
@@ -40,8 +53,7 @@ func (opt *Optional[T]) HasValue() bool {
|
||||
}
|
||||
|
||||
// UNWRAPPABLE INTERFACE BEGIN
|
||||
func (opt *Optional[T]) Expect(msg string) T {
|
||||
Assert.NotNil(opt)
|
||||
func (opt Optional[T]) Expect(msg string) T {
|
||||
if opt.present {
|
||||
return opt.value
|
||||
}
|
||||
@@ -49,11 +61,13 @@ func (opt *Optional[T]) Expect(msg string) T {
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) Unwrap() T {
|
||||
Assert.NotNil(opt)
|
||||
if opt == nil {
|
||||
panic("Tried unwrapping an Optional that did not have a value!")
|
||||
}
|
||||
if opt.present {
|
||||
return opt.value
|
||||
}
|
||||
panic("Tried unwrapping an Optional that did not have a value!")
|
||||
panic(opt)
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) UnwrapOr(val T) T {
|
||||
@@ -106,3 +120,214 @@ func (opt *Optional[T]) OkOrElse(f func() error) Result[T] {
|
||||
}
|
||||
return Err[T](f())
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) Scan(src interface{}) error {
|
||||
Assert.NotNil(opt)
|
||||
opt.present = false
|
||||
// DB had a null value
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If T is a scanner (Scan is usually implemented on pointers so we need a pointer)
|
||||
var scanner sql.Scanner = nil
|
||||
var ok bool = false
|
||||
if reflect.TypeOf(opt.value).Kind() == reflect.Ptr {
|
||||
scanner, ok = any(opt.value).(sql.Scanner)
|
||||
} else {
|
||||
scanner, ok = any(&opt.value).(sql.Scanner)
|
||||
}
|
||||
if ok {
|
||||
if err := scanner.Scan(src); err != nil {
|
||||
return err
|
||||
}
|
||||
opt.present = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if scanres := opt.scanBuiltin(src); scanres.IsSome() {
|
||||
return scanres.Unwrap()
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported type %T or differs from Optional[%T], and the type doesn't implement sql.Scanner!", src, opt.value)
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) scanBuiltin(src interface{}) Optional[error] {
|
||||
opt.present = false
|
||||
// First handle the special cases where we allow conversion between types
|
||||
// This is usually just parsing []byte into type
|
||||
if scanres := opt.scanTimeSpecial(src); scanres.IsSome() {
|
||||
return scanres
|
||||
}
|
||||
if scanres := opt.scanStringSpecial(src); scanres.IsSome() {
|
||||
return scanres
|
||||
}
|
||||
|
||||
srcVal := reflect.ValueOf(src)
|
||||
optType := reflect.TypeOf(opt.value)
|
||||
|
||||
optElemType := optType
|
||||
if optElemType.Kind() == reflect.Pointer {
|
||||
optElemType = optElemType.Elem()
|
||||
}
|
||||
|
||||
srcElemType := srcVal.Type()
|
||||
if srcElemType.Kind() == reflect.Pointer {
|
||||
srcElemType = srcElemType.Elem()
|
||||
}
|
||||
|
||||
if srcElemType != optElemType {
|
||||
return Some(fmt.Errorf("Optional[%T] (aka %T) differs from %T!", opt.value, opt.value, src))
|
||||
}
|
||||
|
||||
if optType.Kind() == reflect.Pointer {
|
||||
if srcVal.Kind() == reflect.Pointer {
|
||||
opt.value = srcVal.Interface().(T)
|
||||
} else {
|
||||
newPtr := reflect.New(optElemType)
|
||||
newPtr.Elem().Set(srcVal)
|
||||
opt.value = newPtr.Interface().(T)
|
||||
}
|
||||
} else {
|
||||
if srcVal.Kind() == reflect.Pointer {
|
||||
opt.value = srcVal.Elem().Interface().(T)
|
||||
} else {
|
||||
opt.value = srcVal.Interface().(T)
|
||||
}
|
||||
}
|
||||
opt.present = true
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) scanStringSpecial(src interface{}) Optional[error] {
|
||||
opt.present = false
|
||||
switch v := any(opt.value).(type) {
|
||||
case *string:
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*v = string(s)
|
||||
goto ok
|
||||
case *[]byte:
|
||||
*v = string(*s)
|
||||
goto ok
|
||||
}
|
||||
case string:
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
reflect.ValueOf(&opt.value).Elem().Set(reflect.ValueOf(string(s)))
|
||||
goto ok
|
||||
case *[]byte:
|
||||
reflect.ValueOf(&opt.value).Elem().Set(reflect.ValueOf(string(*s)))
|
||||
goto ok
|
||||
}
|
||||
}
|
||||
return None[error]()
|
||||
ok:
|
||||
opt.present = true
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) scanTimeSpecial(src interface{}) Optional[error] {
|
||||
opt.present = false
|
||||
switch v := any(opt.value).(type) {
|
||||
case *time.Time:
|
||||
switch t := src.(type) {
|
||||
case []byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(t))
|
||||
if err == nil {
|
||||
*v = parsedTime
|
||||
goto ok
|
||||
} else {
|
||||
return Some(err)
|
||||
}
|
||||
case *[]byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(*t))
|
||||
if err == nil {
|
||||
*v = parsedTime
|
||||
goto ok
|
||||
} else {
|
||||
return Some(err)
|
||||
}
|
||||
}
|
||||
case time.Time:
|
||||
switch t := src.(type) {
|
||||
case []byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(t))
|
||||
if err == nil {
|
||||
reflect.ValueOf(&opt.value).Elem().Set(reflect.ValueOf(parsedTime))
|
||||
goto ok
|
||||
} else {
|
||||
return Some(err)
|
||||
}
|
||||
case *[]byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(*t))
|
||||
if err == nil {
|
||||
reflect.ValueOf(&opt.value).Elem().Set(reflect.ValueOf(parsedTime))
|
||||
goto ok
|
||||
} else {
|
||||
return Some(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return None[error]()
|
||||
ok:
|
||||
opt.present = true
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (opt Optional[T]) MarshalJSON() ([]byte, error) {
|
||||
if !opt.present {
|
||||
// Return null for `omitempty` compatibility
|
||||
return []byte("null"), nil
|
||||
// panic("Tried to marshal an Optional that did not have a value!")
|
||||
}
|
||||
return json.Marshal(opt.value)
|
||||
}
|
||||
|
||||
func (opt *Optional[T]) UnmarshalJSON(data []byte) error {
|
||||
if string(data) == "null" {
|
||||
opt.present = false
|
||||
return nil
|
||||
}
|
||||
var value T
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
opt.value = value
|
||||
opt.present = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt Optional[T]) Value() (driver.Value, error) {
|
||||
if !opt.present {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if valuer, ok := any(opt.value).(driver.Valuer); ok {
|
||||
return valuer.Value()
|
||||
}
|
||||
|
||||
switch v := any(opt.Value).(type) {
|
||||
|
||||
case string, bool,
|
||||
int, int8, int16, int32, int64,
|
||||
uint, uint8, uint16, uint32, uint64, uintptr,
|
||||
float32, float64,
|
||||
complex64, complex128:
|
||||
return v, nil
|
||||
|
||||
case *string, *bool,
|
||||
*int, *int8, *int16, *int32, *int64,
|
||||
*uint, *uint8, *uint16, *uint32, *uint64, *uintptr,
|
||||
*float32, *float64,
|
||||
*complex64, *complex128:
|
||||
return v, nil
|
||||
|
||||
case fmt.Stringer:
|
||||
return v.String(), nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("unsupported type for Optional")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,23 @@ var (
|
||||
nilOptional = (*Optional[int])(nil)
|
||||
)
|
||||
|
||||
func Test_optionalScan(t *testing.T) {
|
||||
a := None[string]()
|
||||
b := None[string]()
|
||||
c := Some("Not C!")
|
||||
d := None[int]()
|
||||
e := Some(Ptr("Not E!"))
|
||||
a.Scan("a")
|
||||
b.Scan("b")
|
||||
c.Scan(Ptr("c"))
|
||||
e.Scan(Ptr("e"))
|
||||
Testing.AssertEqual(t, "a", a.Unwrap())
|
||||
Testing.AssertEqual(t, "b", b.Unwrap())
|
||||
Testing.AssertEqual(t, "c", c.Unwrap())
|
||||
Testing.AssertPanic(t, func() { d.Unwrap() })
|
||||
Testing.AssertEqual(t, "e", *e.Unwrap())
|
||||
}
|
||||
|
||||
func Test_optionalSome(t *testing.T) {
|
||||
Testing.AssertEqual(t, x, y)
|
||||
Testing.AssertEqual(t, v, u)
|
||||
|
||||
238
type/result.go
238
type/result.go
@@ -1,11 +1,20 @@
|
||||
package Type
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
Assert "github.com/lbatuska/goutils/assert"
|
||||
)
|
||||
|
||||
// Marker interface impl
|
||||
func (res Result[T]) Result() {}
|
||||
|
||||
// CTORS BEGIN
|
||||
func Ok[T any](value T) Result[T] {
|
||||
return Result[T]{value: value, err: nil}
|
||||
@@ -43,8 +52,7 @@ func (res *Result[T]) HasValue() bool {
|
||||
}
|
||||
|
||||
// UNWRAPPABLE INTERFACE
|
||||
func (res *Result[T]) Expect(msg string) T {
|
||||
Assert.NotNil(res)
|
||||
func (res Result[T]) Expect(msg string) T {
|
||||
if res.err == nil {
|
||||
return res.value
|
||||
}
|
||||
@@ -52,11 +60,13 @@ func (res *Result[T]) Expect(msg string) T {
|
||||
}
|
||||
|
||||
func (res *Result[T]) Unwrap() T {
|
||||
Assert.NotNil(res)
|
||||
if res == nil {
|
||||
panic("Tried unwrapping a Result that had an error value!")
|
||||
}
|
||||
if res.err == nil {
|
||||
return res.value
|
||||
}
|
||||
panic("Tried unwrapping a Result that had an error value!")
|
||||
panic(res)
|
||||
}
|
||||
|
||||
func (res *Result[T]) UnwrapOr(val T) T {
|
||||
@@ -134,3 +144,223 @@ func (res *Result[T]) Err() Optional[error] {
|
||||
}
|
||||
return Optional[error]{present: false}
|
||||
}
|
||||
|
||||
func (res *Result[T]) Scan(src interface{}) error {
|
||||
Assert.NotNil(res)
|
||||
e := fmt.Errorf("Unsupported type %T or differs from Result[%T], and the type doesn't implement sql.Scanner!",
|
||||
src, res.value)
|
||||
res.err = e
|
||||
// DB had a null value
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If T is a scanner (Scan is usually implemented on pointers so we need a pointer)
|
||||
var scanner sql.Scanner = nil
|
||||
var ok bool = false
|
||||
if reflect.TypeOf(res.value).Kind() == reflect.Ptr {
|
||||
scanner, ok = any(res.value).(sql.Scanner)
|
||||
} else {
|
||||
scanner, ok = any(&res.value).(sql.Scanner)
|
||||
}
|
||||
if ok {
|
||||
if err := scanner.Scan(src); err != nil {
|
||||
return err
|
||||
}
|
||||
res.err = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if scanres := res.scanBuiltin(src); scanres.IsSome() {
|
||||
return scanres.Unwrap()
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (res *Result[T]) scanBuiltin(src interface{}) Optional[error] {
|
||||
res.err = nil
|
||||
// First handle the special cases where we allow conversion between types
|
||||
// This is usually just parsing []byte into type
|
||||
if scanres := res.scanTimeSpecial(src); scanres.IsSome() {
|
||||
return scanres
|
||||
}
|
||||
if scanres := res.scanStringSpecial(src); scanres.IsSome() {
|
||||
return scanres
|
||||
}
|
||||
|
||||
srcVal := reflect.ValueOf(src)
|
||||
optType := reflect.TypeOf(res.value)
|
||||
|
||||
optElemType := optType
|
||||
if optElemType.Kind() == reflect.Pointer {
|
||||
optElemType = optElemType.Elem()
|
||||
}
|
||||
|
||||
srcElemType := srcVal.Type()
|
||||
if srcElemType.Kind() == reflect.Pointer {
|
||||
srcElemType = srcElemType.Elem()
|
||||
}
|
||||
|
||||
if srcElemType != optElemType {
|
||||
e := fmt.Errorf("Result[%T] (aka %T) differs from %T!", res.value, res.value, src)
|
||||
res.err = e
|
||||
return Some(e)
|
||||
}
|
||||
|
||||
if optType.Kind() == reflect.Pointer {
|
||||
if srcVal.Kind() == reflect.Pointer {
|
||||
res.value = srcVal.Interface().(T)
|
||||
} else {
|
||||
newPtr := reflect.New(optElemType)
|
||||
newPtr.Elem().Set(srcVal)
|
||||
res.value = newPtr.Interface().(T)
|
||||
}
|
||||
} else {
|
||||
if srcVal.Kind() == reflect.Pointer {
|
||||
res.value = srcVal.Elem().Interface().(T)
|
||||
} else {
|
||||
res.value = srcVal.Interface().(T)
|
||||
}
|
||||
}
|
||||
res.err = nil
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (res *Result[T]) scanStringSpecial(src interface{}) Optional[error] {
|
||||
switch v := any(res.value).(type) {
|
||||
case *string:
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*v = string(s)
|
||||
goto ok
|
||||
case *[]byte:
|
||||
*v = string(*s)
|
||||
goto ok
|
||||
}
|
||||
case string:
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
reflect.ValueOf(&res.value).Elem().Set(reflect.ValueOf(string(s)))
|
||||
goto ok
|
||||
case *[]byte:
|
||||
reflect.ValueOf(&res.value).Elem().Set(reflect.ValueOf(string(*s)))
|
||||
goto ok
|
||||
}
|
||||
}
|
||||
return None[error]()
|
||||
ok:
|
||||
res.err = nil
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (res *Result[T]) scanTimeSpecial(src interface{}) Optional[error] {
|
||||
switch v := any(res.value).(type) {
|
||||
case *time.Time:
|
||||
switch t := src.(type) {
|
||||
case []byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(t))
|
||||
if err == nil {
|
||||
*v = parsedTime
|
||||
goto ok
|
||||
} else {
|
||||
res.err = err
|
||||
return Some(err)
|
||||
}
|
||||
case *[]byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(*t))
|
||||
if err == nil {
|
||||
*v = parsedTime
|
||||
goto ok
|
||||
} else {
|
||||
res.err = err
|
||||
return Some(err)
|
||||
}
|
||||
}
|
||||
case time.Time:
|
||||
switch t := src.(type) {
|
||||
case []byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(t))
|
||||
if err == nil {
|
||||
reflect.ValueOf(&res.value).Elem().Set(reflect.ValueOf(parsedTime))
|
||||
goto ok
|
||||
} else {
|
||||
res.err = err
|
||||
return Some(err)
|
||||
}
|
||||
case *[]byte:
|
||||
parsedTime, err := time.Parse(time.RFC3339, string(*t))
|
||||
if err == nil {
|
||||
reflect.ValueOf(&res.value).Elem().Set(reflect.ValueOf(parsedTime))
|
||||
goto ok
|
||||
} else {
|
||||
res.err = err
|
||||
return Some(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return None[error]()
|
||||
ok:
|
||||
res.err = nil
|
||||
return Some[error](nil)
|
||||
}
|
||||
|
||||
func (res Result[T]) MarshalJSON() ([]byte, error) {
|
||||
if nil != res.err {
|
||||
// Return null for `omitempty` compatibility
|
||||
return []byte("null"), nil
|
||||
// panic("Tried to marshal a Result that was error!")
|
||||
}
|
||||
|
||||
return json.Marshal(res.value)
|
||||
}
|
||||
|
||||
func (res *Result[T]) UnmarshalJSON(data []byte) error {
|
||||
var value T
|
||||
res.err = nil
|
||||
if string(data) == "null" {
|
||||
// On null data the best we can do is indicate there was no error and put a default value of T
|
||||
res.value = value
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
res.err = err
|
||||
return err
|
||||
}
|
||||
res.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (res Result[T]) Value() (driver.Value, error) {
|
||||
if nil != res.err {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if valuer, ok := any(res.value).(driver.Valuer); ok {
|
||||
return valuer.Value()
|
||||
}
|
||||
|
||||
switch v := any(res.Value).(type) {
|
||||
|
||||
case string, bool,
|
||||
int, int8, int16, int32, int64,
|
||||
uint, uint8, uint16, uint32, uint64, uintptr,
|
||||
float32, float64,
|
||||
complex64, complex128:
|
||||
return v, nil
|
||||
|
||||
case *string, *bool,
|
||||
*int, *int8, *int16, *int32, *int64,
|
||||
*uint, *uint8, *uint16, *uint32, *uint64, *uintptr,
|
||||
*float32, *float64,
|
||||
*complex64, *complex128:
|
||||
return v, nil
|
||||
|
||||
case fmt.Stringer:
|
||||
return v.String(), nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("unsupported type for Result")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,21 @@ var (
|
||||
nilResult = (*Result[int])(nil)
|
||||
)
|
||||
|
||||
func Test_resultScan(t *testing.T) {
|
||||
a := Ok("Not A!")
|
||||
b := Ok(0)
|
||||
c := Ok(0)
|
||||
d := Err[*string](errors.New(""))
|
||||
a.Scan("a")
|
||||
b.Scan(1)
|
||||
c.Scan("c")
|
||||
d.Scan(Ptr("d"))
|
||||
Testing.AssertEqual(t, a.Unwrap(), "a")
|
||||
Testing.AssertEqual(t, b.Unwrap(), 1)
|
||||
Testing.AssertError(t, c.UnwrapErr())
|
||||
Testing.AssertEqual(t, *d.Unwrap(), "d")
|
||||
}
|
||||
|
||||
func Test_resultConstructors(t *testing.T) {
|
||||
err := errors.New("some error")
|
||||
w := Err[int](err)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package Type
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Expect[T any](val Unwrappable[T], msg string) T {
|
||||
return val.Expect(msg)
|
||||
}
|
||||
@@ -42,3 +48,92 @@ func ResultWrapb[T any](err error, val T) Result[T] {
|
||||
func Ptr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// meant to be used as defer Type.CatchUnwrap(Type.Ptr(&res)) or Type.CatchUnwrap(&res) if res is already a pointer
|
||||
// where res is a pointer to an Option or Result returned by a function (initialized to not be nil)
|
||||
// func X() (res *Optional[int]) {
|
||||
// res = None[int]()
|
||||
// defer CatchUnwrap(&res)
|
||||
//
|
||||
// // Some possibly unsafe unwrapping of values
|
||||
// return res
|
||||
// }
|
||||
// === OR ===
|
||||
// func X() (res Optional[int]) {
|
||||
// res = None[int]()
|
||||
// defer Type.CatchUnwrap(Type.Ptr(&res))
|
||||
//
|
||||
// // Some possibly unsafe unwrapping of values
|
||||
// return res
|
||||
// }
|
||||
|
||||
func CatchUnwrap(ret interface{}) {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
vp := reflect.ValueOf(r)
|
||||
if vp.Kind() != reflect.Pointer || vp.IsNil() {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
if _, ok := r.(OptionalerMarker); ok {
|
||||
if setOptionalNone(ret) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := r.(ResulterMarker); ok {
|
||||
if setResultError(ret, fmt.Errorf("Tried to unwrap a failed result!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic(r)
|
||||
}
|
||||
|
||||
func setOptionalNone(ret interface{}) bool {
|
||||
v := reflect.ValueOf(ret)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return false
|
||||
}
|
||||
elem := v.Elem().Elem()
|
||||
if elem.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
presentField := elem.FieldByName("present")
|
||||
if !presentField.IsValid() {
|
||||
return false
|
||||
}
|
||||
if presentField.Kind() == reflect.Bool {
|
||||
presentField = reflect.NewAt(presentField.Type(),
|
||||
unsafe.Pointer(presentField.UnsafeAddr())).Elem()
|
||||
presentField.SetBool(false) // Setting it to false (None)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func setResultError(ret interface{}, err error) bool {
|
||||
v := reflect.ValueOf(ret)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return false
|
||||
}
|
||||
elem := v.Elem().Elem()
|
||||
if elem.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
errField := elem.FieldByName("err")
|
||||
if !errField.IsValid() {
|
||||
return false
|
||||
}
|
||||
if errField.Kind() == reflect.Interface {
|
||||
errField = reflect.NewAt(errField.Type(),
|
||||
unsafe.Pointer(errField.UnsafeAddr())).Elem()
|
||||
errField.Set(reflect.ValueOf(err)) // Setting the error
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ func (rg *RouteGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rg.mux.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
mwlen := len(rg.global_middlewares) - 1
|
||||
for i := range rg.global_middlewares {
|
||||
handler = rg.global_middlewares[mwlen-i](handler)
|
||||
mwlen := len(*rg.global_middlewares) - 1
|
||||
for i := range *rg.global_middlewares {
|
||||
handler = (*rg.global_middlewares)[mwlen-i](handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
@@ -81,7 +81,7 @@ func (rg *RouteGroup) Handler(r *http.Request) (h http.Handler, pattern string)
|
||||
|
||||
// Creates a new SimpleRouter with the basePath of "/"
|
||||
func SimpleRouter() *RouteGroup {
|
||||
return &RouteGroup{mux: http.NewServeMux(), basePath: "/"}
|
||||
return &RouteGroup{mux: http.NewServeMux(), basePath: "/", global_middlewares: &[]Middleware{}}
|
||||
}
|
||||
|
||||
// Puts middlewares on top of existing ones in order
|
||||
@@ -105,20 +105,21 @@ func (rg *RouteGroup) PopMiddleware() Middleware {
|
||||
return lastMiddleware
|
||||
}
|
||||
|
||||
// this vs PushMiddleware ? what's the diff?
|
||||
func (rg *RouteGroup) PushGlobalMiddleware(first Middleware, others ...Middleware) *RouteGroup {
|
||||
rg.global_middlewares = append(rg.global_middlewares, first)
|
||||
rg.global_middlewares = append(rg.global_middlewares, others...)
|
||||
*rg.global_middlewares = append(*rg.global_middlewares, first)
|
||||
*rg.global_middlewares = append(*rg.global_middlewares, others...)
|
||||
return rg
|
||||
}
|
||||
|
||||
func (rg *RouteGroup) PopGlobalMiddleware() Middleware {
|
||||
if len(rg.global_middlewares) == 0 {
|
||||
if len(*rg.global_middlewares) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastIndex := len(rg.global_middlewares) - 1
|
||||
lastMiddleware := rg.global_middlewares[lastIndex]
|
||||
rg.global_middlewares = rg.global_middlewares[:lastIndex]
|
||||
lastIndex := len(*rg.global_middlewares) - 1
|
||||
lastMiddleware := (*rg.global_middlewares)[lastIndex]
|
||||
*rg.global_middlewares = (*rg.global_middlewares)[:lastIndex]
|
||||
|
||||
return lastMiddleware
|
||||
}
|
||||
@@ -146,8 +147,9 @@ func (rg *RouteGroup) registerRoute(method string, path string, handler http.Han
|
||||
|
||||
func (rg *RouteGroup) SubPath(path string) *RouteGroup {
|
||||
rgc := &RouteGroup{
|
||||
mux: rg.mux,
|
||||
basePath: rg.basePath + path + "/",
|
||||
mux: rg.mux,
|
||||
basePath: rg.basePath + path + "/",
|
||||
global_middlewares: rg.global_middlewares,
|
||||
}
|
||||
middlewares := make([]Middleware, len(rg.middlewares))
|
||||
copy(middlewares, rg.middlewares)
|
||||
|
||||
@@ -192,12 +192,24 @@ func Test_globalMiddlewares(t *testing.T) {
|
||||
})
|
||||
}
|
||||
simpleRouter := SimpleRouter()
|
||||
simpleRouter2 := simpleRouter.SubPath("2")
|
||||
|
||||
simpleRouter.PushGlobalMiddleware(gmw1)
|
||||
simpleRouter.PushGlobalMiddleware(gmw2)
|
||||
simpleRouter.PushMiddleware(mw1, mw2)
|
||||
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
// Subpath
|
||||
simpleRouter2.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
_ts := httptest.NewServer(simpleRouter2)
|
||||
_req, _ := http.NewRequest("GET", _ts.URL+"/2/test", nil)
|
||||
_, _err := http.DefaultClient.Do(_req)
|
||||
Testing.AssertNotError(t, _err)
|
||||
Testing.AssertEqual(t, 12, counter)
|
||||
counter = 0
|
||||
// Subpath
|
||||
|
||||
ts := httptest.NewServer(simpleRouter)
|
||||
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
|
||||
_, err := http.DefaultClient.Do(req)
|
||||
|
||||
@@ -8,5 +8,5 @@ type RouteGroup struct {
|
||||
mux *http.ServeMux // A ponter to the underlying ServeMux, this allows us to call ListenAndServe on any instance
|
||||
basePath string // The current path we are defining handlers on / appending to
|
||||
middlewares []Middleware // Stack of middlewares that will be applied on a handler in order
|
||||
global_middlewares []Middleware
|
||||
global_middlewares *[]Middleware
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user