diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,16 @@
 # Revision history for base64
 
+## 0.4.2.2
+
+* Add `NFData`, `Exception`, and `Generic` instances for `Base64Error` + `@since` annotations for new instances. ([#28](https://github.com/emilypi/base64/pull/28))
+* Doc improvements and add `-XTrustworty` and `-XSafe` annotations where needed. ([#27](https://github.com/emilypi/base64/pull/27))
+* Improve URL canonicity validation and correctness checking (now supports correct checking for unpadded Base64url) ([#26](https://github.com/emilypi/base64/pull/26))
+* Fixed perf regressions in decode
+* Test coverage is at 98%
+
 ## 0.4.2.1
 
-* [Security fix]: reject non-canonical base64 encoded values - ([#25](https://github.com/emilypi/base64/pull/25))
+* Security fix: reject non-canonical base64 encoded values - ([#25](https://github.com/emilypi/base64/pull/25))
 
 * Perf improvements
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -25,4 +25,4 @@
 - Checks for both validity and correctness of Base64 and Base64url encodings
 - Rejects non-canonical encodings that do not roundtrip in other base64 libraries like `ZE==`.
 
-There are no dependencies aside from those bundled with GHC, and the `ghc-byteorder` re-export.
+There are no dependencies aside from those bundled with GHC, `text-short`, and the `ghc-byteorder` re-export.
diff --git a/base64.cabal b/base64.cabal
--- a/base64.cabal
+++ b/base64.cabal
@@ -1,20 +1,20 @@
-cabal-version:      2.0
-name:               base64
-version:            0.4.2.1
-synopsis:           Fast RFC 4648-compliant Base64 encoding
+cabal-version:   2.0
+name:            base64
+version:         0.4.2.2
+synopsis:        Fast RFC 4648-compliant Base64 encoding
 description:
   RFC 4648-compliant padded and unpadded base64 and base64url encoding and decoding. This library provides
   performant encoding and decoding primitives, as well as support for textual values.
 
-homepage:           https://github.com/emilypi/base64
-bug-reports:        https://github.com/emilypi/base64/issues
-license:            BSD3
-license-file:       LICENSE
-author:             Emily Pillmore
-maintainer:         emilypi@cohomolo.gy
-copyright:          (c) 2019-2020 Emily Pillmore
-category:           Data
-build-type:         Simple
+homepage:        https://github.com/emilypi/base64
+bug-reports:     https://github.com/emilypi/base64/issues
+license:         BSD3
+license-file:    LICENSE
+author:          Emily Pillmore
+maintainer:      emilypi@cohomolo.gy
+copyright:       (c) 2019-2020 Emily Pillmore
+category:        Data
+build-type:      Simple
 extra-doc-files:
   CHANGELOG.md
   README.md
@@ -61,6 +61,7 @@
   build-depends:
       base           >=4.10     && <5
     , bytestring     ^>=0.10
+    , deepseq        >=1.4.3.0  && <1.4.5.0
     , ghc-byteorder  ^>=4.11.0.0
     , text           ^>=1.2
     , text-short     ^>=0.1
@@ -76,12 +77,11 @@
   other-modules:    Internal
   main-is:          Main.hs
   build-depends:
-      base                  >=4.10 && <5
+      base               >=4.10 && <5
     , base64
     , base64-bytestring
     , bytestring
     , QuickCheck
-    , quickcheck-instances
     , random-bytestring
     , tasty
     , tasty-hunit
diff --git a/src/Data/ByteString/Base64.hs b/src/Data/ByteString/Base64.hs
--- a/src/Data/ByteString/Base64.hs
+++ b/src/Data/ByteString/Base64.hs
@@ -1,23 +1,28 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Base64
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.ByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64
+-- encoding format. This includes lenient decoding variants, as well as
+-- internal and external validation for canonicity.
 --
 module Data.ByteString.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
+  -- * Decoding
 , decodeBase64
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -38,33 +43,54 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: ByteString -> Text
 encodeBase64 = T.decodeUtf8 . encodeBase64'
-{-# INLINE encodeBase64 #-}
+{-# inline encodeBase64 #-}
 
 -- | Encode a 'ByteString' value as a Base64 'ByteString'  value with padding.
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "Sun"
+-- "U3Vu"
+--
 encodeBase64' :: ByteString -> ByteString
 encodeBase64' = encodeBase64_ base64Table
-{-# INLINE encodeBase64' #-}
+{-# inline encodeBase64' #-}
 
 -- | Decode a padded Base64-encoded 'ByteString' value.
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: ByteString -> Either Text ByteString
-decodeBase64 bs@(PS _ _ l)
+decodeBase64 bs@(PS _ _ !l)
     | l == 0 = Right bs
     | r == 1 = Left "Base64-encoded bytestring has invalid size"
     | r /= 0 = Left "Base64-encoded bytestring requires padding"
     | otherwise = unsafeDupablePerformIO $ decodeBase64_ dlen decodeB64Table bs
   where
-    q = l `quot` 4
-    r = l `rem` 4
-    dlen = q * 3
-{-# INLINE decodeBase64 #-}
+    !q = l `quot` 4
+    !r = l `rem` 4
+    !dlen = q * 3
+{-# inline decodeBase64 #-}
 
 -- | Leniently decode an unpadded Base64-encoded 'ByteString' value. This function
 -- will not generate parse errors. If input data contains padding chars,
@@ -72,15 +98,41 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: ByteString -> ByteString
 decodeBase64Lenient = decodeBase64Lenient_ decodeB64Table
-{-# INLINE decodeBase64Lenient #-}
+{-# inline decodeBase64Lenient #-}
 
 -- | Tell whether a 'ByteString' value is base64 encoded.
 --
+-- This function will also detect non-canonical encodings such as @ZE==@, which are
+-- externally valid Base64url-encoded values, but are internally inconsistent "impossible"
+-- values.
+--
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: ByteString -> Bool
 isBase64 bs = isValidBase64 bs && isRight (decodeBase64 bs)
-{-# INLINE isBase64 #-}
+{-# inline isBase64 #-}
 
 -- | Tell whether a 'ByteString' value is a valid Base64 format.
 --
@@ -88,6 +140,20 @@
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ByteString' value, use 'isBase64'.
 --
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
+--
 isValidBase64 :: ByteString -> Bool
 isValidBase64 = validateBase64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-{-# INLINE isValidBase64 #-}
+{-# inline isValidBase64 #-}
diff --git a/src/Data/ByteString/Base64/Internal.hs b/src/Data/ByteString/Base64/Internal.hs
--- a/src/Data/ByteString/Base64/Internal.hs
+++ b/src/Data/ByteString/Base64/Internal.hs
@@ -18,6 +18,7 @@
 --
 module Data.ByteString.Base64.Internal
 ( validateBase64
+, validateBase64Url
 , validateLastPad
 ) where
 
@@ -52,6 +53,34 @@
 
         if f w then go (plusPtr p 1) end else return False
 {-# INLINE validateBase64 #-}
+
+validateBase64Url :: ByteString -> ByteString -> Bool
+validateBase64Url !alphabet bs@(PS _ _ l)
+    | l == 0 = True
+    | r == 0 = f bs
+    | r == 2 = f (BS.append bs "==")
+    | r == 3 = f (BS.append bs "=")
+    | otherwise = False
+
+  where
+    r = l `rem` 4
+
+    f (PS fp o n) = accursedUnutterablePerformIO $
+      withForeignPtr fp $ \p -> go (plusPtr p o) (plusPtr p (n + o))
+
+    go !p !end
+      | p == end = return True
+      | otherwise = do
+        w <- peek p
+
+        let check a
+              | a == 0x3d, plusPtr p 1 == end = True
+              | a == 0x3d, plusPtr p 2 == end = True
+              | a == 0x3d = False
+              | otherwise = BS.elem a alphabet
+
+        if check w then go (plusPtr p 1) end else return False
+{-# INLINE validateBase64Url #-}
 
 -- | This function checks that the last char of a bytestring is '='
 -- and, if true, fails with a message or completes some io action.
diff --git a/src/Data/ByteString/Base64/Internal/Head.hs b/src/Data/ByteString/Base64/Internal/Head.hs
--- a/src/Data/ByteString/Base64/Internal/Head.hs
+++ b/src/Data/ByteString/Base64/Internal/Head.hs
@@ -44,13 +44,13 @@
 
 
 encodeBase64_ :: EncodingTable -> ByteString -> ByteString
-encodeBase64_ (EncodingTable aptr efp) (PS sfp soff slen) =
+encodeBase64_ (EncodingTable !aptr !efp) (PS !sfp !soff !slen) =
     unsafeDupablePerformIO $ do
       dfp <- mallocPlainForeignPtrBytes dlen
       withForeignPtr dfp $ \dptr ->
         withForeignPtr sfp $ \sptr ->
         withForeignPtr efp $ \eptr -> do
-          let end = plusPtr sptr (soff + slen)
+          let !end = plusPtr sptr (soff + slen)
           innerLoop
             eptr
             (castPtr (plusPtr sptr soff))
@@ -58,7 +58,7 @@
             end
             (loopTail dfp aptr dptr (castPtr end))
   where
-    dlen = 4 * ((slen + 2) `div` 3)
+    !dlen = 4 * ((slen + 2) `div` 3)
 
 encodeBase64Nopad_ :: EncodingTable -> ByteString -> ByteString
 encodeBase64Nopad_ (EncodingTable !aptr !efp) (PS !sfp !soff !slen) =
@@ -92,17 +92,16 @@
     -> ForeignPtr Word8
     -> ByteString
     -> IO (Either Text ByteString)
-decodeBase64_ dlen dtfp (PS sfp soff slen) =
+decodeBase64_ !dlen !dtfp (PS !sfp !soff !slen) =
     withForeignPtr dtfp $ \dtable ->
     withForeignPtr sfp $ \sptr -> do
       dfp <- mallocPlainForeignPtrBytes dlen
-      withForeignPtr dfp $ \dptr ->
-        decodeLoop
-          dtable
+      withForeignPtr dfp $ \dptr -> do
+        let !end = plusPtr sptr (soff + slen)
+        decodeLoop dtable
           (plusPtr sptr soff)
-          dptr
-          (plusPtr sptr (soff + slen))
-          dfp
+          dptr end dfp
+{-# inline decodeBase64_ #-}
 
 decodeBase64Lenient_ :: ForeignPtr Word8 -> ByteString -> ByteString
 decodeBase64Lenient_ !dtfp (PS !sfp !soff !slen) = unsafeDupablePerformIO $
diff --git a/src/Data/ByteString/Base64/Internal/W16/Loop.hs b/src/Data/ByteString/Base64/Internal/W16/Loop.hs
--- a/src/Data/ByteString/Base64/Internal/W16/Loop.hs
+++ b/src/Data/ByteString/Base64/Internal/W16/Loop.hs
@@ -53,9 +53,9 @@
         !j <- w32 <$> peek (plusPtr src 1)
         !k <- w32 <$> peek (plusPtr src 2)
 
-        let !w = (shiftL i 16) .|. (shiftL j 8) .|. k
+        let !w = (unsafeShiftL i 16) .|. (unsafeShiftL j 8) .|. k
 
-        !x <- peekElemOff etable (fromIntegral (shiftR w 12))
+        !x <- peekElemOff etable (fromIntegral (unsafeShiftR w 12))
         !y <- peekElemOff etable (fromIntegral (w .&. 0xfff))
 
         poke dst x
@@ -76,22 +76,25 @@
     -> IO (Either Text ByteString)
 decodeLoop !dtable !sptr !dptr !end !dfp = go dptr sptr
   where
+    err :: Ptr Word8 -> IO (Either Text ByteString)
     err p = return . Left . T.pack
       $ "invalid character at offset: "
       ++ show (p `minusPtr` sptr)
 
+    padErr :: Ptr Word8 -> IO (Either Text ByteString)
     padErr p =  return . Left . T.pack
       $ "invalid padding at offset: "
       ++ show (p `minusPtr` sptr)
 
+    canonErr :: Ptr Word8 -> IO (Either Text ByteString)
     canonErr p = return . Left . T.pack
       $ "non-canonical encoding detected at offset: "
       ++ show (p `minusPtr` sptr)
 
     look :: Ptr Word8 -> IO Word32
     look !p = do
-      i <- peekByteOff @Word8 p 0
-      v <- peekByteOff @Word8 dtable (fromIntegral i)
+      !i <- peekByteOff @Word8 p 0
+      !v <- peekByteOff @Word8 dtable (fromIntegral i)
       return (fromIntegral v)
 
     go !dst !src
@@ -103,17 +106,17 @@
         finalChunk dst src a b c d
 
       | otherwise = do
-        !a <- look src
-        !b <- look (src `plusPtr` 1)
-        !c <- look (src `plusPtr` 2)
-        !d <- look (src `plusPtr` 3)
+        a <- look src
+        b <- look (src `plusPtr` 1)
+        c <- look (src `plusPtr` 2)
+        d <- look (src `plusPtr` 3)
         decodeChunk dst src a b c d
 
     -- | Decodes chunks of 4 bytes at a time, recombining into
     -- 3 bytes. Note that in the inner loop stage, no padding
     -- characters are admissible.
     --
-    decodeChunk !dst !src !a !b !c !d
+    decodeChunk !dst !src a b c d
      | a == 0x63 = padErr src
      | b == 0x63 = padErr (plusPtr src 1)
      | c == 0x63 = padErr (plusPtr src 2)
@@ -138,7 +141,7 @@
     -- 3 bytes. Note that in this stage, we can have padding chars
     -- but only in the final 2 positions.
     --
-    finalChunk !dst !src !a !b !c !d
+    finalChunk !dst !src a b c d
       | a == 0x63 = padErr src
       | b == 0x63 = padErr (plusPtr src 1)
       | c == 0x63 && d /= 0x63 = err (plusPtr src 3) -- make sure padding is coherent.
diff --git a/src/Data/ByteString/Base64/URL.hs b/src/Data/ByteString/Base64/URL.hs
--- a/src/Data/ByteString/Base64/URL.hs
+++ b/src/Data/ByteString/Base64/URL.hs
@@ -1,28 +1,32 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Base64.URL
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64-URL encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.ByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient decoding
+-- variants, as well as internal and external validation for canonicity.
 --
 module Data.ByteString.Base64.URL
-( encodeBase64
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
-, decodeBase64
 , encodeBase64Unpadded
 , encodeBase64Unpadded'
+  -- * Decoding
+, decodeBase64
 , decodeBase64Unpadded
 , decodeBase64Padded
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -44,6 +48,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: ByteString -> Text
 encodeBase64 = T.decodeUtf8 . encodeBase64'
 {-# INLINE encodeBase64 #-}
@@ -52,6 +61,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64' :: ByteString -> ByteString
 encodeBase64' = encodeBase64_ base64UrlTable
 
@@ -63,17 +77,31 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: ByteString -> Either Text ByteString
-decodeBase64 bs@(PS _ _ l)
+decodeBase64 bs@(PS _ _ !l)
     | l == 0 = Right bs
     | r == 0 = unsafeDupablePerformIO $ decodeBase64_ dlen decodeB64UrlTable bs
     | r == 2 = unsafeDupablePerformIO $ decodeBase64_ dlen decodeB64UrlTable (BS.append bs "==")
     | r == 3 = validateLastPad bs $ decodeBase64_ dlen decodeB64UrlTable (BS.append bs "=")
     | otherwise = Left "Base64-encoded bytestring has invalid size"
   where
-    q = l `quot` 4
-    r = l `rem` 4
-    dlen = q * 3
+    !q = l `quot` 4
+    !r = l `rem` 4
+    !dlen = q * 3
 {-# INLINE decodeBase64 #-}
 
 -- | Encode a 'ByteString' value as Base64url 'Text' without padding. Note that for Base64url,
@@ -82,6 +110,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: ByteString -> Text
 encodeBase64Unpadded = T.decodeUtf8 . encodeBase64Unpadded'
 {-# INLINE encodeBase64Unpadded #-}
@@ -92,6 +125,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded' "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded' :: ByteString -> ByteString
 encodeBase64Unpadded' = encodeBase64Nopad_ base64UrlTable
 
@@ -104,17 +142,25 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: ByteString -> Either Text ByteString
-decodeBase64Unpadded bs@(PS _ _ l)
+decodeBase64Unpadded bs@(PS _ _ !l)
     | l == 0 = Right bs
     | r == 0 = validateLastPad bs $ decodeBase64_ dlen decodeB64UrlTable bs
     | r == 2 = validateLastPad bs $ decodeBase64_ dlen decodeB64UrlTable (BS.append bs "==")
     | r == 3 = validateLastPad bs $ decodeBase64_ dlen decodeB64UrlTable (BS.append bs "=")
     | otherwise = Left "Base64-encoded bytestring has invalid size"
   where
-    q = l `quot` 4
-    r = l `rem` 4
-    dlen = q * 3
+    !q = l `quot` 4
+    !r = l `rem` 4
+    !dlen = q * 3
 {-# INLINE decodeBase64Unpadded #-}
 
 -- | Decode a padded Base64url-encoded 'ByteString' value. Input strings are
@@ -126,16 +172,24 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: ByteString -> Either Text ByteString
-decodeBase64Padded bs@(PS _ _ l)
+decodeBase64Padded bs@(PS _ _ !l)
     | l == 0 = Right bs
     | r == 1 = Left "Base64-encoded bytestring has invalid size"
     | r /= 0 = Left "Base64-encoded bytestring requires padding"
     | otherwise = unsafeDupablePerformIO $ decodeBase64_ dlen decodeB64UrlTable bs
   where
-    q = l `quot` 4
-    r = l `rem` 4
-    dlen = q * 3
+    !q = l `quot` 4
+    !r = l `rem` 4
+    !dlen = q * 3
 {-# INLINE decodeBase64Padded #-}
 
 -- | Leniently decode an unpadded Base64url-encoded 'ByteString'. This function
@@ -144,12 +198,35 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: ByteString -> ByteString
 decodeBase64Lenient = decodeBase64Lenient_ decodeB64UrlTable
 {-# INLINE decodeBase64Lenient #-}
 
--- | Tell whether a 'ByteString' is Base64url-encoded.
+-- | Tell whether a 'ByteString' is encoded in padded /or/ unpadded Base64url format.
 --
+-- This function will also detect non-canonical encodings such as @ZE==@, which are
+-- externally valid Base64url-encoded values, but are internally inconsistent "impossible"
+-- values.
+--
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: ByteString -> Bool
 isBase64Url bs = isValidBase64Url bs && isRight (decodeBase64 bs)
 {-# INLINE isBase64Url #-}
@@ -160,6 +237,17 @@
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ByteString' value, use 'isBase64Url'.
 --
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
+--
 isValidBase64Url :: ByteString -> Bool
-isValidBase64Url = validateBase64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+isValidBase64Url = validateBase64Url "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
 {-# INLINE isValidBase64Url #-}
diff --git a/src/Data/ByteString/Lazy/Base64.hs b/src/Data/ByteString/Lazy/Base64.hs
--- a/src/Data/ByteString/Lazy/Base64.hs
+++ b/src/Data/ByteString/Lazy/Base64.hs
@@ -1,23 +1,27 @@
-{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Lazy.Base64
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.Lazy.ByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64
+-- encoding format. This includes lenient decoding variants, as well as
+-- internal and external validation for canonicity.
 --
 module Data.ByteString.Lazy.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
+  -- * Decoding
 , decodeBase64
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -39,6 +43,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: ByteString -> TL.Text
 encodeBase64 = TL.decodeUtf8 . encodeBase64'
 {-# INLINE encodeBase64 #-}
@@ -47,6 +56,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "Sun"
+-- "U3Vu"
+--
 encodeBase64' :: ByteString -> ByteString
 encodeBase64' = fromChunks
   . fmap B64.encodeBase64'
@@ -58,6 +72,17 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: ByteString -> Either T.Text ByteString
 decodeBase64 = fmap (fromChunks . (:[]))
   . B64.decodeBase64
@@ -71,6 +96,17 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: ByteString -> ByteString
 decodeBase64Lenient = fromChunks
     . fmap B64.decodeBase64Lenient
@@ -81,6 +117,21 @@
 
 -- | Tell whether a 'ByteString' value is base64 encoded.
 --
+-- This function will also detect non-canonical encodings such as @ZE==@, which are
+-- externally valid Base64url-encoded values, but are internally inconsistent "impossible"
+-- values.
+--
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: ByteString -> Bool
 isBase64 bs = isValidBase64 bs && isRight (decodeBase64 bs)
 {-# INLINE isBase64 #-}
@@ -90,6 +141,20 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ByteString' value, use 'isBase64'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
 --
 isValidBase64 :: ByteString -> Bool
 isValidBase64 = go . toChunks
diff --git a/src/Data/ByteString/Lazy/Base64/URL.hs b/src/Data/ByteString/Lazy/Base64/URL.hs
--- a/src/Data/ByteString/Lazy/Base64/URL.hs
+++ b/src/Data/ByteString/Lazy/Base64/URL.hs
@@ -1,27 +1,32 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Lazy.Base64.URL
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.Lazy.ByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient
+-- decoding variants, as well as internal and external validation for canonicity.
 --
 module Data.ByteString.Lazy.Base64.URL
-( encodeBase64
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
 , encodeBase64Unpadded
 , encodeBase64Unpadded'
+  -- * Decoding
 , decodeBase64
-, decodeBase64Padded
 , decodeBase64Unpadded
+, decodeBase64Padded
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -44,6 +49,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: ByteString -> TL.Text
 encodeBase64 = TL.decodeUtf8 . encodeBase64'
 {-# INLINE encodeBase64 #-}
@@ -52,6 +62,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64' :: ByteString -> ByteString
 encodeBase64' = fromChunks
   . fmap B64U.encodeBase64'
@@ -66,6 +81,20 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: ByteString -> Either T.Text ByteString
 decodeBase64 = fmap (fromChunks . (:[]))
   . B64U.decodeBase64
@@ -79,6 +108,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: ByteString -> TL.Text
 encodeBase64Unpadded = TL.decodeUtf8 . encodeBase64Unpadded'
 {-# INLINE encodeBase64Unpadded #-}
@@ -89,6 +123,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded' "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded' :: ByteString -> ByteString
 encodeBase64Unpadded' = fromChunks
   . fmap B64U.encodeBase64Unpadded'
@@ -104,6 +143,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: ByteString -> Either T.Text ByteString
 decodeBase64Unpadded = fmap (fromChunks . (:[]))
   . B64U.decodeBase64Unpadded
@@ -120,6 +167,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: ByteString -> Either T.Text ByteString
 decodeBase64Padded = fmap (fromChunks . (:[]))
   . B64U.decodeBase64Padded
@@ -133,6 +188,14 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: ByteString -> ByteString
 decodeBase64Lenient = fromChunks
     . fmap B64U.decodeBase64Lenient
@@ -143,6 +206,17 @@
 
 -- | Tell whether a 'ByteString' is Base64url-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: ByteString -> Bool
 isBase64Url bs = isValidBase64Url bs && isRight (decodeBase64 bs)
 {-# INLINE isBase64Url #-}
@@ -152,6 +226,17 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ByteString' value, use 'isBase64Url'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
 --
 isValidBase64Url :: ByteString -> Bool
 isValidBase64Url = go . toChunks
diff --git a/src/Data/ByteString/Short/Base64.hs b/src/Data/ByteString/Short/Base64.hs
--- a/src/Data/ByteString/Short/Base64.hs
+++ b/src/Data/ByteString/Short/Base64.hs
@@ -1,21 +1,26 @@
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Short.Base64
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.Short.ShortByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64
+-- encoding format. This includes lenient decoding variants, as well as
+-- internal and external validation for canonicity.
 --
 module Data.ByteString.Short.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
+  -- * Decoding
 , decodeBase64
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -27,10 +32,15 @@
 import Data.Text.Short (ShortText)
 import Data.Text.Short.Unsafe (fromShortByteStringUnsafe)
 
--- | Encode a 'ShortByteString' value as Base64 'Text' with padding.
+-- | Encode a 'ShortByteString' value as Base64 'ShortText' with padding.
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: ShortByteString -> ShortText
 encodeBase64 = fromShortByteStringUnsafe . encodeBase64'
 {-# INLINE encodeBase64 #-}
@@ -39,6 +49,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "Sun"
+-- "U3Vu"
+--
 encodeBase64' :: ShortByteString -> ShortByteString
 encodeBase64' = toShort . B64.encodeBase64' . fromShort
 {-# INLINE encodeBase64' #-}
@@ -47,6 +62,17 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: ShortByteString -> Either Text ShortByteString
 decodeBase64 = fmap toShort . B64.decodeBase64 . fromShort
 {-# INLINE decodeBase64 #-}
@@ -57,12 +83,34 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: ShortByteString -> ShortByteString
 decodeBase64Lenient = toShort . B64.decodeBase64Lenient . fromShort
 {-# INLINE decodeBase64Lenient #-}
 
 -- | Tell whether a 'ShortByteString' value is base64 encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: ShortByteString -> Bool
 isBase64 = B64.isBase64 . fromShort
 {-# INLINE isBase64 #-}
@@ -72,6 +120,20 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ShortByteString' value, use 'isBase64'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
 --
 isValidBase64 :: ShortByteString -> Bool
 isValidBase64 = B64.isValidBase64 . fromShort
diff --git a/src/Data/ByteString/Short/Base64/URL.hs b/src/Data/ByteString/Short/Base64/URL.hs
--- a/src/Data/ByteString/Short/Base64/URL.hs
+++ b/src/Data/ByteString/Short/Base64/URL.hs
@@ -1,25 +1,30 @@
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.ByteString.Short.Base64.URL
 -- Copyright    : (c) 2019-2020 Emily Pillmore
 -- License      : BSD-style
 --
 -- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
--- Stability    : Experimental
--- Portability  : portable
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64url encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.ByteString.Short.ShortByteString'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient decoding
+-- variants, as well as internal and external validation for canonicity.
 --
 module Data.ByteString.Short.Base64.URL
-( encodeBase64
-, encodeBase64Unpadded
+( -- * Encoding
+  encodeBase64
 , encodeBase64'
+, encodeBase64Unpadded
 , encodeBase64Unpadded'
+  -- * Decoding
 , decodeBase64
-, decodeBase64Padded
 , decodeBase64Unpadded
+, decodeBase64Padded
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -35,6 +40,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: ShortByteString -> ShortText
 encodeBase64 = fromShortByteStringUnsafe . encodeBase64'
 {-# INLINE encodeBase64 #-}
@@ -43,6 +53,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64' "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64' :: ShortByteString -> ShortByteString
 encodeBase64' = toShort . B64U.encodeBase64' . fromShort
 
@@ -54,6 +69,20 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: ShortByteString -> Either Text ShortByteString
 decodeBase64 = fmap toShort . B64U.decodeBase64 . fromShort
 
@@ -65,6 +94,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: ShortByteString -> ShortText
 encodeBase64Unpadded = fromShortByteStringUnsafe . encodeBase64Unpadded'
 {-# INLINE encodeBase64Unpadded #-}
@@ -75,6 +109,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded' "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded' :: ShortByteString -> ShortByteString
 encodeBase64Unpadded' = toShort . B64U.encodeBase64Unpadded' . fromShort
 
@@ -87,6 +126,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: ShortByteString -> Either Text ShortByteString
 decodeBase64Unpadded = fmap toShort . B64U.decodeBase64Unpadded . fromShort
 {-# INLINE decodeBase64Unpadded #-}
@@ -100,6 +147,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: ShortByteString -> Either Text ShortByteString
 decodeBase64Padded = fmap toShort . B64U.decodeBase64Padded . fromShort
 {-# INLINE decodeBase64Padded #-}
@@ -110,12 +165,31 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: ShortByteString -> ShortByteString
 decodeBase64Lenient = toShort . B64U.decodeBase64Lenient . fromShort
 {-# INLINE decodeBase64Lenient #-}
 
 -- | Tell whether a 'ShortByteString' is Base64url-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: ShortByteString -> Bool
 isBase64Url = B64U.isBase64Url . fromShort
 {-# INLINE isBase64Url #-}
@@ -125,6 +199,17 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ShortByteString' value, use 'isBase64Url'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
 --
 isValidBase64Url :: ShortByteString -> Bool
 isValidBase64Url = B64U.isValidBase64Url . fromShort
diff --git a/src/Data/Text/Encoding/Base64.hs b/src/Data/Text/Encoding/Base64.hs
--- a/src/Data/Text/Encoding/Base64.hs
+++ b/src/Data/Text/Encoding/Base64.hs
@@ -1,22 +1,26 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Safe #-}
 -- |
 -- Module       : Data.Text.Encoding.Base64
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Text'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64
+-- encoding format. This includes lenient decoding variants, as well as
+-- internal and external validation for canonicity.
 --
 module Data.Text.Encoding.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -34,6 +38,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: Text -> Text
 encodeBase64 = B64.encodeBase64 . T.encodeUtf8
 {-# INLINE encodeBase64 #-}
@@ -48,6 +57,17 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: Text -> Either Text Text
 decodeBase64 = fmap T.decodeLatin1 . B64.decodeBase64 . T.encodeUtf8
 {-# INLINE decodeBase64 #-}
@@ -58,7 +78,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
 -- 'decodeBase64With' 'T.decodeUtf8''
@@ -82,6 +102,17 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: Text -> Text
 decodeBase64Lenient = T.decodeLatin1
     . B64.decodeBase64Lenient
@@ -90,6 +121,17 @@
 
 -- | Tell whether a 'Text' value is Base64-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: Text -> Bool
 isBase64 = B64.isBase64 . T.encodeUtf8
 {-# INLINE isBase64 #-}
@@ -99,6 +141,20 @@
 -- This will not tell you whether or not this is a correct Base64 representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'Text' value, use 'isBase64'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
 --
 isValidBase64 :: Text -> Bool
 isValidBase64 = B64.isValidBase64 . T.encodeUtf8
diff --git a/src/Data/Text/Encoding/Base64/Error.hs b/src/Data/Text/Encoding/Base64/Error.hs
--- a/src/Data/Text/Encoding/Base64/Error.hs
+++ b/src/Data/Text/Encoding/Base64/Error.hs
@@ -1,11 +1,13 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE Safe #-}
 -- |
 -- Module       : Data.Text.Encoding.Base64.Error
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
 -- This module contains the error types raised (not as exceptions!)
 -- in the decoding process.
@@ -15,8 +17,13 @@
 ) where
 
 
+import Control.DeepSeq (NFData(..))
+import Control.Exception (Exception(..))
+
 import Data.Text (Text)
 
+import GHC.Generics
+
 -- | This data type represents the type of decoding errors of
 -- various kinds as they pertain to decoding 'Text' values.
 -- Namely, to distinguish between decoding errors from opaque
@@ -29,4 +36,21 @@
   | ConversionError e
     -- ^ The error associated with the decoding failure
     -- as a result of the conversion process
-  deriving (Eq, Show)
+  deriving
+    ( Eq, Show
+    , Generic
+      -- ^ @since 4.2.2
+    )
+
+-- |
+--
+-- @since 4.2.2
+--
+instance Exception e => Exception (Base64Error e)
+
+
+-- |
+--
+-- @since 4.2.2
+--
+instance NFData e => NFData (Base64Error e)
diff --git a/src/Data/Text/Encoding/Base64/URL.hs b/src/Data/Text/Encoding/Base64/URL.hs
--- a/src/Data/Text/Encoding/Base64/URL.hs
+++ b/src/Data/Text/Encoding/Base64/URL.hs
@@ -1,26 +1,31 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Safe #-}
 -- |
 -- Module       : Data.Text.Encoding.Base64.URL
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64-URL encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Text'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient decoding
+-- variants, as well as internal and external validation for canonicity.
+--
 module Data.Text.Encoding.Base64.URL
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+, encodeBase64Unpadded
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
-, encodeBase64Unpadded
 , decodeBase64Unpadded
 , decodeBase64UnpaddedWith
 , decodeBase64Padded
 , decodeBase64PaddedWith
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -38,6 +43,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: Text -> Text
 encodeBase64 = B64U.encodeBase64 . T.encodeUtf8
 {-# INLINE encodeBase64 #-}
@@ -56,6 +66,20 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: Text -> Either Text Text
 decodeBase64 = fmap T.decodeLatin1 . B64U.decodeBase64 . T.encodeUtf8
 {-# INLINE decodeBase64 #-}
@@ -66,7 +90,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
 -- 'decodeBase64With' 'T.decodeUtf8''
@@ -90,6 +114,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: Text -> Text
 encodeBase64Unpadded = B64U.encodeBase64Unpadded . T.encodeUtf8
 {-# INLINE encodeBase64Unpadded #-}
@@ -104,6 +133,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: Text -> Either Text Text
 decodeBase64Unpadded = fmap T.decodeLatin1
     . B64U.decodeBase64Unpadded
@@ -116,10 +153,10 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
--- 'decodeBase64With' 'T.decodeUtf8''
+-- 'decodeBase64UnpaddedWith' 'T.decodeUtf8''
 --   :: 'ByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'Text'
 -- @
 --
@@ -144,6 +181,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: Text -> Either Text Text
 decodeBase64Padded = fmap T.decodeLatin1
     . B64U.decodeBase64Padded
@@ -156,10 +201,10 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
--- 'decodeBase64With' 'T.decodeUtf8''
+-- 'decodeBase64PaddedWith' 'T.decodeUtf8''
 --   :: 'ByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'Text'
 -- @
 --
@@ -180,6 +225,14 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: Text -> Text
 decodeBase64Lenient = T.decodeLatin1
     . B64U.decodeBase64Lenient
@@ -188,6 +241,17 @@
 
 -- | Tell whether a 'Text' value is Base64url-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: Text -> Bool
 isBase64Url = B64U.isBase64Url . T.encodeUtf8
 {-# INLINE isBase64Url #-}
@@ -197,6 +261,17 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'Text' value, use 'isBase64Url'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
 --
 isValidBase64Url :: Text -> Bool
 isValidBase64Url = B64U.isValidBase64Url . T.encodeUtf8
diff --git a/src/Data/Text/Lazy/Encoding/Base64.hs b/src/Data/Text/Lazy/Encoding/Base64.hs
--- a/src/Data/Text/Lazy/Encoding/Base64.hs
+++ b/src/Data/Text/Lazy/Encoding/Base64.hs
@@ -1,21 +1,26 @@
+{-# LANGUAGE Safe #-}
 -- |
 -- Module       : Data.Text.Lazy.Encoding.Base64
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Lazy.Text'-valued combinators
+-- implementing the RFC 4648 specification for the Base64
+-- encoding format. This includes lenient decoding variants, and
+-- external + internal validations for canonicity.
 --
 module Data.Text.Lazy.Encoding.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -34,6 +39,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: TL.Text -> TL.Text
 encodeBase64 = BL64.encodeBase64 . TL.encodeUtf8
 {-# INLINE encodeBase64 #-}
@@ -48,6 +58,17 @@
 -- caller to make sure inputs are valid. If unsure, defer to `decodeBase64With`
 -- and pass in a custom decode function.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: TL.Text -> Either T.Text TL.Text
 decodeBase64 = fmap TL.decodeLatin1 . BL64.decodeBase64 . TL.encodeUtf8
 {-# INLINE decodeBase64 #-}
@@ -58,7 +79,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
 -- 'decodeBase64With' 'TL.decodeUtf8''
@@ -82,6 +103,17 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: TL.Text -> TL.Text
 decodeBase64Lenient = TL.decodeLatin1
     . BL64.decodeBase64Lenient
@@ -90,6 +122,17 @@
 
 -- | Tell whether a 'TL.Text' value is Base64-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: TL.Text -> Bool
 isBase64 = BL64.isBase64 . TL.encodeUtf8
 {-# INLINE isBase64 #-}
@@ -99,6 +142,20 @@
 -- This will not tell you whether or not this is a correct Base64 representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'TL.Text' value, use 'isBase64'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
 --
 isValidBase64 :: TL.Text -> Bool
 isValidBase64 = BL64.isValidBase64 . TL.encodeUtf8
diff --git a/src/Data/Text/Lazy/Encoding/Base64/URL.hs b/src/Data/Text/Lazy/Encoding/Base64/URL.hs
--- a/src/Data/Text/Lazy/Encoding/Base64/URL.hs
+++ b/src/Data/Text/Lazy/Encoding/Base64/URL.hs
@@ -1,26 +1,31 @@
+{-# LANGUAGE Safe #-}
 -- |
 -- Module       : Data.Text.Lazy.Encoding.Base64.URL
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64-URL encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Lazy.Text'-valued combinators for
+-- implementing the RFC 4648 specification of the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient decoding
+-- variants, as well as internal and external validation for canonicity.
 --
 module Data.Text.Lazy.Encoding.Base64.URL
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+, encodeBase64Unpadded
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
-, encodeBase64Unpadded
 , decodeBase64Unpadded
 , decodeBase64UnpaddedWith
 , decodeBase64Padded
 , decodeBase64PaddedWith
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -39,6 +44,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: TL.Text -> TL.Text
 encodeBase64 = BL64U.encodeBase64 . TL.encodeUtf8
 {-# INLINE encodeBase64 #-}
@@ -57,6 +67,20 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: TL.Text -> Either T.Text TL.Text
 decodeBase64 = fmap TL.decodeLatin1 . BL64U.decodeBase64 . TL.encodeUtf8
 {-# INLINE decodeBase64 #-}
@@ -67,7 +91,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
 -- 'decodeBase64With' 'TL.decodeUtf8''
@@ -91,6 +115,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: TL.Text -> TL.Text
 encodeBase64Unpadded = BL64U.encodeBase64Unpadded . TL.encodeUtf8
 {-# INLINE encodeBase64Unpadded #-}
@@ -105,6 +134,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: TL.Text -> Either T.Text TL.Text
 decodeBase64Unpadded = fmap TL.decodeLatin1
     . BL64U.decodeBase64Unpadded
@@ -117,10 +154,10 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
--- 'decodeBase64With' 'TL.decodeUtf8''
+-- 'decodeBase64UnpaddedWith' 'TL.decodeUtf8''
 --   :: 'ByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'TL.Text'
 -- @
 --
@@ -145,6 +182,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: TL.Text -> Either T.Text TL.Text
 decodeBase64Padded = fmap TL.decodeLatin1
     . BL64U.decodeBase64Padded
@@ -157,11 +202,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
--- 'decodeBase64With' 'TL.decodeUtf8''
---   :: 'ByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'TL.Text'
+-- 'decodeBase64PaddedWith' 'T.decodeUtf8''
+--   :: 'ByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'Text'
 -- @
 --
 decodeBase64PaddedWith
@@ -181,14 +226,33 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: TL.Text -> TL.Text
 decodeBase64Lenient = TL.decodeLatin1
     . BL64U.decodeBase64Lenient
     . TL.encodeUtf8
 {-# INLINE decodeBase64Lenient #-}
 
--- | Tell whether a 'TL.Text' value is Base64url-encoded.
+-- | Tell whether a 'TL.Text' value is Base64url-encoded
 --
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: TL.Text -> Bool
 isBase64Url = BL64U.isBase64Url . TL.encodeUtf8
 {-# INLINE isBase64Url #-}
@@ -198,6 +262,17 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'TL.Text' value, use 'isBase64Url'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
 --
 isValidBase64Url :: TL.Text -> Bool
 isValidBase64Url = BL64U.isValidBase64Url . TL.encodeUtf8
diff --git a/src/Data/Text/Short/Encoding/Base64.hs b/src/Data/Text/Short/Encoding/Base64.hs
--- a/src/Data/Text/Short/Encoding/Base64.hs
+++ b/src/Data/Text/Short/Encoding/Base64.hs
@@ -1,22 +1,26 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
--- Module       : Data.Text.Encoding.Base64
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Module       : Data.Text.Short.Encoding.Base64
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64 encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Short.ShortText'-valued combinators
+-- implementing the RFC 4648 specification for the Base64
+-- encoding format. This includes lenient decoding variants, and
+-- external + internal validations for canonicity.
 --
 module Data.Text.Short.Encoding.Base64
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
 , decodeBase64Lenient
+  -- * Validation
 , isBase64
 , isValidBase64
 ) where
@@ -36,6 +40,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "Sun"
+-- "U3Vu"
+--
 encodeBase64 :: ShortText -> ShortText
 encodeBase64 = fromByteStringUnsafe
   . B64.encodeBase64'
@@ -52,6 +61,17 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "U3Vu"
+-- Right "Sun"
+--
+-- >>> decodeBase64 "U3V"
+-- Left "Base64-encoded bytestring requires padding"
+--
+-- >>> decodebase64 "U3V="
+-- Left "non-canonical encoding detected at offset: 2"
+--
 decodeBase64 :: ShortText -> Either Text ShortText
 decodeBase64 = fmap fromText . B64T.decodeBase64 . toText
 {-# INLINE decodeBase64 #-}
@@ -62,7 +82,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Example__:
 --
 -- @
 -- 'decodeBase64With' 'T.decodeUtf8''
@@ -86,12 +106,34 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "U3Vu"
+-- "Sun"
+--
+-- >>> decodeBase64Lenient "U3V"
+-- "Su"
+--
+-- >>> decodebase64Lenient "U3V="
+-- "Su"
+--
 decodeBase64Lenient :: ShortText -> ShortText
 decodeBase64Lenient = fromText . B64T.decodeBase64Lenient . toText
 {-# INLINE decodeBase64Lenient #-}
 
 -- | Tell whether a 'ShortText' value is Base64-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64 "U3Vu"
+-- True
+--
+-- >>> isBase64 "U3V"
+-- False
+--
+-- >>> isBase64 "U3V="
+-- False
+--
 isBase64 :: ShortText -> Bool
 isBase64 = B64.isBase64 . toByteString
 {-# INLINE isBase64 #-}
@@ -101,6 +143,20 @@
 -- This will not tell you whether or not this is a correct Base64 representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ShortText' value, use 'isBase64'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64 "U3Vu"
+-- True
+--
+-- >>> isValidBase64 "U3V"
+-- True
+--
+-- >>> isValidBase64 "U3V="
+-- True
+--
+-- >>> isValidBase64 "%"
+-- False
 --
 isValidBase64 :: ShortText -> Bool
 isValidBase64 = B64.isValidBase64 . toByteString
diff --git a/src/Data/Text/Short/Encoding/Base64/URL.hs b/src/Data/Text/Short/Encoding/Base64/URL.hs
--- a/src/Data/Text/Short/Encoding/Base64/URL.hs
+++ b/src/Data/Text/Short/Encoding/Base64/URL.hs
@@ -1,25 +1,31 @@
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.Text.Short.Encoding.Base64.URL
--- Copyright 	: (c) 2019-2020 Emily Pillmore
--- License	: BSD-style
+-- Copyright    : (c) 2019-2020 Emily Pillmore
+-- License      : BSD-style
 --
--- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
--- Stability	: Experimental
--- Portability	: portable
+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability    : stable
+-- Portability  : non-portable
 --
--- This module contains the combinators implementing the
--- RFC 4648 specification for the Base64-URL encoding including
--- unpadded and lenient variants
+-- This module contains 'Data.Text.Short.ShortText'-valued combinators
+-- implementing the RFC 4648 specification for the Base64url
+-- encoding format. This includes strictly padded/unpadded and lenient
+-- decoding variants, and external + internal validations for canonicity.
+--
 module Data.Text.Short.Encoding.Base64.URL
-( encodeBase64
+( -- * Encoding
+  encodeBase64
+, encodeBase64Unpadded
+  -- * Decoding
 , decodeBase64
 , decodeBase64With
-, encodeBase64Unpadded
 , decodeBase64Unpadded
 , decodeBase64UnpaddedWith
 , decodeBase64Padded
 , decodeBase64PaddedWith
 , decodeBase64Lenient
+  -- * Validation
 , isBase64Url
 , isValidBase64Url
 ) where
@@ -39,6 +45,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-5 RFC-4648 section 5>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64 "<<?>>"
+-- "PDw_Pj4="
+--
 encodeBase64 :: ShortText -> ShortText
 encodeBase64 = fromByteStringUnsafe
   . B64U.encodeBase64'
@@ -59,6 +70,20 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64 "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64 "PDw-Pg="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
+-- >>> decodeBase64 "PDw-Pg"
+-- Right "<<>>"
+--
 decodeBase64 :: ShortText -> Either Text ShortText
 decodeBase64 = fmap fromText . B64TU.decodeBase64 . toText
 {-# INLINE decodeBase64 #-}
@@ -69,7 +94,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
 -- 'decodeBase64With' 'T.decodeUtf8''
@@ -93,6 +118,11 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>
 --
+-- === __Examples__:
+--
+-- >>> encodeBase64Unpadded "<<?>>"
+-- "PDw_Pj4"
+--
 encodeBase64Unpadded :: ShortText -> ShortText
 encodeBase64Unpadded = fromByteStringUnsafe
   . B64U.encodeBase64Unpadded'
@@ -109,6 +139,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4"
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Unpadded "PDw_Pj4="
+-- Left "Base64-encoded bytestring has invalid padding"
+--
 decodeBase64Unpadded :: ShortText -> Either Text ShortText
 decodeBase64Unpadded = fmap fromText . B64TU.decodeBase64Unpadded . toText
 {-# INLINE decodeBase64Unpadded #-}
@@ -119,10 +157,10 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
--- 'decodeBase64With' 'T.decodeUtf8''
+-- 'decodeBase64UnpaddedWith' 'T.decodeUtf8''
 --   :: 'ShortByteString' -> 'Either' ('Base64Error' 'UnicodeException') 'ShortText'
 -- @
 --
@@ -147,6 +185,14 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Padded "PDw_Pj4="
+-- Right "<<?>>"
+--
+-- >>> decodeBase64Padded "PDw_Pj4"
+-- Left "Base64-encoded bytestring requires padding"
+--
 decodeBase64Padded :: ShortText -> Either Text ShortText
 decodeBase64Padded = fmap fromText . B64TU.decodeBase64Padded . toText
 {-# INLINE decodeBase64Padded #-}
@@ -157,7 +203,7 @@
 --
 -- See: <https://tools.ietf.org/html/rfc4648#section-4 RFC-4648 section 4>
 --
--- Example:
+-- === __Examples__:
 --
 -- @
 -- 'decodeBase64With' 'T.decodeUtf8''
@@ -181,12 +227,31 @@
 --
 -- __Note:__ This is not RFC 4648-compliant.
 --
+-- === __Examples__:
+--
+-- >>> decodeBase64Lenient "PDw_Pj4="
+-- "<<?>>"
+--
+-- >>> decodeBase64Lenient "PDw_%%%$}Pj4"
+-- "<<?>>"
+--
 decodeBase64Lenient :: ShortText -> ShortText
 decodeBase64Lenient = fromText . B64TU.decodeBase64Lenient . toText
 {-# INLINE decodeBase64Lenient #-}
 
 -- | Tell whether a 'ShortText' value is Base64url-encoded.
 --
+-- === __Examples__:
+--
+-- >>> isBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isBase64Url "PDw_Pj4"
+-- True
+--
+-- >>> isBase64Url "PDw_Pj"
+-- False
+--
 isBase64Url :: ShortText -> Bool
 isBase64Url = B64U.isBase64Url . toByteString
 {-# INLINE isBase64Url #-}
@@ -196,6 +261,17 @@
 -- This will not tell you whether or not this is a correct Base64url representation,
 -- only that it conforms to the correct shape. To check whether it is a true
 -- Base64 encoded 'ShortText' value, use 'isBase64Url'.
+--
+-- === __Examples__:
+--
+-- >>> isValidBase64Url "PDw_Pj4="
+-- True
+--
+-- >>> isValidBase64Url "PDw_Pj"
+-- True
+--
+-- >>> isValidBase64Url "%"
+-- False
 --
 isValidBase64Url :: ShortText -> Bool
 isValidBase64Url = B64U.isValidBase64Url . toByteString
diff --git a/test/Internal.hs b/test/Internal.hs
--- a/test/Internal.hs
+++ b/test/Internal.hs
@@ -44,7 +44,6 @@
 import "base64" Data.Text.Short.Encoding.Base64.URL as TS64U
 
 import Test.QuickCheck hiding (label)
-import Test.QuickCheck.Instances ()
 
 -- ------------------------------------------------------------------ --
 -- Test Harnesses
@@ -237,14 +236,44 @@
 -- ------------------------------------------------------------------ --
 -- Quickcheck instances
 
+instance Arbitrary BS.ByteString where
+    arbitrary = BS.pack <$> arbitrary
+    shrink xs = BS.pack <$> shrink (BS.unpack xs)
+
+instance CoArbitrary BS.ByteString where
+    coarbitrary = coarbitrary . BS.unpack
+
+instance Arbitrary LBS.ByteString where
+    arbitrary = LBS.pack <$> arbitrary
+    shrink xs = LBS.pack <$> shrink (LBS.unpack xs)
+
+instance CoArbitrary LBS.ByteString where
+    coarbitrary = coarbitrary . LBS.unpack
+
+instance Arbitrary SBS.ShortByteString where
+    arbitrary = SBS.pack <$> arbitrary
+    shrink xs = SBS.pack <$> shrink (SBS.unpack xs)
+
+instance CoArbitrary SBS.ShortByteString where
+    coarbitrary = coarbitrary . SBS.unpack
+
+instance Arbitrary T.Text where
+    arbitrary = T.pack <$> arbitrary
+    shrink xs = T.pack <$> shrink (T.unpack xs)
+
+instance Arbitrary TL.Text where
+    arbitrary = TL.pack <$> arbitrary
+    shrink xs = TL.pack <$> shrink (TL.unpack xs)
+
+instance CoArbitrary T.Text where
+    coarbitrary = coarbitrary . T.unpack
+
+instance CoArbitrary TL.Text where
+    coarbitrary = coarbitrary . TL.unpack
+
 instance Arbitrary TS.ShortText where
   arbitrary = TS.fromText <$> arbitrary
   shrink xs = fmap TS.fromText $ shrink (TS.toText xs)
 
 instance CoArbitrary TS.ShortText where
   coarbitrary = coarbitrary . TS.toText
-
-instance Function TS.ShortText where
-  function = functionMap
-    (T.unpack . TS.toText)
-    (TS.fromText . T.pack)
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -130,6 +130,8 @@
   [ paddingTests last_ length_
   , rfcVectors
   , offsetVectors
+  , validityTests
+  , canonicityTests
   ]
 
 -- | Make unit tests for textual 'decode*With' functions
@@ -373,7 +375,10 @@
       decode @a "eAoe=Ao=" @=? Left "invalid padding at offset: 4"
       decode @a "eAoeA=o=" @=? Left "invalid padding at offset: 5"
     ]
-  , testGroup "Non-canonical encodings fail and canonical encodings succeed"
+  ]
+
+canonicityTests :: forall a b proxy. Harness a b => proxy a -> TestTree
+canonicityTests _ =  testGroup "Canonicity unit tests"
     [ testCase "roundtrip for d ~ ZA==" $ do
       decode @a "ZE==" @=? Left "non-canonical encoding detected at offset: 1"
       decode @a "ZK==" @=? Left "non-canonical encoding detected at offset: 1"
@@ -393,7 +398,6 @@
       decode @a "Zm9vYmD=" @=? Left "non-canonical encoding detected at offset: 6"
       decode @a "Zm9vYmA=" @=? Right "foob`"
     ]
-  ]
 
 -- | Unit test trees for the `decode*With` family of text-valued functions
 --
@@ -462,5 +466,60 @@
       a <- either (assertFailure . show) pure $ decodeUrlNopad @a "PDw_Pz4-"
       b <- either (assertFailure . show) pure $ decodeUrlUnpaddedWith_ @a utf8 "PDw_Pz4-"
       a @=? b
+    ]
+  ]
+
+-- | Validity unit tests for the URL workflow
+--
+validityTests :: forall a b proxy. Harness a b => proxy a -> TestTree
+validityTests _ = testGroup "Validity and correctness unit tests"
+  [ testGroup "Validity unit tests"
+    [ testCase "Padding tests" $ do
+      not (validateUrl @a "P") @? "P"
+      validateUrl @a "PA" @? "PA"
+      validateUrl @a "PDw" @? "PDw"
+      validateUrl @a "PDw_" @? "PDw_"
+      validateUrl @a "PA==" @? "PA=="
+      validateUrl @a "PDw=" @? "PDw="
+      validateUrl @a "PDw_" @? "PDw_"
+
+    , testCase "Canonicity tests" $ do
+      validateUrl @a "ZK==" @? "ZK=="
+      validateUrl @a "ZE==" @? "ZE=="
+
+      validateUrl @a "ZA==" @? "ZA=="
+      validateUrl @a "ZK==" @? "ZK=="
+      validateUrl @a "ZK" @? "ZK"
+
+      validateUrl @a "ZmA=" @? "ZmA="
+      validateUrl @a "ZmC=" @? "ZmC="
+      validateUrl @a "ZmE" @? "ZmE"
+
+      validateUrl @a "Zm9vYmA=" @? "Zm9vYmA="
+      validateUrl @a "Zm9vYmC=" @? "Zm9vYmC="
+      validateUrl @a "Zm9vYmC" @? "Zm9vYmC"
+    ]
+  , testGroup "Correctness unit tests"
+    [ testCase "Padding tests" $ do
+      not (validateUrl @a "P") @? "P"
+      correctUrl @a "PA" @? "PA"
+      correctUrl @a "PDw" @? "PDw"
+      correctUrl @a "PDw_" @? "PDw_"
+      correctUrl @a "PA==" @? "PA=="
+      correctUrl @a "PDw=" @? "PDw="
+      correctUrl @a "PDw_" @? "PDw_"
+
+    , testCase "Canonicity tests" $ do
+      not (correctUrl @a "ZK==") @? "ZK=="
+      not (correctUrl @a "ZE==") @? "ZE=="
+      correctUrl @a "ZA==" @? "ZA=="
+
+      correctUrl @a "ZmA=" @? "ZmA="
+      not (correctUrl @a "ZmC=") @? "ZmC="
+      not (correctUrl @a "ZmD") @? "ZmD"
+
+      correctUrl @a "Zm9vYmA=" @? "Zm9vYmA="
+      not (correctUrl @a "Zm9vYmC=") @? "Zm9vYmC="
+      not (correctUrl @a "Zm9vYmC") @? "Zm9vYmC"
     ]
   ]
