+
+-- | Transform a Pipe of 'ByteString's expected to be UTF-8 encoded
+-- into a Pipe of Text with a replacement function of type @String -> Maybe Word8 -> Maybe Char@
+-- E.g. 'Data.Text.Encoding.Error.lenientDecode', which simply replaces bad bytes with \"�\"
+decodeUtf8With
+ :: Monad m
+ => TE.OnDecodeError
+ -> Producer ByteString m r -> Producer Text m (Producer ByteString m r)
+decodeUtf8With onErr = go (TE.streamDecodeUtf8With onErr)
+ where go dec p = do
+ x <- lift (next p)
+ case x of
+ Left r -> return (return r)
+ Right (chunk, p') -> do
+ let TE.Some text l dec' = dec chunk
+ if B.null l
+ then do
+ yield text
+ go dec' p'
+ else return $ do
+ yield l
+ p'
+{-# INLINEABLE decodeUtf8With #-}
+
+-- | A simple pipe from 'ByteString' to 'Text'; a decoding error will arise
+-- with any chunk that contains a sequence of bytes that is unreadable. Otherwise
+-- only few bytes will only be moved from one chunk to the next before decoding.
+pipeDecodeUtf8 :: Monad m => Pipe ByteString Text m r
+pipeDecodeUtf8 = go TE.streamDecodeUtf8
+ where go dec = do chunk <- await
+ case dec chunk of
+ TE.Some text l dec' -> do yield text
+ go dec'
+{-# INLINEABLE pipeDecodeUtf8 #-}
+
+-- | A simple pipe from 'ByteString' to 'Text' using a replacement function.
+pipeDecodeUtf8With
+ :: Monad m
+ => TE.OnDecodeError
+ -> Pipe ByteString Text m r
+pipeDecodeUtf8With onErr = go (TE.streamDecodeUtf8With onErr)
+ where go dec = do chunk <- await
+ case dec chunk of
+ TE.Some text l dec' -> do yield text
+ go dec'
+{-# INLINEABLE pipeDecodeUtf8With #-}