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 { func IsNillable(kind reflect.Kind) bool {
switch kind { switch kind {
// based on reflect/type.go -> 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 return true
default: default:
return false return false

View File

@@ -48,6 +48,14 @@ func (logger *ConsoleLoggerImpl) WriteErrRequest(err error, uuid string) (errnum
return 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) { func (logger *ConsoleLoggerImpl) WriteDebug(message string) {
if DEBUG { if DEBUG {
logger.Write(message) logger.Write(message)

View File

@@ -76,8 +76,8 @@ func (logger *FileLoggerImpl) Write(message string) {
logger.messages <- time.Now().Format(time.UnixDate) + " : " + message + "\n" logger.messages <- time.Now().Format(time.UnixDate) + " : " + message + "\n"
} }
func (logger *FileLoggerImpl) WriteRequest(message string, request string) { func (logger *FileLoggerImpl) WriteRequest(message string, uuid string) {
logger.Write(request + " : " + message) logger.Write(uuid + " : " + message)
} }
func (logger *FileLoggerImpl) WriteErr(err error) (errnum int) { func (logger *FileLoggerImpl) WriteErr(err error) (errnum int) {
@@ -96,6 +96,14 @@ func (logger *FileLoggerImpl) WriteErrRequest(err error, uuid string) (errnum in
return errnum 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) { func (logger *FileLoggerImpl) WriteDebug(message string) {
if DEBUG { if DEBUG {
logger.Write(message) logger.Write(message)
@@ -123,3 +131,23 @@ func (logger *FileLoggerImpl) WriteErrRequestDebug(err error, uuid string) (errn
} }
return errnum 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 // If an error that is not nill passed in it logs the error and returns 1, otherwise 0
WriteErr(error) int WriteErr(error) int
WriteErrRequest(err error, uuid string) 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 // Use _DEBUG prints to strip them out of release builds
DebugLogger interface { DebugLogger interface {
@@ -38,6 +40,8 @@ type (
WriteRequestDebug(message string, uuid string) WriteRequestDebug(message string, uuid string)
WriteErrDebug(err error) (errnum int) WriteErrDebug(err error) (errnum int)
WriteErrRequestDebug(err error, uuid string) int WriteErrRequestDebug(err error, uuid string) int
WriteErrMsgRequestDebug(err error, message string, uuid string) int
} }
) )
@@ -46,4 +50,5 @@ var (
_ Logger = (*NullLoggerImpl)(nil) _ Logger = (*NullLoggerImpl)(nil)
_ Logger = (*ConsoleLoggerImpl)(nil) _ Logger = (*ConsoleLoggerImpl)(nil)
_ Logger = (*FileLoggerImpl)(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) 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) { func (logger *NullLoggerImpl) WriteErr(err error) (errnum int) {
if err != nil { if err != nil {
@@ -17,7 +17,14 @@ func (logger *NullLoggerImpl) WriteErr(err error) (errnum int) {
return errnum 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 { if err != nil {
errnum = 1 errnum = 1
} }
@@ -41,3 +48,10 @@ func (logger *NullLoggerImpl) WriteErrRequestDebug(err error, uuid string) (errn
} }
return errnum 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 filepath string
initfilepath string initfilepath string
} }
type SlogLoggerImpl struct{}

View File

@@ -1,10 +1,20 @@
package Type package Type
import (
"database/sql"
"database/sql/driver"
"encoding/json"
)
// Created to abstract over Is_some and Is_ok // Created to abstract over Is_some and Is_ok
type ValueContainer interface { type ValueContainer interface {
HasValue() bool HasValue() bool
} }
type Recoverable[T any] interface {
CatchUnwrap(T)
}
type Unwrappable[T any] interface { type Unwrappable[T any] interface {
Expect(string) T // panics with a provided custom message Expect(string) T // panics with a provided custom message
Unwrap() T // panics with a generic message Unwrap() T // panics with a generic message
@@ -24,7 +34,8 @@ type Optionaler[T any] interface {
IsNone() bool IsNone() bool
OkOr(error) Result[T] OkOr(error) Result[T]
OkOrElse(func() error) Result[T] OkOrElse(func() error) Result[T]
Unwrappable[T] Optioner[T]
OptionalerMarker
} }
type Resulter[T any] interface { type Resulter[T any] interface {
@@ -32,15 +43,36 @@ type Resulter[T any] interface {
IsErr() bool IsErr() bool
Ok() Optional[T] Ok() Optional[T]
Err() Optional[error] 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 // Ensure compile time the interfaces are implemented
var ( var (
_ OptionalerMarker = (*Optional[any])(nil)
_ ResulterMarker = (*Result[any])(nil)
_ Optioner[any] = (*Optional[any])(nil) _ Optioner[any] = (*Optional[any])(nil)
_ Optioner[any] = (*Result[any])(nil) _ Optioner[any] = (*Result[any])(nil)
_ Optionaler[any] = (*Optional[any])(nil) _ Optionaler[any] = (*Optional[any])(nil)
_ Resulter[any] = (*Result[any])(nil) _ Resulter[any] = (*Result[any])(nil)
_ ValueContainer = (*Optional[any])(nil) _ ValueContainer = (*Optional[any])(nil)
_ ValueContainer = (*Result[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 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 // CTORS BEGIN
func Some[T any](value T) Optional[T] { func Some[T any](value T) Optional[T] {
@@ -40,8 +53,7 @@ func (opt *Optional[T]) HasValue() bool {
} }
// UNWRAPPABLE INTERFACE BEGIN // UNWRAPPABLE INTERFACE BEGIN
func (opt *Optional[T]) Expect(msg string) T { func (opt Optional[T]) Expect(msg string) T {
Assert.NotNil(opt)
if opt.present { if opt.present {
return opt.value return opt.value
} }
@@ -49,11 +61,13 @@ func (opt *Optional[T]) Expect(msg string) T {
} }
func (opt *Optional[T]) Unwrap() 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 { if opt.present {
return opt.value return opt.value
} }
panic("Tried unwrapping an Optional that did not have a value!") panic(opt)
} }
func (opt *Optional[T]) UnwrapOr(val T) T { 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()) 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) 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) { func Test_optionalSome(t *testing.T) {
Testing.AssertEqual(t, x, y) Testing.AssertEqual(t, x, y)
Testing.AssertEqual(t, v, u) Testing.AssertEqual(t, v, u)

View File

@@ -1,11 +1,20 @@
package Type package Type
import ( import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors" "errors"
"fmt"
"reflect"
"time"
Assert "github.com/lbatuska/goutils/assert" Assert "github.com/lbatuska/goutils/assert"
) )
// Marker interface impl
func (res Result[T]) Result() {}
// CTORS BEGIN // CTORS BEGIN
func Ok[T any](value T) Result[T] { func Ok[T any](value T) Result[T] {
return Result[T]{value: value, err: nil} return Result[T]{value: value, err: nil}
@@ -43,8 +52,7 @@ func (res *Result[T]) HasValue() bool {
} }
// UNWRAPPABLE INTERFACE // UNWRAPPABLE INTERFACE
func (res *Result[T]) Expect(msg string) T { func (res Result[T]) Expect(msg string) T {
Assert.NotNil(res)
if res.err == nil { if res.err == nil {
return res.value return res.value
} }
@@ -52,11 +60,13 @@ func (res *Result[T]) Expect(msg string) T {
} }
func (res *Result[T]) Unwrap() 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 { if res.err == nil {
return res.value return res.value
} }
panic("Tried unwrapping a Result that had an error value!") panic(res)
} }
func (res *Result[T]) UnwrapOr(val T) T { func (res *Result[T]) UnwrapOr(val T) T {
@@ -134,3 +144,223 @@ func (res *Result[T]) Err() Optional[error] {
} }
return Optional[error]{present: false} 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) 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) { func Test_resultConstructors(t *testing.T) {
err := errors.New("some error") err := errors.New("some error")
w := Err[int](err) w := Err[int](err)

View File

@@ -1,5 +1,11 @@
package Type package Type
import (
"fmt"
"reflect"
"unsafe"
)
func Expect[T any](val Unwrappable[T], msg string) T { func Expect[T any](val Unwrappable[T], msg string) T {
return val.Expect(msg) 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 { func Ptr[T any](v T) *T {
return &v 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) rg.mux.ServeHTTP(w, r)
}) })
mwlen := len(rg.global_middlewares) - 1 mwlen := len(*rg.global_middlewares) - 1
for i := range rg.global_middlewares { for i := range *rg.global_middlewares {
handler = rg.global_middlewares[mwlen-i](handler) handler = (*rg.global_middlewares)[mwlen-i](handler)
} }
handler.ServeHTTP(w, r) 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 "/" // Creates a new SimpleRouter with the basePath of "/"
func SimpleRouter() *RouteGroup { 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 // Puts middlewares on top of existing ones in order
@@ -105,20 +105,21 @@ func (rg *RouteGroup) PopMiddleware() Middleware {
return lastMiddleware return lastMiddleware
} }
// this vs PushMiddleware ? what's the diff?
func (rg *RouteGroup) PushGlobalMiddleware(first Middleware, others ...Middleware) *RouteGroup { func (rg *RouteGroup) PushGlobalMiddleware(first Middleware, others ...Middleware) *RouteGroup {
rg.global_middlewares = append(rg.global_middlewares, first) *rg.global_middlewares = append(*rg.global_middlewares, first)
rg.global_middlewares = append(rg.global_middlewares, others...) *rg.global_middlewares = append(*rg.global_middlewares, others...)
return rg return rg
} }
func (rg *RouteGroup) PopGlobalMiddleware() Middleware { func (rg *RouteGroup) PopGlobalMiddleware() Middleware {
if len(rg.global_middlewares) == 0 { if len(*rg.global_middlewares) == 0 {
return nil return nil
} }
lastIndex := len(rg.global_middlewares) - 1 lastIndex := len(*rg.global_middlewares) - 1
lastMiddleware := rg.global_middlewares[lastIndex] lastMiddleware := (*rg.global_middlewares)[lastIndex]
rg.global_middlewares = rg.global_middlewares[:lastIndex] *rg.global_middlewares = (*rg.global_middlewares)[:lastIndex]
return lastMiddleware return lastMiddleware
} }
@@ -148,6 +149,7 @@ func (rg *RouteGroup) SubPath(path string) *RouteGroup {
rgc := &RouteGroup{ rgc := &RouteGroup{
mux: rg.mux, mux: rg.mux,
basePath: rg.basePath + path + "/", basePath: rg.basePath + path + "/",
global_middlewares: rg.global_middlewares,
} }
middlewares := make([]Middleware, len(rg.middlewares)) middlewares := make([]Middleware, len(rg.middlewares))
copy(middlewares, rg.middlewares) copy(middlewares, rg.middlewares)

View File

@@ -192,12 +192,24 @@ func Test_globalMiddlewares(t *testing.T) {
}) })
} }
simpleRouter := SimpleRouter() simpleRouter := SimpleRouter()
simpleRouter2 := simpleRouter.SubPath("2")
simpleRouter.PushGlobalMiddleware(gmw1) simpleRouter.PushGlobalMiddleware(gmw1)
simpleRouter.PushGlobalMiddleware(gmw2) simpleRouter.PushGlobalMiddleware(gmw2)
simpleRouter.PushMiddleware(mw1, mw2) simpleRouter.PushMiddleware(mw1, mw2)
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.GET("test2", 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) ts := httptest.NewServer(simpleRouter)
req, _ := http.NewRequest("GET", ts.URL+"/test", nil) req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
_, err := http.DefaultClient.Do(req) _, 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 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 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 middlewares []Middleware // Stack of middlewares that will be applied on a handler in order
global_middlewares []Middleware global_middlewares *[]Middleware
} }