datastar-hs-zlib-1.0.0.0: src/Hypermedia/Datastar/Compression/Zlib.hs
module Hypermedia.Datastar.Compression.Zlib
( gzip
, gzipWith
, deflate
, deflateWith
, defaultCompressionLevel
)
where
import Control.Exception (throwIO)
import Data.ByteString qualified as BS
import Data.ByteString.Builder qualified as BSB
import Data.ByteString.Lazy qualified as BL
import Data.Streaming.Zlib
( PopperRes (..)
, WindowBits (..)
, feedDeflate
, finishDeflate
, flushDeflate
, initDeflate
)
import Hypermedia.Datastar.WAI (Compressor (..))
defaultCompressionLevel :: Int
defaultCompressionLevel = 6
-- | A gzip 'Compressor' (@Content-Encoding: gzip@) at 'defaultCompressionLevel'.
gzip :: Compressor
gzip = gzipWith defaultCompressionLevel
-- | A gzip 'Compressor' at an explicit zlib level (0–9).
gzipWith :: Int -> Compressor
gzipWith level = zlibCompressor "gzip" level (WindowBits 31)
-- | A zlib/deflate 'Compressor' (@Content-Encoding: deflate@) at 'defaultCompressionLevel'.
deflate :: Compressor
deflate = deflateWith defaultCompressionLevel
-- | A zlib/deflate 'Compressor' at an explicit zlib level (0–9).
deflateWith :: Int -> Compressor
deflateWith level = zlibCompressor "deflate" level (WindowBits 15)
-- WindowBits 31 selects gzip framing, 15 selects zlib/deflate framing.
zlibCompressor :: BS.ByteString -> Int -> WindowBits -> Compressor
zlibCompressor enc level wbits = Compressor enc wrap
where
wrap rawWrite rawFlush = do
def <- initDeflate level wbits
let drain popper = do
res <- popper
case res of
PRDone -> pure ()
PRNext bs -> rawWrite (BSB.byteString bs) >> drain popper
PRError e -> throwIO e
write builder = do
popper <- feedDeflate def $ BL.toStrict $ BSB.toLazyByteString builder
drain popper
flush = drain (flushDeflate def) >> rawFlush
finish = drain (finishDeflate def) >> rawFlush
pure (write, flush, finish)