Skip to content

Instantly share code, notes, and snippets.

@DeedleFake
Last active August 28, 2020 01:44
Show Gist options
  • Save DeedleFake/f098b32b8634725c2f370d5ef67896be to your computer and use it in GitHub Desktop.
Save DeedleFake/f098b32b8634725c2f370d5ef67896be to your computer and use it in GitHub Desktop.
Generic testing sandbox.
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)
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))))
}
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
}
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)
}
}
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