Handling Haskell zlib decompression errors

2019-08-14 06:18发布

问题:

I have a String x which may or may not be gzip-compressed. Using the zlib library, I want to try decompressing x -- if it succeeds, the function shall return the compressed String. If not (i.e. x is not gzip-compressed) I want to simply return x.

As GZip.decompress generates an error if applied to a non-gzip string, I could use catch or similar, but I'm specifically asking for a solution that uses the zlib error handling mechanism.

How can I write a function, say decompressIfPossible :: ByteString -> ByteString that has the previously described characteristics? I'd prefer a Either String ByteString to represent either the error or the decompression result.

Note: This question intentionally does not show research effort, as it was immediately answered in a Q&A-style manner.

回答1:

The function from zlib you need to use here is called decompressWithErrors. Its value is the recursive DecompressStream data structure that you can fold to a ByteString using v:fromDecompressStream

Here's a full example of how to write the function you asked for:

import Data.Either
import Codec.Compression.Zlib.Internal
import qualified Data.ByteString.Lazy.Char8 as LB

-- | Convert & unfold the custom DecompressStream
--   error format from zlib to a Either
decompressStreamToEither :: DecompressStream -> Either String LB.ByteString
decompressStreamToEither (StreamError _ errmsg) = Left errmsg
decompressStreamToEither stream@(StreamChunk _ _) = Right $ fromDecompressStream stream
decompressStreamToEither StreamEnd = Right $ ""

-- | Decompress with explicit error handling
safeDecompress :: LB.ByteString -> Either String LB.ByteString
safeDecompress bstr = decompressStreamToEither $ decompressWithErrors gzipOrZlibFormat defaultDecompressParams bstr

-- | Decompress gzip, if it fails, return uncompressed String
decompressIfPossible :: LB.ByteString -> LB.ByteString
decompressIfPossible bstr =
    let conv (Left a) = bstr
        conv (Right a) = a
    in (conv . safeDecompress) bstr

Note that this example uses gzipOrZlibFormat which automatically detects if the header is a zlib or a gzip header.