Skip to content
75 changes: 41 additions & 34 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ import (
type PuffApp struct {
// Config is the underlying application configuration.
Config *AppConfig
// RootRouter is the application's default router.
RootRouter *Router

// Server is the http.Server that will be used to serve requests.
Server *http.Server

// rootRouter is the application's default router.
rootRouter *Router
}

// Add a Router to the main app.
// Under the hood attaches the router to the App's RootRouter
func (a *PuffApp) IncludeRouter(r *Router) {
r.puff = a
a.RootRouter.IncludeRouter(r)
// Under the hood attaches the router to the App's rootRouter
func (a *PuffApp) Mount(mountPath string, r *Router) *Router {
return a.rootRouter.Mount(mountPath, r)
}

// Use registers a middleware function to be used by the root router of the PuffApp.
Expand All @@ -31,7 +31,7 @@ func (a *PuffApp) IncludeRouter(r *Router) {
// Parameters:
// - m: Middleware function to be added.
func (a *PuffApp) Use(m Middleware) {
a.RootRouter.Middlewares = append(a.RootRouter.Middlewares, &m)
a.rootRouter.Middlewares = append(a.rootRouter.Middlewares, &m)
}

// addOpenAPIRoutes adds routes to serve OpenAPI documentation for the PuffApp.
Expand All @@ -42,15 +42,9 @@ func (a *PuffApp) Use(m Middleware) {
// This method will not add any routes if DocsURL is empty.
//
// Errors during spec generation are logged, and the method will exit early if any occur.
func (a *PuffApp) addOpenAPIRoutes() {
if a.Config.DisableOpenAPIGeneration {
return
}
func (a *PuffApp) createDocsRouter() *Router {
a.GenerateOpenAPISpec()
docsRouter := Router{
Prefix: a.Config.DocsURL,
Name: "OpenAPI Documentation Router",
}
docsRouter := NewRouter("OpenAPI Documentation Router")

// Provides JSON OpenAPI Schema.
docsRouter.Get(".json", nil, func(c *Context) {
Expand Down Expand Up @@ -81,8 +75,7 @@ func (a *PuffApp) addOpenAPIRoutes() {
}
c.SendResponse(res)
})

a.IncludeRouter(&docsRouter)
return docsRouter
}

// attachMiddlewares recursively applies middlewares to all routes within a router.
Expand Down Expand Up @@ -111,11 +104,11 @@ func attachMiddlewares(middleware_combo *[]Middleware, router *Router) {
// of the PuffApp. It also patches the routes of each router to ensure they have been
// processed for middlewares.
func (a *PuffApp) patchAllRoutes() {
a.RootRouter.patchRoutes()
for _, r := range a.RootRouter.Routers {
a.rootRouter.patchRoutes()
for _, r := range a.rootRouter.Routers {
r.patchRoutes()
}
attachMiddlewares(&[]Middleware{}, a.RootRouter)
attachMiddlewares(&[]Middleware{}, a.rootRouter)
}

// ListenAndServe starts the PuffApp server on the specified address.
Expand All @@ -128,17 +121,23 @@ func (a *PuffApp) patchAllRoutes() {
// Parameters:
// - listenAddr: The address the server will listen on (e.g., ":8080").
func (a *PuffApp) ListenAndServe(listenAddr string) error {

a.patchAllRoutes()
a.addOpenAPIRoutes()

if !a.Config.DisableOpenAPIGeneration {
docsRouter := a.createDocsRouter()
a.Mount(a.Config.DocsURL, docsRouter)
}

slog.Debug(fmt.Sprintf("Running Puff 💨 on %s", listenAddr))
slog.Debug(fmt.Sprintf("Visit docs 💨 on %s", fmt.Sprintf("http://localhost%s%s", listenAddr, a.Config.DocsURL)))

if a.Config.VisualizeRoutesOnStartup {
a.Visualize()
}

if a.Server == nil {
a.Server = &http.Server{
Addr: listenAddr,
Handler: a.RootRouter,
Handler: a,
}
}

Expand All @@ -159,7 +158,7 @@ func (a *PuffApp) ListenAndServe(listenAddr string) error {
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function that will be executed when the route is accessed.
func (a *PuffApp) Get(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.Get(path, fields, handleFunc)
return a.rootRouter.Get(path, fields, handleFunc)
}

// Post registers an HTTP POST route in the PuffApp's root router.
Expand All @@ -169,7 +168,7 @@ func (a *PuffApp) Get(path string, fields any, handleFunc func(*Context)) *Route
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function that will be executed when the route is accessed.
func (a *PuffApp) Post(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.Post(path, fields, handleFunc)
return a.rootRouter.Post(path, fields, handleFunc)
}

// Patch registers an HTTP PATCH route in the PuffApp's root router.
Expand All @@ -179,7 +178,7 @@ func (a *PuffApp) Post(path string, fields any, handleFunc func(*Context)) *Rout
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function that will be executed when the route is accessed.
func (a *PuffApp) Patch(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.Patch(path, fields, handleFunc)
return a.rootRouter.Patch(path, fields, handleFunc)
}

// Put registers an HTTP PUT route in the PuffApp's root router.
Expand All @@ -189,7 +188,7 @@ func (a *PuffApp) Patch(path string, fields any, handleFunc func(*Context)) *Rou
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function that will be executed when the route is accessed.
func (a *PuffApp) Put(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.Put(path, fields, handleFunc)
return a.rootRouter.Put(path, fields, handleFunc)
}

// Delete registers an HTTP DELETE route in the PuffApp's root router.
Expand All @@ -199,7 +198,7 @@ func (a *PuffApp) Put(path string, fields any, handleFunc func(*Context)) *Route
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function that will be executed when the route is accessed.
func (a *PuffApp) Delete(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.Delete(path, fields, handleFunc)
return a.rootRouter.Delete(path, fields, handleFunc)
}

// WebSocket registers a WebSocket route in the PuffApp's root router.
Expand All @@ -210,13 +209,13 @@ func (a *PuffApp) Delete(path string, fields any, handleFunc func(*Context)) *Ro
// - fields: Optional fields associated with the route.
// - handleFunc: The handler function to handle WebSocket connections.
func (a *PuffApp) WebSocket(path string, fields any, handleFunc func(*Context)) *Route {
return a.RootRouter.WebSocket(path, fields, handleFunc)
return a.rootRouter.WebSocket(path, fields, handleFunc)
}

// AllRoutes returns all routes registered in the PuffApp, including those in sub-routers.
// This function provides an aggregated view of all routes in the application.
func (a *PuffApp) AllRoutes() []*Route {
return a.RootRouter.AllRoutes()
return a.rootRouter.AllRoutes()
}

// GenerateOpenAPISpec is responsible for taking the PuffApp configuration and turning it into an OpenAPI json.
Expand All @@ -234,11 +233,11 @@ func (a *PuffApp) GenerateOpenAPISpec() {
func (a *PuffApp) GeneratePathsTags() (*Paths, *[]Tag) {
tags := []Tag{}
tagNames := []string{}
var paths = make(Paths)
for _, route := range a.RootRouter.Routes {
paths := make(Paths)
for _, route := range a.rootRouter.Routes {
addRoute(route, &tags, &tagNames, &paths)
}
for _, router := range a.RootRouter.Routers {
for _, router := range a.rootRouter.Routers {
for _, route := range router.Routes {
addRoute(route, &tags, &tagNames, &paths)
}
Expand Down Expand Up @@ -266,3 +265,11 @@ func (a *PuffApp) Shutdown(ctx context.Context) error {
func (a *PuffApp) Close() error {
return a.Server.Close()
}

func (a *PuffApp) Visualize() {
a.rootRouter.visualizeNode(a.rootRouter.rootNode, "", true)
}

func (a *PuffApp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
a.rootRouter.ServeHTTP(w, req)
}
25 changes: 17 additions & 8 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ type Context struct {
// WebSocket will be nil if the route does not use websockets.
WebSocket *websocket.Conn

// LoggerConfig
LoggerConfig LoggerConfig
statusCode int
statusCode int

puff *PuffApp
}

func NewContext(w http.ResponseWriter, r *http.Request, a *PuffApp) *Context {
return &Context{
Request: r,
ResponseWriter: w,
registry: make(map[string]any), // prevents assignment to nil map
LoggerConfig: *a.Config.LoggerConfig,
puff: a,
}
}

Expand Down Expand Up @@ -187,11 +187,20 @@ func (ctx *Context) GetBearerToken() string {
// below are methods that are more error message focused.

func (ctx *Context) response(status_code int, message string, a ...any) {
ctx.SendResponse(JSONResponse{

if ctx.puff.Config.ErrorConfig.UseJSONResponse {
ctx.SendResponse(JSONResponse{
StatusCode: status_code,
Content: map[string]any{
ctx.puff.Config.ErrorConfig.ErrorKey: fmt.Sprintf(message, a...),
},
})
return
}

ctx.SendResponse(GenericResponse{
StatusCode: status_code,
Content: map[string]any{
"error": fmt.Sprintf(message, a...),
},
Content: fmt.Sprintf(message, a...),
})
}

Expand Down
4 changes: 2 additions & 2 deletions docs/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ func main(){
Name: "my_router_name_here",
Prefix: ""
)
app.IncludeRouter(router) // Including the router is mandatory to serve the router on the app.
app.Mount(router) // Including the router is mandatory to serve the router on the app.
}
```

It is possible to do `router.IncludeRouter(anotherRouter)`.
It is possible to do `router.Mount(anotherRouter)`.

## Example Router Tree

Expand Down
1 change: 1 addition & 0 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ func handleStructType(route *Route, st reflect.Type, sv reflect.Value) *Schema {
newDef.Properties[fieldName] = fieldSchema
}

// TODO(aji): if un-named struct, need to fallback to key.
Schemas[st.Name()] = &newDef
return &Schema{
Ref: "#/components/schemas/" + st.Name(),
Expand Down
2 changes: 1 addition & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (f *File) SaveTo(filepath ...string) (n int, err error) {
return
}
defer f.MultipartFile.Close()
file, err := os.OpenFile(fp, os.O_CREATE|os.O_WRONLY, 0640)
file, err := os.OpenFile(fp, os.O_CREATE|os.O_WRONLY, 0o640)
if err != nil {
return 0, err
}
Expand Down
20 changes: 20 additions & 0 deletions handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package puff

import (
"encoding/json"
"net/http"
)

// common handlers returned inside puff
func ErrMethodNotAllowed(c *Context) {
c.ResponseWriter.Header().Set("Content-Type", "application/json")
c.ResponseWriter.WriteHeader(http.StatusMethodNotAllowed)
response := map[string]string{
"error": "Method Not Allowed for the requested resource.",
"message": "Method Not Allowed for the requested resource.",
}
json.NewEncoder(c.ResponseWriter).Encode(response)
}

// TODO: make the keys passible inside Puff Router as a struct like the key for the error 'ErrorKey' as well as the ErrResponseType which for now can be string or JSON.
// TODO: may need to make it so that maxNumberofConnections can be pulled from Server or something and used as stuff inside context
23 changes: 23 additions & 0 deletions issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- documents issues with the current router

mounting order is very important apparently oops

/docs
.json

-foo

/tenants/{tenant_id}/conversations/{conversation_id}


/users is the router




we are hardcoding to allow . but i think thats okay




generally discourage to create silly routes that dont use
Loading