From ef9ef5fa40925c80a14045476581ff9060f0d9b0 Mon Sep 17 00:00:00 2001 From: Mojtaba Arezoomand Date: Thu, 16 Mar 2023 01:14:24 +0330 Subject: [PATCH] feat: make kid configurable --- kid.go | 38 ++++++++++++--- kid_test.go | 60 +++++++++++++++++++++++ mux.go | 4 +- options.go | 81 +++++++++++++++++++++++++++++++ options_test.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ static.go | 4 +- 6 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 options.go create mode 100644 options_test.go diff --git a/kid.go b/kid.go index d77902c..7d3cafa 100644 --- a/kid.go +++ b/kid.go @@ -3,6 +3,7 @@ package kid import ( "net/http" "net/url" + "reflect" "sync" htmlrenderer "github.com/mojixcoder/kid/html_renderer" @@ -69,9 +70,7 @@ func (k *Kid) Run(address ...string) error { // Use registers a new middleware. The middleware will be applied to all of the routes. func (k *Kid) Use(middleware MiddlewareFunc) { - if middleware == nil { - panic("middleware cannot be nil") - } + panicIfNil(middleware, "middleware cannot be nil") k.middlewares = append(k.middlewares, middleware) } @@ -209,11 +208,6 @@ func (k *Kid) ServeHTTP(w http.ResponseWriter, r *http.Request) { k.pool.Put(c) } -// Debug returns whether we are in debug mode or not. -func (k *Kid) Debug() bool { - return k.debug -} - // applyMiddlewaresToHandler applies middlewares to the handler and returns the handler. func (k *Kid) applyMiddlewaresToHandler(handler HandlerFunc, middlewares ...MiddlewareFunc) HandlerFunc { for i := len(middlewares) - 1; i >= 0; i-- { @@ -222,6 +216,20 @@ func (k *Kid) applyMiddlewaresToHandler(handler HandlerFunc, middlewares ...Midd return handler } +// Debug returns whether we are in debug mode or not. +func (k *Kid) Debug() bool { + return k.debug +} + +// ApplyOptions applies the given options. +func (k *Kid) ApplyOptions(opts ...Option) { + for _, opt := range opts { + panicIfNil(opt, "option cannot be nil") + + opt.apply(k) + } +} + // getPath returns request's path. func getPath(u *url.URL) string { if u.RawPath != "" { @@ -237,3 +245,17 @@ func resolveAddress(addresses []string) string { } return addresses[0] } + +// panicIfNil panics if the given parameter is nil. +func panicIfNil(x any, message string) { + if x == nil { + panic(message) + } + + switch reflect.TypeOf(x).Kind() { + case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice, reflect.Interface, reflect.Func: + if reflect.ValueOf(x).IsNil() { + panic(message) + } + } +} diff --git a/kid_test.go b/kid_test.go index 4b613f8..29910ff 100644 --- a/kid_test.go +++ b/kid_test.go @@ -623,3 +623,63 @@ func TestGetPath(t *testing.T) { assert.NotEmpty(t, u.RawPath) assert.Equal(t, u.RawPath, getPath(u)) } + +func TestApplyOptions(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "option cannot be nil", func() { + k.ApplyOptions(nil) + }) + + handler := func(c *Context, err error) { + } + + k.ApplyOptions( + WithDebug(true), + WithErrorHandler(handler), + ) + + assert.True(t, k.Debug()) + assert.True(t, funcsAreEqual(handler, k.errorHandler)) +} + +func TestPanicIfNil(t *testing.T) { + assert.PanicsWithValue(t, "nil", func() { + panicIfNil(nil, "nil") + }) + + assert.Panics(t, func() { + var x *int + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x []string + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x map[string]string + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x interface{} + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x func() + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x chan bool + panicIfNil(x, "") + }) + + assert.Panics(t, func() { + var x [1]int + panicIfNil(x, "") + }) +} diff --git a/mux.go b/mux.go index d1845ca..da536b3 100644 --- a/mux.go +++ b/mux.go @@ -57,9 +57,7 @@ func (router *Router) add(path string, handler HandlerFunc, methods []string, mi panic("providing at least one method is required") } - if handler == nil { - panic("handler cannot be nil") - } + panicIfNil(handler, "handler cannot be nil") path = cleanPath(path, false) diff --git a/options.go b/options.go new file mode 100644 index 0000000..f182c23 --- /dev/null +++ b/options.go @@ -0,0 +1,81 @@ +package kid + +import ( + htmlrenderer "github.com/mojixcoder/kid/html_renderer" + "github.com/mojixcoder/kid/serializer" +) + +type ( + // Option is the interface for customizing Kid. + Option interface { + apply(*Kid) + } + + optionImpl func(*Kid) +) + +// applyOption implements the Option interface. +func (f optionImpl) apply(k *Kid) { + f(k) +} + +// WithDebug configures Kid's debug option. +func WithDebug(debug bool) Option { + return optionImpl(func(k *Kid) { + k.debug = debug + }) +} + +// WithHTMLRenderer configures Kid's HTML renderer. +func WithHTMLRenderer(renderer htmlrenderer.HTMLRenderer) Option { + panicIfNil(renderer, "renderer cannot be nil") + + return optionImpl(func(k *Kid) { + k.htmlRenderer = renderer + }) +} + +// WithXMLSerializer configures Kid's XML serializer. +func WithXMLSerializer(serializer serializer.Serializer) Option { + panicIfNil(serializer, "xml serializer cannot be nil") + + return optionImpl(func(k *Kid) { + k.xmlSerializer = serializer + }) +} + +// WithJSONSerializer configures Kid's JSON serializer. +func WithJSONSerializer(serializer serializer.Serializer) Option { + panicIfNil(serializer, "json serializer cannot be nil") + + return optionImpl(func(k *Kid) { + k.jsonSerializer = serializer + }) +} + +// WithErrorHandler configures Kid's error handler. +func WithErrorHandler(errHandler ErrorHandler) Option { + panicIfNil(errHandler, "error handler cannot be nil") + + return optionImpl(func(k *Kid) { + k.errorHandler = errHandler + }) +} + +// WithNotFoundHandler configures Kid's not found handler. +func WithNotFoundHandler(handler HandlerFunc) Option { + panicIfNil(handler, "not found handler cannot be nil") + + return optionImpl(func(k *Kid) { + k.notFoundHandler = handler + }) +} + +// WithMethodNotAllowedHandler configures Kid's method not allowed handler. +func WithMethodNotAllowedHandler(handler HandlerFunc) Option { + panicIfNil(handler, "method not allowed handler cannot be nil") + + return optionImpl(func(k *Kid) { + k.methodNotAllowedHandler = handler + }) +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..f6a0572 --- /dev/null +++ b/options_test.go @@ -0,0 +1,126 @@ +package kid + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +type mockEverything struct{} + +func (*mockEverything) RenderHTML(res http.ResponseWriter, path string, data any) error { + return nil +} + +func (*mockEverything) Write(w http.ResponseWriter, in any, indent string) error { + return nil +} + +func (*mockEverything) Read(req *http.Request, out any) error { + return nil +} + +func TestWithDebug(t *testing.T) { + k := New() + + opt := WithDebug(true) + opt.apply(k) + + assert.True(t, k.Debug()) +} + +func TestWithHTMLRenderer(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "renderer cannot be nil", func() { + WithHTMLRenderer(nil) + }) + + renderer := &mockEverything{} + + opt := WithHTMLRenderer(renderer) + opt.apply(k) + + assert.Equal(t, renderer, k.htmlRenderer) +} + +func TestWithXMLSerializer(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "xml serializer cannot be nil", func() { + WithXMLSerializer(nil) + }) + + serializer := &mockEverything{} + + opt := WithXMLSerializer(serializer) + opt.apply(k) + + assert.Equal(t, serializer, k.xmlSerializer) +} + +func TestWithJSONSerializer(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "json serializer cannot be nil", func() { + WithJSONSerializer(nil) + }) + + serializer := &mockEverything{} + + opt := WithJSONSerializer(serializer) + opt.apply(k) + + assert.Equal(t, serializer, k.jsonSerializer) +} + +func TestWithErrorHandler(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "error handler cannot be nil", func() { + WithErrorHandler(nil) + }) + + hanlder := func(c *Context, err error) { + } + + opt := WithErrorHandler(hanlder) + opt.apply(k) + + assert.True(t, funcsAreEqual(hanlder, k.errorHandler)) +} + +func TestWithNotFoundHandler(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "not found handler cannot be nil", func() { + WithNotFoundHandler(nil) + }) + + hanlder := func(c *Context) error { + return nil + } + + opt := WithNotFoundHandler(hanlder) + opt.apply(k) + + assert.True(t, funcsAreEqual(hanlder, k.notFoundHandler)) +} + +func TestWithMethodNotAllowedHandler(t *testing.T) { + k := New() + + assert.PanicsWithValue(t, "method not allowed handler cannot be nil", func() { + WithMethodNotAllowedHandler(nil) + }) + + hanlder := func(c *Context) error { + return nil + } + + opt := WithMethodNotAllowedHandler(hanlder) + opt.apply(k) + + assert.True(t, funcsAreEqual(hanlder, k.methodNotAllowedHandler)) +} diff --git a/static.go b/static.go index 0aacc83..597e058 100644 --- a/static.go +++ b/static.go @@ -34,9 +34,7 @@ func (fs FS) Open(name string) (http.File, error) { // newFileServer returns new file server. func newFileServer(urlPath string, fs http.FileSystem) http.Handler { - if fs == nil { - panic("file system cannot be nil") - } + panicIfNil(fs, "file system cannot be nil") urlPath = cleanPath(urlPath, false) urlPath = appendSlash(urlPath)