packages feed

binrep-1.1.0: src/Binrep/Type/Text/Internal.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Binrep.Type.Text.Internal where

import Data.Text ( Text )
import Data.ByteString qualified as B
import Rerefined.Refine

import System.IO.Unsafe qualified
import Control.Exception qualified
import Data.Text.Encoding.Error qualified
import Data.Bifunctor ( bimap )

type Bytes = B.ByteString

-- | A string of a given encoding, stored in the 'Text' type.
--
-- Essentially 'Text' carrying a proof that it can be successfully encoded into
-- the given encoding. For example, @'AsText' 'ASCII'@ means the 'Text' stored
-- is pure ASCII.
type AsText enc = Refined enc Text

-- | Bytestring encoders for text validated for a given encoding.
class Encode enc where
    -- | Encode text to bytes. Internal function, use 'encode'.
    encode' :: Text -> Bytes

class Decode enc where
    -- | Decode a 'ByteString' to 'Text' with an explicit encoding.
    --
    -- This is intended to be used with visible type applications.
    decode :: Bytes -> Either String (AsText enc)

--------------------------------------------------------------------------------
-- Internal helpers

-- | Helper for decoding a 'Bytes' to a 'Text' tagged with its encoding.
decodeText
    :: forall enc e
    .  (e -> String) -> (Bytes -> Either e Text) -> Bytes
    -> Either String (AsText enc)
decodeText g f = bimap g unsafeRefine . f

-- | Run an unsafe decoder safely.
--
-- Copied from @Data.Text.Encoding.decodeUtf8'@, so should be bulletproof?
wrapUnsafeDecoder
    :: (Bytes -> Text)
    -> Bytes -> Either Data.Text.Encoding.Error.UnicodeException Text
wrapUnsafeDecoder f =
      System.IO.Unsafe.unsafeDupablePerformIO
    . Control.Exception.try
    . Control.Exception.evaluate
    . f