Skip to content

Commit

Permalink
Improve middleware functionality
Browse files Browse the repository at this point in the history
- Allow specifying if middlewares are for paths with ID, without ID,
  or both
- Add GetRequestedResourceAndDoMiddleware to make it easier to create
  middlewares that access resource by ID
  • Loading branch information
calvinmclean committed Dec 22, 2023
1 parent 66a56ab commit 67cad58
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 13 deletions.
25 changes: 19 additions & 6 deletions babyapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ type API[T Resource] struct {
name string
base string

subAPIs map[string]RelatedAPI
middlewares chi.Middlewares
storage Storage[T]
subAPIs map[string]RelatedAPI
middlewares []func(http.Handler) http.Handler
idMiddlewares []func(http.Handler) http.Handler
storage Storage[T]

server *http.Server
quit chan os.Signal
Expand Down Expand Up @@ -58,6 +59,7 @@ func NewAPI[T Resource](name, base string, instance func() T) *API[T] {
base,
map[string]RelatedAPI{},
nil,
nil,
MapStorage[T]{},
nil,
make(chan os.Signal, 1),
Expand Down Expand Up @@ -177,9 +179,20 @@ func (a *API[T]) Storage() Storage[T] {
return a.storage
}

// AddMiddlewares appends chi.Middlewares to existing middlewares
func (a *API[T]) AddMiddlewares(m chi.Middlewares) *API[T] {
a.middlewares = append(a.middlewares, m...)
// AddPersistentMiddleware adds a middleware which is active on paths with and without resource ID
func (a *API[T]) AddPersistentMiddleware(m func(http.Handler) http.Handler) *API[T] {
return a.AddMiddleware(m).AddIDMiddleware(m)

Check warning on line 184 in babyapi.go

View check run for this annotation

Codecov / codecov/patch

babyapi.go#L184

Added line #L184 was not covered by tests
}

// AddMiddleware adds a middleware which is active only on the paths without resource ID
func (a *API[T]) AddMiddleware(m func(http.Handler) http.Handler) *API[T] {
a.middlewares = append(a.middlewares, m)
return a
}

// AddIDMiddleware adds a middleware which is active only on the paths including a resource ID
func (a *API[T]) AddIDMiddleware(m func(http.Handler) http.Handler) *API[T] {
a.idMiddlewares = append(a.idMiddlewares, m)

Check warning on line 195 in babyapi.go

View check run for this annotation

Codecov / codecov/patch

babyapi.go#L195

Added line #L195 was not covered by tests
return a
}

Expand Down
12 changes: 5 additions & 7 deletions babyapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,13 +732,11 @@ func TestAPIModifiers(t *testing.T) {

api := babyapi.NewAPI[*Album]("Albums", "/albums", func() *Album { return &Album{} }).
SetCustomResponseCode(http.MethodPut, http.StatusTeapot).
AddMiddlewares(chi.Middlewares{
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
middleware++
next.ServeHTTP(w, r)
})
},
AddMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
middleware++
next.ServeHTTP(w, r)
})
}).
SetOnCreateOrUpdate(func(r *http.Request, a *Album) *babyapi.ErrResponse {
onCreateOrUpdate++
Expand Down
23 changes: 23 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ func (a *API[T]) GetRequestedResourceAndDo(do func(*http.Request, T) (render.Ren
}
}

func (a *API[T]) GetRequestedResourceAndDoMiddleware(do func(*http.Request, T) *ErrResponse) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger := GetLoggerFromContext(r.Context())

Check warning on line 70 in helpers.go

View check run for this annotation

Codecov / codecov/patch

helpers.go#L68-L70

Added lines #L68 - L70 were not covered by tests

resource, httpErr := a.GetRequestedResource(r)
if httpErr != nil {
logger.Error("error getting requested resource", "error", httpErr.Error())
_ = render.Render(w, r, httpErr)
return

Check warning on line 76 in helpers.go

View check run for this annotation

Codecov / codecov/patch

helpers.go#L72-L76

Added lines #L72 - L76 were not covered by tests
}

httpErr = do(r, resource)
if httpErr != nil {
_ = render.Render(w, r, httpErr)
return

Check warning on line 82 in helpers.go

View check run for this annotation

Codecov / codecov/patch

helpers.go#L79-L82

Added lines #L79 - L82 were not covered by tests
}

next.ServeHTTP(w, r)

Check warning on line 85 in helpers.go

View check run for this annotation

Codecov / codecov/patch

helpers.go#L85

Added line #L85 was not covered by tests
})
}
}

// ReadRequestBodyAndDo is a wrapper that handles decoding the request body into the resource type and rendering a response
func (a *API[T]) ReadRequestBodyAndDo(do func(*http.Request, T) (T, *ErrResponse)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down
4 changes: 4 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (a *API[T]) Route(r chi.Router) {
r.Get("/", a.GetAll)

r.With(a.resourceExistsMiddleware).Route(fmt.Sprintf("/{%s}", a.IDParamKey()), func(r chi.Router) {
for _, middleware := range a.idMiddlewares {
r.Use(middleware)

Check warning on line 52 in router.go

View check run for this annotation

Codecov / codecov/patch

router.go#L52

Added line #L52 was not covered by tests
}

r.Get("/", a.Get)
r.Delete("/", a.Delete)
r.With(a.requestBodyMiddleware).Put("/", a.Put)
Expand Down

0 comments on commit 67cad58

Please sign in to comment.