diff --git a/.gitignore b/.gitignore index 5292519..f30e625 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -logs/ \ No newline at end of file +logs/ +.idea/ \ No newline at end of file diff --git a/example/main.go b/example/main.go deleted file mode 100644 index 949ba36..0000000 --- a/example/main.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "log/slog" - - "ron" -) - -type SomethingElements struct { - Name string - Description string -} - -var elements = []SomethingElements{ - {"element 1", "description 1"}, - {"element 2", "description 2"}, - {"element 3", "description 3"}, - {"element 4", "description 4"}, - {"element 5", "description 5"}, - {"element 6", "description 6"}, - {"element 7", "description 7"}, - {"element 8", "description 8"}, - {"element 9", "description 9"}, - {"element 10", "description 10"}, - {"element 11", "description 11"}, - {"element 12", "description 12"}, - {"element 13", "description 13"}, - {"element 14", "description 14"}, - {"element 15", "description 15"}, - {"element 16", "description 16"}, - {"element 17", "description 17"}, - {"element 18", "description 18"}, - {"element 19", "description 19"}, - {"element 20", "description 20"}, - {"element 21", "description 21"}, - {"element 22", "description 22"}, - {"element 23", "description 23"}, - {"element 24", "description 24"}, - {"element 25", "description 25"}, - {"element 26", "description 26"}, - {"element 27", "description 27"}, - {"element 28", "description 28"}, - {"element 29", "description 29"}, - {"element 30", "description 30"}, - {"element 31", "description 31"}, - {"element 32", "description 32"}, - {"element 33", "description 33"}, - {"element 34", "description 34"}, - {"element 35", "description 35"}, - {"element 36", "description 36"}, - {"element 37", "description 37"}, - {"element 38", "description 38"}, - {"element 39", "description 39"}, - {"element 40", "description 40"}, - {"element 41", "description 41"}, - {"element 42", "description 42"}, - {"element 43", "description 43"}, - {"element 44", "description 44"}, - {"element 45", "description 45"}, - {"element 46", "description 46"}, - {"element 47", "description 47"}, - {"element 48", "description 48"}, - {"element 49", "description 49"}, -} - -func main() { - r := ron.New(func(e *ron.Engine) { - e.LogLevel = slog.LevelDebug - }) - - htmlRender := ron.NewHTMLRender() - r.Render = htmlRender - - r.Static("static", "static") - - r.GET("/json", helloWorldJSON) - r.POST("/another", anotherHelloWorld) - r.GET("/html", helloWorldHTML) - r.GET("/component", componentHTML) - - r.Run(":8080") -} - -func helloWorld(c *ron.Context) { - slog.Info("Dummy info message") - c.W.Write([]byte("hello world")) -} - -func anotherHelloWorld(c *ron.Context) { - c.W.Write([]byte("another hello world")) -} - -func helloWorldJSON(c *ron.Context) { - c.JSON(200, ron.Data{"message": "hello world"}) -} - -func helloWorldHTML(c *ron.Context) { - - pages := ron.Pages{ - TotalElements: len(elements), - ElementsPerPage: 5, - } - - pages.PaginationParams(c.R) - elementsPaginated := pages.PaginateArray(elements) - - td := &ron.TemplateData{ - Data: ron.Data{"title": "hello world", "message": "hello world from html", "elements": elementsPaginated}, - Pages: pages, - } - - c.HTML(200, "page.index.gohtml", td) -} - -func componentHTML(c *ron.Context) { - c.HTML(200, "component.list.gohtml", nil) -} diff --git a/example/static/img/dummy.png b/example/static/img/dummy.png deleted file mode 100644 index 885878c..0000000 Binary files a/example/static/img/dummy.png and /dev/null differ diff --git a/example/templates/component.list.gohtml b/example/templates/component.list.gohtml deleted file mode 100644 index 342f35f..0000000 --- a/example/templates/component.list.gohtml +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/example/templates/page.index.gohtml b/example/templates/page.index.gohtml deleted file mode 100644 index f1eef8a..0000000 --- a/example/templates/page.index.gohtml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - {{ .Data.title }} - - - - - - -{{ .Data.message }} - -{{ range .Data.elements }} - - -{{ end }} - -{{ if not .Pages.HasPrevious }} -primera página -{{ else }} -primera -anterior -{{ end }} - -{{ range .Pages.PageRange 5 }} -{{ if .Active }} -{{ .Number }} -{{ else }} -{{ .Number }} -{{ end }} -{{ end }} - -{{ if not .Pages.HasNext }} -última página -{{ else }} -siguiente -última -{{ end }} - - - - - - \ No newline at end of file diff --git a/ron.go b/ron.go index aeea964..75fcb78 100644 --- a/ron.go +++ b/ron.go @@ -24,10 +24,27 @@ type ( E *Engine } + router struct { + path string + method string + handler func(*Context) + group *routerGroup + } + + routerGroup struct { + prefix string + middlewares middlewareChain + engine *Engine + } + + middlewareChain []func(http.Handler) http.Handler + Engine struct { - mux *http.ServeMux - LogLevel slog.Level - Render *Render + LogLevel slog.Level + Render *Render + mux *http.ServeMux + middlewares middlewareChain + router []router } ) @@ -53,8 +70,32 @@ func (e *Engine) apply(opts ...EngineOptions) *Engine { return e } +func (e *Engine) applyMiddlewares(handler http.Handler) http.Handler { + for i := len(e.middlewares) - 1; i >= 0; i-- { + handler = e.middlewares[i](handler) + } + return handler +} + func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) { - e.handleRequest(w, r) + handler := e.applyMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, route := range e.router { + if strings.HasPrefix(r.URL.Path, route.path) && r.Method == route.method { + groupHandler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + route.handler(&Context{W: w, R: r, E: e}) + })) + if route.group != nil { + for i := len(route.group.middlewares) - 1; i >= 0; i-- { + groupHandler = route.group.middlewares[i](groupHandler) + } + } + groupHandler.ServeHTTP(w, r) + return + } + } + http.NotFound(w, r) + })) + handler.ServeHTTP(w, r) } func (e *Engine) Run(addr string) error { @@ -66,24 +107,35 @@ func (e *Engine) handleRequest(w http.ResponseWriter, r *http.Request) { e.mux.ServeHTTP(w, r) } +func (e *Engine) USE(middleware ...func(http.Handler) http.Handler) { + e.middlewares = append(e.middlewares, middleware...) +} + func (e *Engine) GET(path string, handler func(*Context)) { - e.mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - handler(&Context{W: w, R: r, E: e}) - }) + e.router = append(e.router, router{path: path, method: http.MethodGet, handler: handler}) } func (e *Engine) POST(path string, handler func(*Context)) { - e.mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - handler(&Context{W: w, R: r, E: e}) - }) + e.router = append(e.router, router{path: path, method: http.MethodPost, handler: handler}) +} + +func (e *Engine) GROUP(prefix string) *routerGroup { + return &routerGroup{ + prefix: prefix, + engine: e, + } +} + +func (rg *routerGroup) USE(middleware ...func(http.Handler) http.Handler) { + rg.middlewares = append(rg.middlewares, middleware...) +} + +func (rg *routerGroup) GET(path string, handler func(*Context)) { + rg.engine.router = append(rg.engine.router, router{path: rg.prefix + path, method: http.MethodGet, handler: handler, group: rg}) +} + +func (rg *routerGroup) POST(path string, handler func(*Context)) { + rg.engine.router = append(rg.engine.router, router{path: rg.prefix + path, method: http.MethodPost, handler: handler, group: rg}) } // Static serves static files from a specified directory, accessible through a defined URL path. @@ -107,7 +159,9 @@ func (e *Engine) Static(path, dir string) { } fs := http.FileServer(http.Dir(dir)) - e.mux.Handle(path, http.StripPrefix(path, fs)) + e.GET(path, func(c *Context) { + http.StripPrefix(path, fs).ServeHTTP(c.W, c.R) + }) slog.Info("Static files served", "path", path, "dir", dir) } diff --git a/ron_test.go b/ron_test.go index 05593f1..7b25e30 100644 --- a/ron_test.go +++ b/ron_test.go @@ -10,14 +10,14 @@ import ( func Test_defaultEngine(t *testing.T) { e := defaultEngine() if e == nil { - t.Error("Expected Engine, Actual: nil") + t.Error("Expected engine, Actual: nil") } } func Test_New(t *testing.T) { e := New() if e == nil { - t.Error("Expected Engine, Actual: nil") + t.Error("Expected engine, Actual: nil") } if e.Render != nil { t.Error("No expected Renderer, Actual: Renderer") @@ -88,6 +88,96 @@ func Test_POST(t *testing.T) { } } +func Test_USE(t *testing.T) { + e := New() + e.USE(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("MIDDLEWARE")) + next.ServeHTTP(w, r) + }) + }) + e.GET("/", func(c *Context) { + c.W.WriteHeader(http.StatusOK) + c.W.Write([]byte("GET")) + }) + + rr := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + e.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) + } + + if rr.Body.String() != "MIDDLEWAREGET" { + t.Errorf("Expected: MIDDLEWAREGET, Actual: %s", rr.Body.String()) + } +} + +func Test_GROUP(t *testing.T) { + tests := []struct { + method string + path string + expectedCode int + expectedBody string + }{ + {"GET", "/group/", http.StatusOK, "GET"}, + {"POST", "/group/", http.StatusOK, "POST"}, + } + + e := New() + g := e.GROUP("/group") + g.GET("/", func(c *Context) { + c.W.WriteHeader(http.StatusOK) + c.W.Write([]byte("GET")) + }) + g.POST("/", func(c *Context) { + c.W.WriteHeader(http.StatusOK) + c.W.Write([]byte("POST")) + }) + + for _, tt := range tests { + rr := httptest.NewRecorder() + req, _ := http.NewRequest(tt.method, tt.path, nil) + e.ServeHTTP(rr, req) + + if status := rr.Code; status != tt.expectedCode { + t.Errorf("Expected status code: %d, Actual: %d", tt.expectedCode, status) + } + + if rr.Body.String() != tt.expectedBody { + t.Errorf("Expected: %s, Actual: %s", tt.expectedBody, rr.Body.String()) + } + } +} + +func Test_GROUPUSE(t *testing.T) { + e := New() + g := e.GROUP("/group") + g.USE(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("MIDDLEWARE")) + next.ServeHTTP(w, r) + }) + }) + g.GET("/", func(c *Context) { + c.W.WriteHeader(http.StatusOK) + c.W.Write([]byte("GET")) + }) + + rr := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/group/", nil) + e.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) + } + + if rr.Body.String() != "MIDDLEWAREGET" { + t.Errorf("Expected: MIDDLEWAREGET, Actual: %s", rr.Body.String()) + } +} + func Test_Static(t *testing.T) { os.Mkdir("assets", os.ModePerm) f, _ := os.Create("assets/style.css")