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