Skip to content

Commit 75d316c

Browse files
author
Ollie Phillips
authored
Merge pull request #305 from olliephillips/ponzu-dev
Tentatively merging this @nilslice. The code and markdown docs are complete I think. As I understand it, you'll need to merge it to master. I can't do that - and a good thing too ;) I'll do the same on the main docs repo also
2 parents 8882d90 + d337a22 commit 75d316c

File tree

3 files changed

+120
-9
lines changed

3 files changed

+120
-9
lines changed

docs/src/Interfaces/Item.md

+27-2
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,16 @@ func (p *Post) Omit(res http.ResponseWriter, req *http.Request) ([]string, error
107107

108108
### [item.Hookable](https://godoc.org/github.com/ponzu-cms/ponzu/system/item#Hookable)
109109
Hookable provides lifecycle hooks into the http handlers which manage Save, Delete,
110-
Approve, and Reject routines. All methods in its set take an
111-
`http.ResponseWriter, *http.Request` and return an `error`.
110+
Approve, Reject routines, and API response routines. All methods in its set take an
111+
`http.ResponseWriter, *http.Request` and return an `error`. Hooks which relate to the API response, additionally take data of type `[]byte`, and may provide a return of the same type.
112112

113113
##### Method Set
114114

115115
```go
116116
type Hookable interface {
117+
BeforeAPIResponse(http.ResponseWriter, *http.Request, []byte) ([]byte, error)
118+
AfterAPIResponse(http.ResponseWriter, *http.Request, []byte) error
119+
117120
BeforeAPICreate(http.ResponseWriter, *http.Request) error
118121
AfterAPICreate(http.ResponseWriter, *http.Request) error
119122

@@ -155,6 +158,28 @@ type Hookable interface {
155158

156159
##### Implementations
157160

161+
#### BeforeAPIResponse
162+
BeforeAPIResponse is called before content is sent over the Ponzu API, and
163+
provides an opportunity to modify the response data. If a non-nil `error` value
164+
is returned, a 500 Internal Server Error is sent instead of the response.
165+
166+
```go
167+
func (p *Post) BeforeAPIResponse(res http.ResponseWriter, req *http.Request, data []byte) ([]byte, error) {
168+
return data, nil
169+
}
170+
```
171+
172+
#### AfterAPIResponse
173+
AfterAPIResponse is called after content is sent over the Ponzu API, whether
174+
modified or not. The sent response data is available to the hook. A non-nil
175+
`error` return will simply generate a log message.
176+
177+
```go
178+
func (p *Post) AfterAPIResponse(res http.ResponseWriter, req *http.Request, data []byte) error {
179+
return nil
180+
}
181+
```
182+
158183
#### BeforeAPICreate
159184
BeforeAPICreate is called before an item is created via a 3rd-party client. If a
160185
non-nil `error` value is returned, the item will not be created/saved.

system/api/handlers.go

+73
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,31 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) {
100100
return
101101
}
102102

103+
// assert hookable
104+
get := it()
105+
hook, ok := get.(item.Hookable)
106+
if !ok {
107+
log.Println("[Response] error: Type", t, "does not implement item.Hookable or embed item.Item.")
108+
res.WriteHeader(http.StatusBadRequest)
109+
return
110+
}
111+
112+
// hook before response
113+
j, err = hook.BeforeAPIResponse(res, req, j)
114+
if err != nil {
115+
log.Println("[Response] error calling BeforeAPIResponse:", err)
116+
res.WriteHeader(http.StatusInternalServerError)
117+
return
118+
}
119+
103120
sendData(res, req, j)
121+
122+
// hook after response
123+
err = hook.AfterAPIResponse(res, req, j)
124+
if err != nil {
125+
log.Println("[Response] error calling AfterAPIResponse:", err)
126+
return
127+
}
104128
}
105129

106130
func contentHandler(res http.ResponseWriter, req *http.Request) {
@@ -156,7 +180,31 @@ func contentHandler(res http.ResponseWriter, req *http.Request) {
156180
return
157181
}
158182

183+
// assert hookable
184+
get := p
185+
hook, ok := get.(item.Hookable)
186+
if !ok {
187+
log.Println("[Response] error: Type", t, "does not implement item.Hookable or embed item.Item.")
188+
res.WriteHeader(http.StatusBadRequest)
189+
return
190+
}
191+
192+
// hook before response
193+
j, err = hook.BeforeAPIResponse(res, req, j)
194+
if err != nil {
195+
log.Println("[Response] error calling BeforeAPIResponse:", err)
196+
res.WriteHeader(http.StatusInternalServerError)
197+
return
198+
}
199+
159200
sendData(res, req, j)
201+
202+
// hook after response
203+
err = hook.AfterAPIResponse(res, req, j)
204+
if err != nil {
205+
log.Println("[Response] error calling AfterAPIResponse:", err)
206+
return
207+
}
160208
}
161209

162210
func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) {
@@ -206,7 +254,32 @@ func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) {
206254
return
207255
}
208256

257+
// assert hookable
258+
get := p
259+
hook, ok := get.(item.Hookable)
260+
if !ok {
261+
log.Println("[Response] error: Type", t, "does not implement item.Hookable or embed item.Item.")
262+
res.WriteHeader(http.StatusBadRequest)
263+
return
264+
}
265+
266+
// hook before response
267+
j, err = hook.BeforeAPIResponse(res, req, j)
268+
if err != nil {
269+
log.Println("[Response] error calling BeforeAPIResponse:", err)
270+
res.WriteHeader(http.StatusInternalServerError)
271+
return
272+
}
273+
209274
sendData(res, req, j)
275+
276+
// hook after response
277+
err = hook.AfterAPIResponse(res, req, j)
278+
if err != nil {
279+
log.Println("[Response] error calling AfterAPIResponse:", err)
280+
return
281+
}
282+
210283
}
211284

212285
func uploadsHandler(res http.ResponseWriter, req *http.Request) {

system/item/item.go

+20-7
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ func init() {
2424
// We store the compiled regex as the key
2525
// and assign the replacement as the map's value.
2626
rxList = map[*regexp.Regexp][]byte{
27-
regexp.MustCompile("`[-]+`"): []byte("-"),
28-
regexp.MustCompile("[[:space:]]"): []byte("-"),
29-
regexp.MustCompile("[[:blank:]]"): []byte(""),
30-
regexp.MustCompile("`[^a-z0-9]`i"): []byte("-"),
31-
regexp.MustCompile("[!/:-@[-`{-~]"): []byte(""),
32-
regexp.MustCompile("/[^\x20-\x7F]/"): []byte(""),
33-
regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i"): []byte("-"),
27+
regexp.MustCompile("`[-]+`"): []byte("-"),
28+
regexp.MustCompile("[[:space:]]"): []byte("-"),
29+
regexp.MustCompile("[[:blank:]]"): []byte(""),
30+
regexp.MustCompile("`[^a-z0-9]`i"): []byte("-"),
31+
regexp.MustCompile("[!/:-@[-`{-~]"): []byte(""),
32+
regexp.MustCompile("/[^\x20-\x7F]/"): []byte(""),
33+
regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i"): []byte("-"),
3434
regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i"): []byte("\\1"),
3535
}
3636
}
@@ -65,6 +65,9 @@ type Sortable interface {
6565
// to the different lifecycles/events a struct may encounter. Item implements
6666
// Hookable with no-ops so our user can override only whichever ones necessary.
6767
type Hookable interface {
68+
BeforeAPIResponse(http.ResponseWriter, *http.Request, []byte) ([]byte, error)
69+
AfterAPIResponse(http.ResponseWriter, *http.Request, []byte) error
70+
6871
BeforeAPICreate(http.ResponseWriter, *http.Request) error
6972
AfterAPICreate(http.ResponseWriter, *http.Request) error
7073

@@ -177,6 +180,16 @@ func (i Item) String() string {
177180
return fmt.Sprintf("Item ID: %s", i.UniqueID())
178181
}
179182

183+
// BeforeAPIResponse is a no-op to ensure structs which embed Item implement Hookable
184+
func (i Item) BeforeAPIResponse(res http.ResponseWriter, req *http.Request, data []byte) ([]byte, error) {
185+
return data, nil
186+
}
187+
188+
// AfterAPIResponse is a no-op to ensure structs which embed Item implement Hookable
189+
func (i Item) AfterAPIResponse(res http.ResponseWriter, req *http.Request, data []byte) error {
190+
return nil
191+
}
192+
180193
// BeforeAPICreate is a no-op to ensure structs which embed Item implement Hookable
181194
func (i Item) BeforeAPICreate(res http.ResponseWriter, req *http.Request) error {
182195
return nil

0 commit comments

Comments
 (0)