Last active
August 28, 2020 01:44
-
-
Save DeedleFake/f098b32b8634725c2f370d5ef67896be to your computer and use it in GitHub Desktop.
Generic testing sandbox.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
) | |
type Pair[T1 any, T2 any] struct { | |
First T1 | |
Second T2 | |
} | |
type Error[T any] struct { | |
v T | |
err error | |
} | |
func Do(err error) Error[struct{}] { | |
return Error[struct{}]{ | |
err: err, | |
} | |
} | |
func Try[T any](v T, err error) Error[T] { | |
return Error[T]{ | |
v: v, | |
err: err, | |
} | |
} | |
func Try2[T1 any, T2 any](v1 T1, v2 T2, err error) Error[Pair[T1, T2]] { | |
return Error[Pair[T1, T2]]{ | |
v: Pair[T1, T2]{ | |
First: v1, | |
Second: v2, | |
}, | |
err: err, | |
} | |
} | |
func Catch(f func(err error)) { | |
switch r := recover().(type) { | |
case perror: | |
f(r) | |
case nil: | |
return | |
default: | |
panic(r) | |
} | |
} | |
func Return(rerr *error) func(error) { | |
return func(err error) { | |
*rerr = err | |
} | |
} | |
func Wrap[T any](str string, args ...interface{}) CatchFunc[T] { | |
return func(v T, err error) (T, error) { | |
if err == nil { | |
return v, err | |
} | |
return v, fmt.Errorf(str, append(args, err)...) | |
} | |
} | |
func (err Error[T]) Get() (T, error) { | |
return err.v, err.err | |
} | |
func (err Error[T]) Must() T { | |
if err.err != nil { | |
panic(perror{err.err}) | |
} | |
return err.v | |
} | |
func (err Error[T]) Err() error { | |
return err.err | |
} | |
func (err Error[T]) Or(v T) Error[T] { | |
if err.err == nil { | |
return err | |
} | |
return Error[T]{ | |
v: v, | |
} | |
} | |
func (err Error[T]) Catch(catch CatchFunc[T]) Error[T] { | |
return Try(catch(err.Get())) | |
} | |
type perror struct { | |
err error | |
} | |
func (err perror) Error() string { | |
return err.err.Error() | |
} | |
func (err perror) Unwrap() error { | |
return err.err | |
} | |
type CatchFunc[T any] func(T, error) (T, error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"reflect" | |
"strconv" | |
) | |
const ( | |
StatusSuccess = "success" | |
StatusError = "error" | |
) | |
type ctxKey uint | |
const ( | |
paramsKey ctxKey = iota | |
) | |
func WithParams[P any](ctx context.Context, p P) context.Context { | |
return context.WithValue(ctx, paramsKey, p) | |
} | |
func Params[P any](ctx context.Context) (P, bool) { | |
p, ok := ctx.Value(paramsKey).(P) | |
return p, ok | |
} | |
func parseParameters[P any](req *http.Request, p *P) (err error) { | |
defer Catch(Return(&err)) | |
query := req.URL.Query() | |
v := reflect.ValueOf(p).Elem() | |
for i := 0; i < v.NumField(); i++ { | |
f := v.Type().Field(i) | |
data := query.Get(f.Name) | |
if data == "" { | |
continue | |
} | |
switch k := f.Type.Kind(); k { | |
case reflect.Bool: | |
d := Try(strconv.ParseBool(data)). | |
Catch(Wrap(bool)("parse %q: %w", f.Name)). | |
Must() | |
v.Field(i).SetBool(d) | |
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
num := Try(strconv.ParseInt(data, 10, 64)). | |
Catch(Wrap(int64)("parse %q: %w", f.Name)). | |
Must() | |
v.Field(i).SetInt(num) | |
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
num := Try(strconv.ParseUint(data, 10, 64)). | |
Catch(Wrap(uint64)("parse %q: %w", f.Name)). | |
Must() | |
v.Field(i).SetUint(num) | |
case reflect.Float32, reflect.Float64: | |
num := Try(strconv.ParseFloat(data, 64)). | |
Catch(Wrap(float64)("parse %q: %w", f.Name)). | |
Must() | |
v.Field(i).SetFloat(num) | |
case reflect.Complex64, reflect.Complex128: | |
num := Try(strconv.ParseComplex(data, 128)). | |
Catch(Wrap(complex128)("parse %q: %w", f.Name)). | |
Must() | |
v.Field(i).SetComplex(num) | |
case reflect.String: | |
v.Field(i).SetString(data) | |
default: | |
panic(fmt.Errorf("unexpected param kind for %q: %v", f.Name, k)) | |
} | |
} | |
return nil | |
} | |
func ParamsHandler[P any](h http.Handler, defaults P) http.Handler { | |
if k := reflect.TypeOf(defaults).Kind(); k != reflect.Struct { | |
panic(fmt.Errorf("invalid params kind: %v", k)) | |
} | |
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | |
p := defaults | |
Do(parseParameters(req, &p)).Catch(Wrap(struct{})("parse params: %w")).Must() | |
req = req.WithContext(WithParams(req.Context(), p)) | |
h.ServeHTTP(rw, req) | |
}) | |
} | |
func PanicHandler(h http.Handler) http.Handler { | |
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | |
defer func() { | |
r := recover() | |
if r == nil { | |
return | |
} | |
log.Printf("panic: %v", r) | |
err := json.NewEncoder(rw).Encode(Response{ | |
Status: StatusError, | |
Error: fmt.Sprint(r), | |
}) | |
if err != nil { | |
log.Printf("encode error: %v", r) | |
} | |
}() | |
h.ServeHTTP(rw, req) | |
}) | |
} | |
func HeaderHandler(h http.Handler) http.Handler { | |
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | |
rw.Header().Set("Content-Type", "application/json") | |
h.ServeHTTP(rw, req) | |
}) | |
} | |
type Response struct { | |
Status string `json:"status"` | |
Data interface{} `json:"data,omitempty"` | |
Error string `json:"error,omitempty"` | |
} | |
type EndpointFunc[T any] func(req *http.Request, params T) (interface{}, error) | |
func Endpoint[T any](handler EndpointFunc[T]) http.Handler { | |
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | |
params := Option(Params[T](req.Context())).Must() | |
data := Try(handler(req, params)).Must() | |
Do(json.NewEncoder(rw).Encode(Response{ | |
Status: StatusSuccess, | |
Data: data, | |
})).Must() | |
}) | |
} | |
type Middleware func(h http.Handler) http.Handler | |
func Handle[T any](mux *http.ServeMux, pattern string, handler EndpointFunc[T], defaultParams T, middleware ...Middleware) { | |
if mux == nil { | |
mux = http.DefaultServeMux | |
} | |
h := Endpoint(handler) | |
for i := len(middleware) - 1; i >= 0; i-- { | |
h = middleware[i](h) | |
} | |
mux.Handle(pattern, HeaderHandler(PanicHandler(ParamsHandler(h, defaultParams)))) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
) | |
var ( | |
ErrNoValue = errors.New("no value") | |
) | |
type Optional[T any] struct { | |
v T | |
ok bool | |
} | |
func Option[T any](v T, ok bool) Optional[T] { | |
return Optional[T]{ | |
v: v, | |
ok: ok, | |
} | |
} | |
func (o Optional[T]) Or(v T) Optional[T] { | |
if o.ok { | |
return o | |
} | |
return Optional[T]{ | |
v: v, | |
} | |
} | |
func (o Optional[T]) Get() (T, bool) { | |
return o.v, o.ok | |
} | |
func (o Optional[T]) Must() T { | |
if !o.ok { | |
panic(perror{err: ErrNoValue}) | |
} | |
return o.v | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"log" | |
"net" | |
"net/http" | |
"os" | |
) | |
type mainParams struct { | |
PostID uint | |
Comments int | |
} | |
func handleMain(req *http.Request, params mainParams) (interface{}, error) { | |
if params.PostID <= 0 { | |
return nil, fmt.Errorf("invalid post ID: %v", params.PostID) | |
} | |
return params, nil | |
} | |
func main() { | |
ctx := WithSignal(context.Background(), os.Interrupt) | |
params := mainParams{ | |
Comments: 100, | |
} | |
Handle(nil, "/", handleMain, params) | |
log.Println("Starting server...") | |
server := &http.Server{ | |
Addr: ":8080", | |
BaseContext: func(lis net.Listener) context.Context { | |
return ctx | |
}, | |
} | |
go func() { | |
<-ctx.Done() | |
log.Println("Stopping server...") | |
Do(server.Shutdown(context.Background())). | |
Catch(Wrap[struct{}]("shutdown server: %w")). | |
Must() | |
}() | |
err := Do(server.ListenAndServe()).Catch(Wrap[struct{}]("listen and serve: %w")).Err() | |
if !errors.Is(err, http.ErrServerClosed) { | |
panic(err) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"os" | |
"os/signal" | |
) | |
func WithSignal(ctx context.Context, sig ...os.Signal) context.Context { | |
ctx, cancel := context.WithCancel(ctx) | |
c := make(chan os.Signal, 1) | |
signal.Notify(c, sig...) | |
go func() { | |
defer cancel() | |
defer signal.Stop(c) | |
select { | |
case <-ctx.Done(): | |
case <-c: | |
} | |
}() | |
return ctx | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment