21 Commits
0.0.1 ... 0.0.2

Author SHA1 Message Date
3d413a8605 fix(SimpleRouter): global middlewares 2025-01-22 01:03:31 +01:00
46a47e7cd5 feat(Logger): add WriteErrMsgRequest and WriteErrMsgRequestDebug functions 2025-01-20 21:48:47 +01:00
cb32017224 feat(Type): add new function to be able to recover from panics when unwrapping a none/error value 2025-01-20 14:18:43 +01:00
286ae7f28b fix(Logger): fix slog based logger's debug methods 2025-01-18 19:14:35 +01:00
3f7ef2e0c2 feat(Logger): add slog based logger 2025-01-12 12:52:07 +01:00
b820a75f5e fix(Logger): parameter naming 2025-01-12 12:51:35 +01:00
4e4c43930a fix(Type): better handling for driver.Valuer interface 2025-01-05 22:57:35 +01:00
5b240e0457 fix(Type): only require pointers where nil is meaningful other than panicking on it 2025-01-05 22:27:49 +01:00
4855e19fb3 fix(Type): make sure when checking if an inner type is Scanner we use pointers 2025-01-05 22:23:51 +01:00
7014f8d0ff feat(Type): implement Value,MarshalJSON,UnmarshalJSON for Result 2025-01-05 13:28:19 +01:00
cb7ea4cf3f chore(Type): consistent receiver naming 2025-01-05 13:18:48 +01:00
4ac0a09b85 fix(Type): promote case driver.Valuer to the top in switch statement for Optional Value 2025-01-04 02:41:56 +01:00
7b2c247538 feat(Type): add driver.Valuer implementation to Optional type 2025-01-04 02:37:20 +01:00
80fa0d5279 feat(Type): Scan method fixes for Result 2025-01-04 01:55:26 +01:00
1fb6970d5a fix(Type): fix Scan for Optional, time parsing now can return the error correctly 2025-01-04 01:32:46 +01:00
e23765a571 feat(Type): implement MarshalJSON and UnmarshalJSON for Optional 2025-01-03 23:30:38 +01:00
10983035db fix(Assert): swap reflect.Ptr to the new reflect.Pointer 2025-01-03 14:00:29 +01:00
29270855f8 test(Type): add more tests for Scan function on Optional 2025-01-01 22:37:59 +01:00
864953076a fix(Type): Scan method fixes 2025-01-01 22:24:59 +01:00
b5fc0df93b feat(Type): add time.Time as a possible type for Scan 2024-12-31 17:58:13 +01:00
1ab0570883 feat(Type): add Scan implementation for Optional and Result types for use with databases 2024-12-31 17:36:00 +01:00
16 changed files with 803 additions and 35 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
)

View File

@@ -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
View 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
}

View File

@@ -20,3 +20,5 @@ type FileLoggerImpl struct {
filepath string
initfilepath string
}
type SlogLoggerImpl struct{}

View File

@@ -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 (
_ 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)
)

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}
@@ -148,6 +149,7 @@ func (rg *RouteGroup) SubPath(path string) *RouteGroup {
rgc := &RouteGroup{
mux: rg.mux,
basePath: rg.basePath + path + "/",
global_middlewares: rg.global_middlewares,
}
middlewares := make([]Middleware, len(rg.middlewares))
copy(middlewares, rg.middlewares)

View File

@@ -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)

View File

@@ -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
}