Files

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
}
}