86 lines
2.1 KiB
Go
86 lines
2.1 KiB
Go
package middleware
|
|
|
|
import "net/http"
|
|
|
|
import "codit/internal/db"
|
|
import httpx "codit/internal/http"
|
|
|
|
// TxMode selects how a route-level transaction is started.
|
|
// TxImmediate is meaningful for SQLite, where it starts with BEGIN IMMEDIATE
|
|
// to reserve the writer slot early. For non-SQLite drivers, TxImmediate falls
|
|
// back to the normal deferred BeginStore, so it behaves like TxDeferred.
|
|
type TxMode int
|
|
|
|
const (
|
|
TxDeferred TxMode = iota
|
|
TxImmediate
|
|
)
|
|
|
|
type TxOutputMode int
|
|
|
|
const (
|
|
TxBuffered TxOutputMode = iota
|
|
TxUnbuffered
|
|
)
|
|
|
|
func TxRoute(store *db.Store, mode TxMode, output TxOutputMode, handler httpx.HandlerFunc) httpx.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request, params httpx.Params) {
|
|
var txStore *db.Store
|
|
var mit responseWriterStoringStatus
|
|
var err error
|
|
|
|
if isWebSocketUpgrade(r) { // exception for a websocket upgrade call
|
|
handler(w, r, params)
|
|
return
|
|
}
|
|
|
|
if mode == TxImmediate {
|
|
txStore, err = store.BeginImmediateStore(r.Context())
|
|
} else {
|
|
// all other values are TxDeferred
|
|
txStore, err = store.BeginStore(r.Context())
|
|
}
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
defer rollbackIfPanic(txStore)
|
|
|
|
r = WithStore(r, txStore)
|
|
|
|
if output == TxUnbuffered {
|
|
mit = newPassThroughStatusWriter(w)
|
|
handler(mit, r, params)
|
|
} else {
|
|
mit = newBufferedResponseWriter()
|
|
handler(mit, r, params)
|
|
}
|
|
|
|
if mit.Status() >= http.StatusBadRequest { // 4XX, 5XX, etc
|
|
_ = txStore.Rollback()
|
|
mit.FlushTo(w)
|
|
return
|
|
}
|
|
|
|
err = txStore.Commit()
|
|
if err != nil {
|
|
_ = txStore.Rollback()
|
|
if output == TxUnbuffered {
|
|
var ptrw *passThroughStatusWriter
|
|
var ok bool
|
|
ptrw, ok = mit.(*passThroughStatusWriter) // ok retrieval and assertion isn't needed as it can't fail
|
|
if ok && !ptrw.wroteHeader { // but keep it for defense
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
} else {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
runAfterCommit(r.Context())
|
|
|
|
mit.FlushTo(w) // [NOTE] this is no-op for unbuffered output
|
|
}
|
|
}
|