base64-lens 0.3.1 → 0.4
raw patch · 12 files changed
+1407/−522 lines, 12 filesdep +base64-lensdep +tastydep +tasty-hunitdep ~basedep ~base64dep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: base64-lens, tasty, tasty-hunit, tasty-quickcheck
Dependency ranges changed: base, base64, bytestring, lens, text, text-short
API changes (from Hackage documentation)
+ Data.Base64.Types.Lens: _Base64 :: forall (k :: Alphabet) a b p f. (Profunctor p, Functor f) => p a (f b) -> p (Base64 k a) (f (Base64 k b))
+ Data.Base64.Types.Lens: _NonStandard :: Prism' Alphabet ()
+ Data.Base64.Types.Lens: _StdPadded :: Prism' Alphabet ()
+ Data.Base64.Types.Lens: _UrlPadded :: Prism' Alphabet ()
+ Data.Base64.Types.Lens: _UrlUnpadded :: Prism' Alphabet ()
+ Data.Base64.Types.Lens: instance (t GHC.Types.~ Data.Base64.Types.Internal.Base64 k b) => Control.Lens.Wrapped.Rewrapped (Data.Base64.Types.Internal.Base64 k a) t
+ Data.Base64.Types.Lens: instance Control.Lens.Wrapped.Wrapped (Data.Base64.Types.Internal.Base64 k a)
+ Data.ByteString.Base64.Lens: _Base64Untyped :: Prism' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64UrlUntyped :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64Untyped :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64UrlUntyped :: Prism' ByteString ByteString
+ Data.ByteString.Short.Base64.Lens: _Base64Untyped :: Prism' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64UrlUntyped :: Prism' ShortByteString ShortByteString
+ Data.Text.Encoding.Base64.Lens: _Base64Untyped :: Prism' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64UrlUntyped :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64Untyped :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlUntyped :: Prism' Text Text
+ Data.Text.Short.Encoding.Base64.Lens: _Base64Untyped :: Prism' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64UrlUnpaddedUntyped :: Prism' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64UrlUntyped :: Prism' ShortText ShortText
- Data.ByteString.Base64.Lens: _Base64 :: Prism' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded ByteString) ByteString
- Data.ByteString.Base64.Lens: _Base64Lenient :: Iso' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard ByteString) ByteString
- Data.ByteString.Base64.Lens: _Base64Url :: Prism' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded ByteString) ByteString
- Data.ByteString.Base64.Lens: _Base64UrlLenient :: Iso' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard ByteString) ByteString
- Data.ByteString.Base64.Lens: _Base64UrlUnpadded :: Prism' ByteString ByteString
+ Data.ByteString.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ByteString) ByteString
- Data.ByteString.Base64.Lens: pattern Base64 :: ByteString -> ByteString
+ Data.ByteString.Base64.Lens: pattern Base64 :: ByteString -> Base64 'StdPadded ByteString
- Data.ByteString.Base64.Lens: pattern Base64Lenient :: ByteString -> ByteString
+ Data.ByteString.Base64.Lens: pattern Base64Lenient :: ByteString -> Base64 'NonStandard ByteString
- Data.ByteString.Base64.Lens: pattern Base64Url :: ByteString -> ByteString
+ Data.ByteString.Base64.Lens: pattern Base64Url :: ByteString -> Base64 'UrlPadded ByteString
- Data.ByteString.Base64.Lens: pattern Base64UrlLenient :: ByteString -> ByteString
+ Data.ByteString.Base64.Lens: pattern Base64UrlLenient :: ByteString -> Base64 'NonStandard ByteString
- Data.ByteString.Base64.Lens: pattern Base64UrlUnpadded :: ByteString -> ByteString
+ Data.ByteString.Base64.Lens: pattern Base64UrlUnpadded :: ByteString -> Base64 'UrlUnpadded ByteString
- Data.ByteString.Lazy.Base64.Lens: _Base64 :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded ByteString) ByteString
- Data.ByteString.Lazy.Base64.Lens: _Base64Lenient :: Iso' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard ByteString) ByteString
- Data.ByteString.Lazy.Base64.Lens: _Base64Url :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded ByteString) ByteString
- Data.ByteString.Lazy.Base64.Lens: _Base64UrlLenient :: Iso' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard ByteString) ByteString
- Data.ByteString.Lazy.Base64.Lens: _Base64UrlUnpadded :: Prism' ByteString ByteString
+ Data.ByteString.Lazy.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ByteString) ByteString
- Data.ByteString.Lazy.Base64.Lens: pattern Base64 :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base64.Lens: pattern Base64 :: ByteString -> Base64 'StdPadded ByteString
- Data.ByteString.Lazy.Base64.Lens: pattern Base64Lenient :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base64.Lens: pattern Base64Lenient :: ByteString -> Base64 'NonStandard ByteString
- Data.ByteString.Lazy.Base64.Lens: pattern Base64Url :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base64.Lens: pattern Base64Url :: ByteString -> Base64 'UrlPadded ByteString
- Data.ByteString.Lazy.Base64.Lens: pattern Base64UrlLenient :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base64.Lens: pattern Base64UrlLenient :: ByteString -> Base64 'NonStandard ByteString
- Data.ByteString.Lazy.Base64.Lens: pattern Base64UrlUnpadded :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base64.Lens: pattern Base64UrlUnpadded :: ByteString -> Base64 'UrlUnpadded ByteString
- Data.ByteString.Short.Base64.Lens: _Base64 :: Prism' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded ShortByteString) ShortByteString
- Data.ByteString.Short.Base64.Lens: _Base64Lenient :: Iso' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard ShortByteString) ShortByteString
- Data.ByteString.Short.Base64.Lens: _Base64Url :: Prism' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded ShortByteString) ShortByteString
- Data.ByteString.Short.Base64.Lens: _Base64UrlLenient :: Iso' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard ShortByteString) ShortByteString
- Data.ByteString.Short.Base64.Lens: _Base64UrlUnpadded :: Prism' ShortByteString ShortByteString
+ Data.ByteString.Short.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ShortByteString) ShortByteString
- Data.ByteString.Short.Base64.Lens: pattern Base64 :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base64.Lens: pattern Base64 :: ShortByteString -> Base64 'StdPadded ShortByteString
- Data.ByteString.Short.Base64.Lens: pattern Base64Lenient :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base64.Lens: pattern Base64Lenient :: ShortByteString -> Base64 'NonStandard ShortByteString
- Data.ByteString.Short.Base64.Lens: pattern Base64Url :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base64.Lens: pattern Base64Url :: ShortByteString -> Base64 'UrlPadded ShortByteString
- Data.ByteString.Short.Base64.Lens: pattern Base64UrlLenient :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base64.Lens: pattern Base64UrlLenient :: ShortByteString -> Base64 'NonStandard ShortByteString
- Data.ByteString.Short.Base64.Lens: pattern Base64UrlUnpadded :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base64.Lens: pattern Base64UrlUnpadded :: ShortByteString -> Base64 'UrlUnpadded ShortByteString
- Data.Text.Encoding.Base64.Error.Lens: _ConversionError :: Prism' (Base64Error err) err
+ Data.Text.Encoding.Base64.Error.Lens: _ConversionError :: forall a b p f. (Choice p, Applicative f) => p a (f b) -> p (Base64Error a) (f (Base64Error b))
- Data.Text.Encoding.Base64.Error.Lens: _DecodeError :: Prism' (Base64Error err) Text
+ Data.Text.Encoding.Base64.Error.Lens: _DecodeError :: forall err p f. (Choice p, Applicative f) => p Text (f Text) -> p (Base64Error err) (f (Base64Error err))
- Data.Text.Encoding.Base64.Lens: _Base64 :: Prism' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded Text) Text
- Data.Text.Encoding.Base64.Lens: _Base64Lenient :: Iso' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard Text) Text
- Data.Text.Encoding.Base64.Lens: _Base64Url :: Prism' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded Text) Text
- Data.Text.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard Text) Text
- Data.Text.Encoding.Base64.Lens: _Base64UrlUnpadded :: Prism' Text Text
+ Data.Text.Encoding.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded Text) Text
- Data.Text.Encoding.Base64.Lens: pattern Base64 :: Text -> Text
+ Data.Text.Encoding.Base64.Lens: pattern Base64 :: Text -> Base64 'StdPadded Text
- Data.Text.Encoding.Base64.Lens: pattern Base64Lenient :: Text -> Text
+ Data.Text.Encoding.Base64.Lens: pattern Base64Lenient :: Text -> Base64 'NonStandard Text
- Data.Text.Encoding.Base64.Lens: pattern Base64Url :: Text -> Text
+ Data.Text.Encoding.Base64.Lens: pattern Base64Url :: Text -> Base64 'UrlPadded Text
- Data.Text.Encoding.Base64.Lens: pattern Base64UrlLenient :: Text -> Text
+ Data.Text.Encoding.Base64.Lens: pattern Base64UrlLenient :: Text -> Base64 'NonStandard Text
- Data.Text.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: Text -> Text
+ Data.Text.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: Text -> Base64 'UrlUnpadded Text
- Data.Text.Lazy.Encoding.Base64.Lens: _Base64 :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded Text) Text
- Data.Text.Lazy.Encoding.Base64.Lens: _Base64Lenient :: Iso' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard Text) Text
- Data.Text.Lazy.Encoding.Base64.Lens: _Base64Url :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded Text) Text
- Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard Text) Text
- Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlUnpadded :: Prism' Text Text
+ Data.Text.Lazy.Encoding.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded Text) Text
- Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64 :: Text -> Text
+ Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64 :: Text -> Base64 'StdPadded Text
- Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64Lenient :: Text -> Text
+ Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64Lenient :: Text -> Base64 'NonStandard Text
- Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64Url :: Text -> Text
+ Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64Url :: Text -> Base64 'UrlPadded Text
- Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64UrlLenient :: Text -> Text
+ Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64UrlLenient :: Text -> Base64 'NonStandard Text
- Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: Text -> Text
+ Data.Text.Lazy.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: Text -> Base64 'UrlUnpadded Text
- Data.Text.Short.Encoding.Base64.Lens: _Base64 :: Prism' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64 :: Iso' (Base64 'StdPadded ShortText) ShortText
- Data.Text.Short.Encoding.Base64.Lens: _Base64Lenient :: Iso' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64Lenient :: Iso' (Base64 'NonStandard ShortText) ShortText
- Data.Text.Short.Encoding.Base64.Lens: _Base64Url :: Prism' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64Url :: Iso' (Base64 'UrlPadded ShortText) ShortText
- Data.Text.Short.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64UrlLenient :: Iso' (Base64 'NonStandard ShortText) ShortText
- Data.Text.Short.Encoding.Base64.Lens: _Base64UrlUnpadded :: Prism' ShortText ShortText
+ Data.Text.Short.Encoding.Base64.Lens: _Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ShortText) ShortText
- Data.Text.Short.Encoding.Base64.Lens: pattern Base64 :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base64.Lens: pattern Base64 :: ShortText -> Base64 'StdPadded ShortText
- Data.Text.Short.Encoding.Base64.Lens: pattern Base64Lenient :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base64.Lens: pattern Base64Lenient :: ShortText -> Base64 'NonStandard ShortText
- Data.Text.Short.Encoding.Base64.Lens: pattern Base64Url :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base64.Lens: pattern Base64Url :: ShortText -> Base64 'UrlPadded ShortText
- Data.Text.Short.Encoding.Base64.Lens: pattern Base64UrlLenient :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base64.Lens: pattern Base64UrlLenient :: ShortText -> Base64 'NonStandard ShortText
- Data.Text.Short.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base64.Lens: pattern Base64UrlUnpadded :: ShortText -> Base64 'UrlUnpadded ShortText
Files
- CHANGELOG.md +48/−0
- README.md +142/−2
- base64-lens.cabal +29/−10
- src/Data/Base64/Types/Lens.hs +90/−0
- src/Data/ByteString/Base64/Lens.hs +128/−84
- src/Data/ByteString/Lazy/Base64/Lens.hs +128/−84
- src/Data/ByteString/Short/Base64/Lens.hs +128/−84
- src/Data/Text/Encoding/Base64/Error/Lens.hs +7/−12
- src/Data/Text/Encoding/Base64/Lens.hs +129/−82
- src/Data/Text/Lazy/Encoding/Base64/Lens.hs +129/−82
- src/Data/Text/Short/Encoding/Base64/Lens.hs +129/−82
- test/Main.hs +320/−0
CHANGELOG.md view
@@ -1,6 +1,54 @@ # Revision history for base64-lens +## 0.4 — 2026-10-13++* Support `base64` 2.0 and GHC 9.6–9.14; update dependency bounds for+ `bytestring` 0.11–0.12, `text` 2.0–2.2, and `lens` 5.0–5.3.+* Change string optics from raw-string prisms to typed isomorphisms over+ `Base64` wrappers. Encoding records the alphabet; decoding consumes the+ corresponding wrapper. Validate untrusted standard input with `parseBase64`.+* Add `_Base64Untyped`, `_Base64UrlUntyped`, and+ `_Base64UrlUnpaddedUntyped` alongside the typed optics in all six string+ modules. These restore raw-string prisms using the checked untyped decoders.+* Change all string patterns to construct and match typed wrappers. Lenient+ optics and patterns use `NonStandard`; fix their patterns to encode on+ construction and decode on matching.+* Add `Data.Base64.Types.Lens` with a type-changing payload isomorphism,+ `Wrapped`/`Rewrapped` instances, and prisms for all `Alphabet` constructors.+ Payload optics preserve the alphabet index and do not validate or encode.+* Generalize `_ConversionError` to allow changing the error payload type.+* Simplify the string optics to direct compositions of the typed codecs.+ Use `Trustworthy` because `base64` 2.0's encoding modules are not Safe+ Haskell modules; the error optics remain `Safe`.+* Add regression and property tests for all representations, typed patterns,+ wrapper optics, alphabet constructors, untyped decoding and rejection, lazy+ chunk boundaries, binary data, Unicode, and errors.+ Replace the unused doctest stub.+* Refresh CI to test source distributions across supported compilers and both+ scalar and SIMD decoding backends.++### Migration from 0.3++For typed decoding, validate external standard-alphabet input with+`parseBase64`, then use `view` or `(^.)` with the corresponding typed optic.+Use `extractBase64` or `Data.Base64.Types.Lens._Base64` when an API needs the+encoded payload without its wrapper.++To keep using `preview` or `(^?)` with ordinary encoded strings, rename the+prisms:++| Previous name | Replacement |+| --- | --- |+| `_Base64` | `_Base64Untyped` |+| `_Base64Url` | `_Base64UrlUntyped` |+| `_Base64UrlUnpadded` | `_Base64UrlUnpaddedUntyped` |++The generic URL prism accepts both padded and unpadded input and reviews to+padded output. The unpadded URL prism rejects padding. Text decoding continues+to require UTF-8 payloads; use the ByteString optics for arbitrary binary data.+ ## 0.3.1+ * Add lazy and short text and bytestring optics * Add safe/trustworthy pragmas
README.md view
@@ -1,6 +1,146 @@ # Base64-lens -[](https://travis-ci.com/emilypi/base64-lens)+[](https://github.com/emilypi/base64-lens/actions/workflows/haskell-ci.yml) [](https://hackage.haskell.org/package/base64-lens) -This package provides optics and convenient pattern synonyms for the [base64](https://hackage.haskell.org/package/base64) library.+Optics and bidirectional patterns for+[base64](https://hackage.haskell.org/package/base64), covering strict, lazy, and+short `ByteString` and `Text` values. Version 0.4 supports `base64` 2.0,+`lens` 5.0–5.3, and GHC 9.6–9.14.++## Encoding and decoding++The string modules expose typed isomorphisms. Viewing decodes a `Base64` value;+reviewing encodes the payload and returns a wrapper with the matching alphabet.++```haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}++import Control.Lens+import Data.Base64.Types (Alphabet(..), Base64)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Base64.Lens as B64++encoded :: Base64 'StdPadded ByteString+encoded = B64._Base64 # "Sun"++-- "Sun"+decoded = encoded ^. B64._Base64++-- Right "Sun": validate external input before using the typed optic.+checked = fmap (view B64._Base64) (Base64.parseBase64 "U3Vu")+```++Each string module provides these optics, where `a` is its string type:++| Optic | Type |+| --- | --- |+| `_Base64` | `Iso' (Base64 'StdPadded a) a` |+| `_Base64Url` | `Iso' (Base64 'UrlPadded a) a` |+| `_Base64UrlUnpadded` | `Iso' (Base64 'UrlUnpadded a) a` |+| `_Base64Lenient` | `Iso' (Base64 'NonStandard a) a` |+| `_Base64UrlLenient` | `Iso' (Base64 'NonStandard a) a` |++The corresponding patterns construct encoded wrappers and match decoded+payloads. For example, `B64.Base64 "Sun"` has type+`Base64 'StdPadded ByteString`, and matching `B64.Base64 bytes` binds the+decoded bytes. Each pattern is complete for its input type.++The lenient optics ignore invalid Base64 characters, stop at padding, and+construct canonical padded output tagged as `NonStandard`. They are not lawful+isomorphisms for arbitrary input.++The `Text` optics expect the decoded payload to be UTF-8, as in the underlying+library. Invalid UTF-8 may throw an exception. Use the `ByteString` optics for+arbitrary binary payloads.++## Untyped decoding prisms++For ordinary encoded strings, each string module also provides:++| Prism | Checked decoder | Review produces |+| --- | --- | --- |+| `_Base64Untyped` | `decodeBase64Untyped` | Standard, padded Base64 |+| `_Base64UrlUntyped` | URL `decodeBase64Untyped` | URL-safe, padded Base64 |+| `_Base64UrlUnpaddedUntyped` | URL `decodeBase64UnpaddedUntyped` | URL-safe, unpadded Base64 |++Each has type `Prism' a a`, where `a` is the module's string type. Preview+returns the decoded payload on success and `Nothing` on a Base64 decoding+error; review encodes directly to an ordinary string.++```haskell+-- Just "Sun"+untypedDecoded = "U3Vu" ^? B64._Base64Untyped++-- Nothing: non-canonical padding bits+untypedRejected = "U3V=" ^? B64._Base64Untyped++-- "U3Vu"+untypedEncoded = B64._Base64Untyped # "Sun"+```++The URL prism accepts both padded and unpadded input, matching its decoder.+Reviewing a successful match adds any required padding, so it is not a lawful+prism over all accepted input. The unpadded URL prism rejects padding. The+`Text` prisms retain the underlying decoders' UTF-8 requirement described above.++## Wrapper and alphabet optics++`Data.Base64.Types.Lens` gives the wrapper its own namespace. Its `_Base64`+accesses the **encoded** payload and can change its representation while+preserving the alphabet index:++```haskell+import qualified Data.Base64.Types.Lens as Wrapped+import qualified Data.ByteString.Lazy as BL++-- "U3Vu": the encoded payload+payload = encoded ^. Wrapped._Base64++lazyEncoded :: Base64 'StdPadded BL.ByteString+lazyEncoded = encoded & Wrapped._Base64 %~ BL.fromStrict+```++Importing this module also enables the standard `Wrapped` and `Rewrapped`+instances, so `_Wrapped` and `_Wrapped'` work with `Base64`. Wrapping uses+`assertBase64`: it does not encode or validate. Payload changes must preserve+the claimed encoding if the result will be decoded. These optics keep the+alphabet index fixed; they do not silently change standard Base64 into URL-safe+Base64.++The same module supplies `_StdPadded`, `_UrlPadded`, `_UrlUnpadded`, and+`_NonStandard`, each a `Prism' Alphabet ()`.++`Data.Text.Encoding.Base64.Error.Lens` provides `_DecodeError` and a+type-changing `_ConversionError` prism for mapping conversion failures.++## Migrating from 0.3++The existing optic names now operate on `Base64` wrappers. Use `view` or `(^.)`+for typed decoding. Use `parseBase64` to validate external standard-alphabet+input, then decode the returned wrapper. Extract encoded output with+`Wrapped._Base64` or `extractBase64` when an API expects an ordinary string.++To retain raw-string prism usage, switch `_Base64` to `_Base64Untyped`,+`_Base64Url` to `_Base64UrlUntyped`, and `_Base64UrlUnpadded` to+`_Base64UrlUnpaddedUntyped`. These support `preview` or `(^?)` as before.++The lenient patterns also change direction: construction encodes and matching+decodes, consistently with the other patterns.++## Development++```sh+cabal update+cabal test --test-show-details=direct+cabal haddock+cabal check+```++To exercise the optional SIMD backend in `base64`:++```sh+cabal test --constraint='base64 +simd' --test-show-details=direct+```
base64-lens.cabal view
@@ -1,23 +1,23 @@ cabal-version: 2.0 name: base64-lens-version: 0.3.1+version: 0.4 synopsis: Optics for the Base64 library-description: Prisms and pattern synonyms for the Base64 library+description: Optics and pattern synonyms for Base64 values homepage: https://github.com/emilypi/base64-lens bug-reports: https://github.com/emilypi/base64-lens/issues license: BSD3 license-file: LICENSE author: Emily Pillmore maintainer: Emily Pillmore <emilypi@cohomolo.gy>-copyright: (c) 2019-2021 Emily Pillmore+copyright: (c) 2019-2026 Emily Pillmore category: Data build-type: Simple-extra-source-files:+extra-doc-files: CHANGELOG.md README.md tested-with:- GHC ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.4+ GHC ==9.6.7 || ==9.8.4 || ==9.10.3 || ==9.12.4 || ==9.14.1 source-repository head type: git@@ -25,6 +25,7 @@ library exposed-modules:+ Data.Base64.Types.Lens Data.ByteString.Base64.Lens Data.ByteString.Lazy.Base64.Lens Data.ByteString.Short.Base64.Lens@@ -34,13 +35,31 @@ Data.Text.Short.Encoding.Base64.Lens build-depends:- base >=4.10 && <5- , base64 ^>=0.4- , bytestring ^>=0.10- , lens >=4.0 && <5.1- , text ^>=1.2+ base >=4.18 && <4.23+ , base64 ^>=2.0+ , bytestring >=0.11 && <0.13+ , lens >=5.0 && <5.4+ , text >=2.0 && <2.3 , text-short ^>=0.1 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall++test-suite tests+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends:+ base+ , base64+ , base64-lens+ , bytestring+ , lens+ , tasty >=1.4 && <1.6+ , tasty-hunit ^>=0.10+ , tasty-quickcheck >=0.10 && <0.12+ , text+ , text-short
+ src/Data/Base64/Types/Lens.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE Trustworthy #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Base64.Types.Lens+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style+--+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable+--+-- Optics for the 'Base64' wrapper and 'Alphabet' constructors. Importing this+-- module also supplies 'Wrapped' and 'Rewrapped' instances for 'Base64'.+-- The wrapper optic accesses encoded data; the string-specific modules+-- contain the encoding and decoding optics.+--+module Data.Base64.Types.Lens+( -- * Encoded payload+ _Base64+ -- * Alphabets+, _StdPadded+, _UrlPadded+, _UrlUnpadded+, _NonStandard+) where++import Control.Lens (Iso, Prism', Wrapped(..), Rewrapped, _Wrapped, iso, prism')+import Data.Base64.Types (Alphabet(..), Base64, assertBase64, extractBase64)++-- $setup+-- >>> import Control.Lens+-- >>> import Data.Base64.Types+-- >>> import Data.Base64.Types.Lens+-- >>> :set -XDataKinds -XOverloadedStrings++-- | Access the encoded payload, preserving the alphabet index while allowing+-- the payload type to change. This is also available as '_Wrapped'.+--+-- Like 'assertBase64', wrapping does not encode or validate the value.+-- Use @parseBase64@ to validate untrusted standard-alphabet input. Changes+-- through this optic must preserve the claimed encoding if it will be decoded.+--+-- >>> view _Base64 (assertBase64 "U3Vu" :: Base64 'StdPadded String)+-- "U3Vu"+--+_Base64 :: Iso (Base64 k a) (Base64 k b) a b+_Base64 = _Wrapped+{-# INLINE _Base64 #-}++-- | The encoded payload, without decoding or validation.+instance Wrapped (Base64 k a) where+ type Unwrapped (Base64 k a) = a+ _Wrapped' = iso extractBase64 assertBase64+ {-# INLINE _Wrapped' #-}++-- | Change the payload type without changing the alphabet index.+instance (t ~ Base64 k b) => Rewrapped (Base64 k a) t++-- | The standard, padded alphabet.+_StdPadded :: Prism' Alphabet ()+_StdPadded = prism' (const StdPadded) $ \case+ StdPadded -> Just ()+ _ -> Nothing+{-# INLINE _StdPadded #-}++-- | The padded URL-safe alphabet.+_UrlPadded :: Prism' Alphabet ()+_UrlPadded = prism' (const UrlPadded) $ \case+ UrlPadded -> Just ()+ _ -> Nothing+{-# INLINE _UrlPadded #-}++-- | The unpadded URL-safe alphabet.+_UrlUnpadded :: Prism' Alphabet ()+_UrlUnpadded = prism' (const UrlUnpadded) $ \case+ UrlUnpadded -> Just ()+ _ -> Nothing+{-# INLINE _UrlUnpadded #-}++-- | A non-standard encoding, suitable for lenient decoding only.+_NonStandard :: Prism' Alphabet ()+_NonStandard = prism' (const NonStandard) $ \case+ NonStandard -> Just ()+ _ -> Nothing+{-# INLINE _NonStandard #-}
src/Data/ByteString/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.ByteString.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism''s and 'Iso''s for Base64-encoding and--- decoding 'ByteString' values.+-- Optics and patterns for Base64-encoded 'ByteString' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'B64.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.ByteString.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,133 +37,173 @@ , pattern Base64UrlLenient ) where --import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.ByteString (ByteString) import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64.URL as B64U - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.ByteString.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications ---- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism'' into the Base64 encoding of a 'ByteString' value+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' ByteString ByteString-_Base64 = prism' B64.encodeBase64' $ \s -> case B64.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded ByteString) ByteString+_Base64 = iso B64.decodeBase64 B64.encodeBase64' {-# INLINE _Base64 #-} --- | A 'Prism'' into the Base64url encoding of a 'ByteString' value+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' ByteString ByteString-_Base64Url = prism' B64U.encodeBase64' $ \s -> case B64U.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded ByteString) ByteString+_Base64Url = iso B64U.decodeBase64 B64U.encodeBase64' {-# INLINE _Base64Url #-} --- | A 'Prism'' into the Base64url encoding of a 'ByteString' value------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' ByteString ByteString-_Base64UrlUnpadded = prism' B64U.encodeBase64Unpadded' $ \s -> case B64U.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ByteString) ByteString+_Base64UrlUnpadded = iso B64U.decodeBase64Unpadded B64U.encodeBase64Unpadded' {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'ByteString' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' ByteString ByteString+_Base64Untyped = prism' (extractBase64 . B64.encodeBase64')+ (either (const Nothing) Just . B64.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' ByteString ByteString+_Base64UrlUntyped = prism' (extractBase64 . B64U.encodeBase64')+ (either (const Nothing) Just . B64U.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' ByteString ByteString+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . B64U.encodeBase64Unpadded')+ (either (const Nothing) Just . B64U.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' ByteString ByteString-_Base64Lenient = iso B64.decodeBase64Lenient B64.encodeBase64'+_Base64Lenient :: Iso' (Base64 'NonStandard ByteString) ByteString+_Base64Lenient = iso (B64.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64.encodeBase64')+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'ByteString' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' ByteString ByteString-_Base64UrlLenient = iso B64U.decodeBase64Lenient B64U.encodeBase64'+_Base64UrlLenient :: Iso' (Base64 'NonStandard ByteString) ByteString+_Base64UrlLenient = iso (B64U.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64U.encodeBase64')+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Bidirectional pattern synonym for base64-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: ByteString -> ByteString-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: ByteString -> Base64 'StdPadded ByteString+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Bidirectional pattern synonym for base64url-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: ByteString -> ByteString-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: ByteString -> Base64 'UrlPadded ByteString+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Bidirectional pattern synonym for unpadded base64url-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: ByteString -> ByteString-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: ByteString -> Base64 'UrlUnpadded ByteString+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: ByteString -> ByteString-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: ByteString -> Base64 'NonStandard ByteString+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: ByteString -> ByteString-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: ByteString -> Base64 'NonStandard ByteString+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
src/Data/ByteString/Lazy/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.ByteString.Lazy.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism''s and 'Iso''s for Base64-encoding and--- decoding 'ByteString' values.+-- Optics and patterns for Base64-encoded 'ByteString' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'BL64.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.ByteString.Lazy.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,133 +37,173 @@ , pattern Base64UrlLenient ) where --import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy.Base64 as BL64 import qualified Data.ByteString.Lazy.Base64.URL as BL64U - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.ByteString.Lazy.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications ---- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism'' into the Base64 encoding of a 'ByteString' value+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' ByteString ByteString-_Base64 = prism' BL64.encodeBase64' $ \s -> case BL64.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded ByteString) ByteString+_Base64 = iso BL64.decodeBase64 BL64.encodeBase64' {-# INLINE _Base64 #-} --- | A 'Prism'' into the Base64url encoding of a 'ByteString' value+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' ByteString ByteString-_Base64Url = prism' BL64U.encodeBase64' $ \s -> case BL64U.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded ByteString) ByteString+_Base64Url = iso BL64U.decodeBase64 BL64U.encodeBase64' {-# INLINE _Base64Url #-} --- | A 'Prism'' into the Base64url encoding of a 'ByteString' value------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' ByteString ByteString-_Base64UrlUnpadded = prism' BL64U.encodeBase64Unpadded' $ \s -> case BL64U.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ByteString) ByteString+_Base64UrlUnpadded = iso BL64U.decodeBase64Unpadded BL64U.encodeBase64Unpadded' {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'ByteString' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' ByteString ByteString+_Base64Untyped = prism' (extractBase64 . BL64.encodeBase64')+ (either (const Nothing) Just . BL64.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' ByteString ByteString+_Base64UrlUntyped = prism' (extractBase64 . BL64U.encodeBase64')+ (either (const Nothing) Just . BL64U.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' ByteString ByteString+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . BL64U.encodeBase64Unpadded')+ (either (const Nothing) Just . BL64U.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' ByteString ByteString-_Base64Lenient = iso BL64.decodeBase64Lenient BL64.encodeBase64'+_Base64Lenient :: Iso' (Base64 'NonStandard ByteString) ByteString+_Base64Lenient = iso (BL64.decodeBase64Lenient . extractBase64)+ (coerceBase64 . BL64.encodeBase64')+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'ByteString' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' ByteString ByteString-_Base64UrlLenient = iso BL64U.decodeBase64Lenient BL64U.encodeBase64'+_Base64UrlLenient :: Iso' (Base64 'NonStandard ByteString) ByteString+_Base64UrlLenient = iso (BL64U.decodeBase64Lenient . extractBase64)+ (coerceBase64 . BL64U.encodeBase64')+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Bidirectional pattern synonym for base64-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: ByteString -> ByteString-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: ByteString -> Base64 'StdPadded ByteString+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Bidirectional pattern synonym for base64url-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: ByteString -> ByteString-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: ByteString -> Base64 'UrlPadded ByteString+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Bidirectional pattern synonym for unpadded base64url-encoded 'ByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: ByteString -> ByteString-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: ByteString -> Base64 'UrlUnpadded ByteString+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: ByteString -> ByteString-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: ByteString -> Base64 'NonStandard ByteString+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: ByteString -> ByteString-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: ByteString -> Base64 'NonStandard ByteString+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
src/Data/ByteString/Short/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.ByteString.Short.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism''s and 'Iso''s for Base64-encoding and--- decoding 'ShortByteString' values.+-- Optics and patterns for Base64-encoded 'ShortByteString' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'BS64.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.ByteString.Short.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,133 +37,173 @@ , pattern Base64UrlLenient ) where --import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.ByteString.Short (ShortByteString) import qualified Data.ByteString.Short.Base64 as BS64 import qualified Data.ByteString.Short.Base64.URL as BS64U - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.ByteString.Short.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications ---- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism'' into the Base64 encoding of a 'ShortByteString' value+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' ShortByteString ShortByteString-_Base64 = prism' BS64.encodeBase64' $ \s -> case BS64.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded ShortByteString) ShortByteString+_Base64 = iso BS64.decodeBase64 BS64.encodeBase64' {-# INLINE _Base64 #-} --- | A 'Prism'' into the Base64url encoding of a 'ShortByteString' value+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' ShortByteString ShortByteString-_Base64Url = prism' BS64U.encodeBase64' $ \s -> case BS64U.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded ShortByteString) ShortByteString+_Base64Url = iso BS64U.decodeBase64 BS64U.encodeBase64' {-# INLINE _Base64Url #-} --- | A 'Prism'' into the Base64url encoding of a 'ShortByteString' value------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' ShortByteString ShortByteString-_Base64UrlUnpadded = prism' BS64U.encodeBase64Unpadded' $ \s -> case BS64U.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ShortByteString) ShortByteString+_Base64UrlUnpadded = iso BS64U.decodeBase64Unpadded BS64U.encodeBase64Unpadded' {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'ShortByteString' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' ShortByteString ShortByteString+_Base64Untyped = prism' (extractBase64 . BS64.encodeBase64')+ (either (const Nothing) Just . BS64.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' ShortByteString ShortByteString+_Base64UrlUntyped = prism' (extractBase64 . BS64U.encodeBase64')+ (either (const Nothing) Just . BS64U.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' ShortByteString ShortByteString+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . BS64U.encodeBase64Unpadded')+ (either (const Nothing) Just . BS64U.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' ShortByteString ShortByteString-_Base64Lenient = iso BS64.decodeBase64Lenient BS64.encodeBase64'+_Base64Lenient :: Iso' (Base64 'NonStandard ShortByteString) ShortByteString+_Base64Lenient = iso (BS64.decodeBase64Lenient . extractBase64)+ (coerceBase64 . BS64.encodeBase64')+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'ShortByteString' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso'. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' ShortByteString ShortByteString-_Base64UrlLenient = iso BS64U.decodeBase64Lenient BS64U.encodeBase64'+_Base64UrlLenient :: Iso' (Base64 'NonStandard ShortByteString) ShortByteString+_Base64UrlLenient = iso (BS64U.decodeBase64Lenient . extractBase64)+ (coerceBase64 . BS64U.encodeBase64')+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Bidirectional pattern synonym for base64-encoded 'ShortByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: ShortByteString -> ShortByteString-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: ShortByteString -> Base64 'StdPadded ShortByteString+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Bidirectional pattern synonym for base64url-encoded 'ShortByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: ShortByteString -> ShortByteString-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: ShortByteString -> Base64 'UrlPadded ShortByteString+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Bidirectional pattern synonym for unpadded base64url-encoded 'ShortByteString' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: ShortByteString -> ShortByteString-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: ShortByteString -> Base64 'UrlUnpadded ShortByteString+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ShortByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: ShortByteString -> ShortByteString-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: ShortByteString -> Base64 'NonStandard ShortByteString+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ShortByteString' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: ShortByteString -> ShortByteString-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: ShortByteString -> Base64 'NonStandard ShortByteString+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
src/Data/Text/Encoding/Base64/Error/Lens.hs view
@@ -1,13 +1,8 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-}-#if MIN_VERSION_lens(5,0,0) {-# LANGUAGE Safe #-}-#else-{-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.Text.Encoding.Base64.Error.Lens--- Copyright : (c) 2019-2021 Emily Pillmore+-- Copyright : (c) 2019-2026 Emily Pillmore -- License : BSD-style -- -- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>@@ -24,7 +19,7 @@ ) where -import Control.Lens+import Control.Lens (Prism, Prism', prism, prism') import Data.Text (Text) import Data.Text.Encoding.Base64.Error (Base64Error(..))@@ -37,9 +32,9 @@ DecodeError t -> Just t ConversionError{} -> Nothing --- | A 'Prism'' into the 'ConversionError' case of a 'Base64Error'+-- | Focus on the 'ConversionError' payload, allowing its type to change. ---_ConversionError :: Prism' (Base64Error err) err-_ConversionError = prism' ConversionError $ \case- ConversionError err -> Just err- DecodeError{} -> Nothing+_ConversionError :: Prism (Base64Error a) (Base64Error b) a b+_ConversionError = prism ConversionError $ \case+ ConversionError err -> Right err+ DecodeError t -> Left (DecodeError t)
src/Data/Text/Encoding/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.Text.Encoding.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism's and 'Iso''s Base64-encoding and--- decoding 'Text' values.+-- Optics and patterns for Base64-encoded 'Text' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'B64T.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.Text.Encoding.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,131 +37,174 @@ , pattern Base64UrlLenient ) where -import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.Text (Text) import qualified Data.Text.Encoding.Base64 as B64T import qualified Data.Text.Encoding.Base64.URL as B64TU - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.Text.Encoding.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications --- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism' into the Base64 encoding of a 'Text' value.+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' Text Text-_Base64 = prism' B64T.encodeBase64 $ \s -> case B64T.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded Text) Text+_Base64 = iso B64T.decodeBase64 B64T.encodeBase64 {-# INLINE _Base64 #-} --- | A 'Prism' into the Base64-url encoding of a 'Text' value.+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' Text Text-_Base64Url = prism' B64TU.encodeBase64 $ \s -> case B64TU.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded Text) Text+_Base64Url = iso B64TU.decodeBase64 B64TU.encodeBase64 {-# INLINE _Base64Url #-} --- | A 'Prism' into the Base64-url encoding of a 'Text' value.------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' Text Text-_Base64UrlUnpadded = prism' B64TU.encodeBase64Unpadded $ \s -> case B64TU.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded Text) Text+_Base64UrlUnpadded = iso B64TU.decodeBase64Unpadded B64TU.encodeBase64Unpadded {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'Text' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. All three untyped prisms+-- retain the decoders' UTF-8 requirement: invalid decoded UTF-8 may throw. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' Text Text+_Base64Untyped = prism' (extractBase64 . B64T.encodeBase64)+ (either (const Nothing) Just . B64T.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' Text Text+_Base64UrlUntyped = prism' (extractBase64 . B64TU.encodeBase64)+ (either (const Nothing) Just . B64TU.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' Text Text+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . B64TU.encodeBase64Unpadded)+ (either (const Nothing) Just . B64TU.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' Text Text-_Base64Lenient = iso B64T.decodeBase64Lenient B64T.encodeBase64+_Base64Lenient :: Iso' (Base64 'NonStandard Text) Text+_Base64Lenient = iso (B64T.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64T.encodeBase64)+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'Text' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' Text Text-_Base64UrlLenient = iso B64TU.decodeBase64Lenient B64TU.encodeBase64+_Base64UrlLenient :: Iso' (Base64 'NonStandard Text) Text+_Base64UrlLenient = iso (B64TU.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64TU.encodeBase64)+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Unidirectional pattern synonym for base64-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: Text -> Text-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: Text -> Base64 'StdPadded Text+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Unidirectional pattern synonym for base64url-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: Text -> Text-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: Text -> Base64 'UrlPadded Text+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Unidirectional pattern synonym for unpadded base64url-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: Text -> Text-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: Text -> Base64 'UrlUnpadded Text+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'Text' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: Text -> Text-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: Text -> Base64 'NonStandard Text+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'Text' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: Text -> Text-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: Text -> Base64 'NonStandard Text+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
src/Data/Text/Lazy/Encoding/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.Text.Lazy.Encoding.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism's and 'Iso''s Base64-encoding and--- decoding 'Text' values.+-- Optics and patterns for Base64-encoded 'Text' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'B64TL.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.Text.Lazy.Encoding.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,131 +37,174 @@ , pattern Base64UrlLenient ) where -import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy.Encoding.Base64 as B64TL import qualified Data.Text.Lazy.Encoding.Base64.URL as B64TLU - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.Text.Lazy.Encoding.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications --- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism' into the Base64 encoding of a 'Text' value.+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' Text Text-_Base64 = prism' B64TL.encodeBase64 $ \s -> case B64TL.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded Text) Text+_Base64 = iso B64TL.decodeBase64 B64TL.encodeBase64 {-# INLINE _Base64 #-} --- | A 'Prism' into the Base64-url encoding of a 'Text' value.+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' Text Text-_Base64Url = prism' B64TLU.encodeBase64 $ \s -> case B64TLU.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded Text) Text+_Base64Url = iso B64TLU.decodeBase64 B64TLU.encodeBase64 {-# INLINE _Base64Url #-} --- | A 'Prism' into the Base64-url encoding of a 'Text' value.------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' Text Text-_Base64UrlUnpadded = prism' B64TLU.encodeBase64Unpadded $ \s -> case B64TLU.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded Text) Text+_Base64UrlUnpadded = iso B64TLU.decodeBase64Unpadded B64TLU.encodeBase64Unpadded {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'Text' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. All three untyped prisms+-- retain the decoders' UTF-8 requirement: invalid decoded UTF-8 may throw. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' Text Text+_Base64Untyped = prism' (extractBase64 . B64TL.encodeBase64)+ (either (const Nothing) Just . B64TL.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' Text Text+_Base64UrlUntyped = prism' (extractBase64 . B64TLU.encodeBase64)+ (either (const Nothing) Just . B64TLU.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' Text Text+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . B64TLU.encodeBase64Unpadded)+ (either (const Nothing) Just . B64TLU.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' Text Text-_Base64Lenient = iso B64TL.decodeBase64Lenient B64TL.encodeBase64+_Base64Lenient :: Iso' (Base64 'NonStandard Text) Text+_Base64Lenient = iso (B64TL.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64TL.encodeBase64)+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'Text' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' Text Text-_Base64UrlLenient = iso B64TLU.decodeBase64Lenient B64TLU.encodeBase64+_Base64UrlLenient :: Iso' (Base64 'NonStandard Text) Text+_Base64UrlLenient = iso (B64TLU.decodeBase64Lenient . extractBase64)+ (coerceBase64 . B64TLU.encodeBase64)+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Unidirectional pattern synonym for base64-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: Text -> Text-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: Text -> Base64 'StdPadded Text+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Unidirectional pattern synonym for base64url-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: Text -> Text-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: Text -> Base64 'UrlPadded Text+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Unidirectional pattern synonym for unpadded base64url-encoded 'Text' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: Text -> Text-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: Text -> Base64 'UrlUnpadded Text+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'Text' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: Text -> Text-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: Text -> Base64 'NonStandard Text+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'Text' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: Text -> Text-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: Text -> Base64 'NonStandard Text+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
src/Data/Text/Short/Encoding/Base64/Lens.hs view
@@ -1,28 +1,32 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}-#if MIN_VERSION_lens(5,0,0)-{-# LANGUAGE Safe #-}-#else {-# LANGUAGE Trustworthy #-}-#endif -- | -- Module : Data.Text.Short.Encoding.Base64.Lens--- Copyright : (c) 2019-2021 Emily Pillmore--- License : BSD-style+-- Copyright : (c) 2019-2026 Emily Pillmore+-- License : BSD-style ----- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>--- Stability : Experimental--- Portability : non-portable+-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability : Experimental+-- Portability : non-portable ----- This module contains 'Prism's and 'Iso''s Base64-encoding and--- decoding 'ShortText' values.+-- Optics and patterns for Base64-encoded 'ShortText' values.+-- The alphabet is recorded in the t'Base64' wrapper. Validate untrusted+-- standard-alphabet input with 'TS64.parseBase64' before decoding,+-- or use '_Base64Untyped' to decode an ordinary string directly.+-- For access to the encoded payload itself, see "Data.Base64.Types.Lens". -- module Data.Text.Short.Encoding.Base64.Lens-( -- * Prisms+( -- * Encoding isomorphisms _Base64 , _Base64Url , _Base64UrlUnpadded+ -- * Untyped decoding prisms+, _Base64Untyped+, _Base64UrlUntyped+, _Base64UrlUnpaddedUntyped+ -- * Lenient decoding , _Base64Lenient , _Base64UrlLenient -- * Patterns@@ -33,131 +37,174 @@ , pattern Base64UrlLenient ) where -import Control.Lens-+import Control.Lens (Iso', Prism', iso, prism', view, (#))+import Data.Base64.Types (Alphabet(..), Base64, coerceBase64, extractBase64) import Data.Text.Short (ShortText) import qualified Data.Text.Short.Encoding.Base64 as TS64 import qualified Data.Text.Short.Encoding.Base64.URL as TS64U - -- $setup--- -- >>> import Control.Lens+-- >>> import Data.Base64.Types (assertBase64) -- >>> import Data.Text.Short.Encoding.Base64.Lens--- -- >>> :set -XOverloadedStrings--- >>> :set -XTypeApplications --- -------------------------------------------------------------------------- ----- Optics---- | A 'Prism' into the Base64 encoding of a 'ShortText' value.+-- | Decode standard, padded Base64, or encode in reverse. -- -- >>> _Base64 # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^? _Base64--- Just "Sun"+-- >>> (_Base64 # "Sun") ^. _Base64+-- "Sun" ---_Base64 :: Prism' ShortText ShortText-_Base64 = prism' TS64.encodeBase64 $ \s -> case TS64.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64 :: Iso' (Base64 'StdPadded ShortText) ShortText+_Base64 = iso TS64.decodeBase64 TS64.encodeBase64 {-# INLINE _Base64 #-} --- | A 'Prism' into the Base64-url encoding of a 'ShortText' value.+-- | Decode padded URL-safe Base64, or encode in reverse. -- -- >>> _Base64Url # "Sun" -- "U3Vu" ----- >>> "PDw_Pz8-Pg==" ^? _Base64Url--- Just "<<???>>"+-- >>> (_Base64Url # "Sun") ^. _Base64Url+-- "Sun" ---_Base64Url :: Prism' ShortText ShortText-_Base64Url = prism' TS64U.encodeBase64 $ \s -> case TS64U.decodeBase64 s of- Left _ -> Nothing- Right a -> Just a+_Base64Url :: Iso' (Base64 'UrlPadded ShortText) ShortText+_Base64Url = iso TS64U.decodeBase64 TS64U.encodeBase64 {-# INLINE _Base64Url #-} --- | A 'Prism' into the Base64-url encoding of a 'ShortText' value.------ Please note that unpadded variants should only be used--- when assumptions about the data can be made. In particular, if the length of--- the input is divisible by 3, then this is a safe function to call.+-- | Decode unpadded URL-safe Base64, or encode in reverse. ----- >>> _Base64UrlUnpadded # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlUnpadded # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^? _Base64UrlUnpadded--- Just "<<??>>"+-- >>> (_Base64UrlUnpadded # "Sun") ^. _Base64UrlUnpadded+-- "Sun" ---_Base64UrlUnpadded :: Prism' ShortText ShortText-_Base64UrlUnpadded = prism' TS64U.encodeBase64Unpadded $ \s -> case TS64U.decodeBase64Unpadded s of- Left _ -> Nothing- Right a -> Just a+_Base64UrlUnpadded :: Iso' (Base64 'UrlUnpadded ShortText) ShortText+_Base64UrlUnpadded = iso TS64U.decodeBase64Unpadded TS64U.encodeBase64Unpadded {-# INLINE _Base64UrlUnpadded #-} --- | An 'Iso'' into the Base64 encoding of a 'ShortText' value--- using lenient decoding.+-- | Decode a canonical, padded standard Base64 string, or encode in reverse.+-- Returns 'Nothing' on a Base64 decoding error. All three untyped prisms+-- retain the decoders' UTF-8 requirement: invalid decoded UTF-8 may throw. --+-- >>> _Base64Untyped # "Sun"+-- "U3Vu" ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- >>> "U3Vu" ^? _Base64Untyped+-- Just "Sun" --+-- >>> "U3V=" ^? _Base64Untyped+-- Nothing+--+_Base64Untyped :: Prism' ShortText ShortText+_Base64Untyped = prism' (extractBase64 . TS64.encodeBase64)+ (either (const Nothing) Just . TS64.decodeBase64Untyped)+{-# INLINE _Base64Untyped #-}++-- | Decode a padded or unpadded URL-safe Base64 string, or encode with padding+-- in reverse. Returns 'Nothing' on a Base64 decoding error.+--+-- Matching unpadded input and reviewing the result normalizes its padding,+-- so this is not a lawful 'Prism'' over all accepted input.+--+-- >>> _Base64UrlUntyped # "<<???>>"+-- "PDw_Pz8-Pg=="+--+-- >>> "PDw_Pz8-Pg" ^? _Base64UrlUntyped+-- Just "<<???>>"+--+_Base64UrlUntyped :: Prism' ShortText ShortText+_Base64UrlUntyped = prism' (extractBase64 . TS64U.encodeBase64)+ (either (const Nothing) Just . TS64U.decodeBase64Untyped)+{-# INLINE _Base64UrlUntyped #-}++-- | Decode a canonical, unpadded URL-safe Base64 string, or encode without+-- padding in reverse. Rejects padded input and other Base64 decoding errors.+--+-- >>> _Base64UrlUnpaddedUntyped # "f"+-- "Zg"+--+-- >>> "Zg" ^? _Base64UrlUnpaddedUntyped+-- Just "f"+--+-- >>> "Zg==" ^? _Base64UrlUnpaddedUntyped+-- Nothing+--+_Base64UrlUnpaddedUntyped :: Prism' ShortText ShortText+_Base64UrlUnpaddedUntyped = prism' (extractBase64 . TS64U.encodeBase64Unpadded)+ (either (const Nothing) Just . TS64U.decodeBase64UnpaddedUntyped)+{-# INLINE _Base64UrlUnpaddedUntyped #-}++-- | Decode lenient standard-alphabet Base64, or encode in reverse.+--+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'.+-- -- >>> _Base64Lenient # "Sun" -- "U3Vu" ----- >>> "U3Vu" ^. _Base64Lenient+-- >>> view _Base64Lenient (assertBase64 "U3!Vu") -- "Sun" ---_Base64Lenient :: Iso' ShortText ShortText-_Base64Lenient = iso TS64.decodeBase64Lenient TS64.encodeBase64+_Base64Lenient :: Iso' (Base64 'NonStandard ShortText) ShortText+_Base64Lenient = iso (TS64.decodeBase64Lenient . extractBase64)+ (coerceBase64 . TS64.encodeBase64)+{-# INLINE _Base64Lenient #-} --- | An 'Iso'' into the Base64url encoding of a 'ShortText' value--- using lenient decoding.---+-- | Decode lenient URL-safe Base64, or encode in reverse. ----- _Note:_ This is not a lawful 'Iso' in general. Please take care!+-- Ignores invalid characters and stops at the first padding character.+-- This is not a lawful 'Iso'' for arbitrary input. Construction produces+-- canonical padded output tagged as 'NonStandard'. ----- >>> _Base64UrlLenient # "<<??>>"--- "PDw_Pz4-"+-- >>> _Base64UrlLenient # "Sun"+-- "U3Vu" ----- >>> "PDw_Pz4-" ^. _Base64UrlLenient--- "<<??>>"+-- >>> view _Base64UrlLenient (assertBase64 "U3!Vu")+-- "Sun" ---_Base64UrlLenient :: Iso' ShortText ShortText-_Base64UrlLenient = iso TS64U.decodeBase64Lenient TS64U.encodeBase64+_Base64UrlLenient :: Iso' (Base64 'NonStandard ShortText) ShortText+_Base64UrlLenient = iso (TS64U.decodeBase64Lenient . extractBase64)+ (coerceBase64 . TS64U.encodeBase64)+{-# INLINE _Base64UrlLenient #-} -- -------------------------------------------------------------------------- -- -- Patterns --- | Unidirectional pattern synonym for base64-encoded 'ShortText' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64 :: ShortText -> ShortText-pattern Base64 a <- (preview _Base64 -> Just a) where+pattern Base64 :: ShortText -> Base64 'StdPadded ShortText+pattern Base64 a <- (view _Base64 -> a) where Base64 a = _Base64 # a+{-# COMPLETE Base64 #-} --- | Unidirectional pattern synonym for base64url-encoded 'ShortText' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Url :: ShortText -> ShortText-pattern Base64Url a <- (preview _Base64Url -> Just a) where+pattern Base64Url :: ShortText -> Base64 'UrlPadded ShortText+pattern Base64Url a <- (view _Base64Url -> a) where Base64Url a = _Base64Url # a+{-# COMPLETE Base64Url #-} --- | Unidirectional pattern synonym for unpadded base64url-encoded 'ShortText' values.+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlUnpadded :: ShortText -> ShortText-pattern Base64UrlUnpadded a <- (preview _Base64UrlUnpadded -> Just a) where+pattern Base64UrlUnpadded :: ShortText -> Base64 'UrlUnpadded ShortText+pattern Base64UrlUnpadded a <- (view _Base64UrlUnpadded -> a) where Base64UrlUnpadded a = _Base64UrlUnpadded # a+{-# COMPLETE Base64UrlUnpadded #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ShortText' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64Lenient :: ShortText -> ShortText-pattern Base64Lenient a <- (view (from _Base64Lenient) -> a) where- Base64Lenient a = view _Base64Lenient a+pattern Base64Lenient :: ShortText -> Base64 'NonStandard ShortText+pattern Base64Lenient a <- (view _Base64Lenient -> a) where+ Base64Lenient a = _Base64Lenient # a {-# COMPLETE Base64Lenient #-} --- | Bidirectional pattern synonym for leniently Base64-encoded 'ShortText' values+-- | Construct an encoded value or match its decoded payload. ---pattern Base64UrlLenient :: ShortText -> ShortText-pattern Base64UrlLenient a <- (view (from _Base64UrlLenient) -> a) where- Base64UrlLenient a = view _Base64UrlLenient a+pattern Base64UrlLenient :: ShortText -> Base64 'NonStandard ShortText+pattern Base64UrlLenient a <- (view _Base64UrlLenient -> a) where+ Base64UrlLenient a = _Base64UrlLenient # a {-# COMPLETE Base64UrlLenient #-}
+ test/Main.hs view
@@ -0,0 +1,320 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}++module Main (main) where++import Control.Lens+import Data.Base64.Types (Alphabet(..), Base64, assertBase64, extractBase64)+import qualified Data.Base64.Types.Lens as Wrapped+import Data.String (IsString)+import Data.Word (Word8)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Short as SBS+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Short as TS+import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Base64.Lens as B+import qualified Data.ByteString.Lazy.Base64.Lens as L+import qualified Data.ByteString.Short.Base64.Lens as S+import qualified Data.Text.Encoding.Base64.Lens as U+import qualified Data.Text.Lazy.Encoding.Base64.Lens as V+import qualified Data.Text.Short.Encoding.Base64.Lens as W+import Data.Text.Encoding.Base64.Error (Base64Error(..))+import Data.Text.Encoding.Base64.Error.Lens+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++-- Check both directions of each optic and its corresponding typed pattern.+data Codec k a = Codec+ { optic :: Iso' (Base64 k a) a+ , construct :: a -> Base64 k a+ , deconstruct :: Base64 k a -> a+ }++data Optics a = Optics+ { standard :: Codec 'StdPadded a+ , url :: Codec 'UrlPadded a+ , unpadded :: Codec 'UrlUnpadded a+ , lenient :: Codec 'NonStandard a+ , urlLenient :: Codec 'NonStandard a+ , standardUntyped :: Prism' a a+ , urlUntyped :: Prism' a a+ , unpaddedUntyped :: Prism' a a+ }++vectors :: IsString a => [(a, a, a)]+vectors =+ [ ("", "", ""), ("f", "Zg==", "Zg"), ("fo", "Zm8=", "Zm8")+ , ("foo", "Zm9v", "Zm9v"), ("foob", "Zm9vYg==", "Zm9vYg")+ , ("fooba", "Zm9vYmE=", "Zm9vYmE"), ("foobar", "Zm9vYmFy", "Zm9vYmFy")+ ]++main :: IO ()+main = defaultMain $ testGroup "base64-lens"+ [ suite "ByteString" byteStringOptics+ , suite "Lazy ByteString" lazyByteStringOptics+ , suite "ShortByteString" shortByteStringOptics+ , suite "Text" textOptics+ , suite "Lazy Text" lazyTextOptics+ , suite "ShortText" shortTextOptics+ , roundtrips "ByteString binary" byteStringOptics (BS.pack :: [Word8] -> BS.ByteString)+ , roundtrips "Lazy ByteString binary, one byte per chunk" lazyByteStringOptics+ (BL.fromChunks . map BS.singleton :: [Word8] -> BL.ByteString)+ , roundtrips "ShortByteString binary" shortByteStringOptics (SBS.pack :: [Word8] -> SBS.ShortByteString)+ , roundtrips "Text Unicode" textOptics T.pack+ , roundtrips "Lazy Text Unicode, one character per chunk" lazyTextOptics (TL.fromChunks . map T.singleton)+ , roundtrips "ShortText Unicode" shortTextOptics TS.pack+ , testGroup "Untyped prisms"+ [ untypedSuite "ByteString" byteStringOptics+ , untypedSuite "Lazy ByteString" lazyByteStringOptics+ , untypedSuite "ShortByteString" shortByteStringOptics+ , untypedSuite "Text" textOptics+ , untypedSuite "Lazy Text" lazyTextOptics+ , untypedSuite "ShortText" shortTextOptics+ ]+ , chunkedPrismTests+ , wrapperTests+ , alphabetTests+ , errorTests+ , testCase "parse untrusted input before typed decoding" $ do+ fmap (view B._Base64) (Base64.parseBase64 "U3Vu") @?= Right "Sun"+ case Base64.parseBase64 "U3V=" of+ Left _ -> pure ()+ Right _ -> assertFailure "Non-canonical input was accepted"+ ]++suite :: (Eq a, Show a, IsString a) => String -> Optics a -> TestTree+suite name o = testGroup name+ [ testGroup "RFC 4648 vectors"+ [ testCase (show raw) $ do+ check (standard o) raw encoded+ check (url o) raw encoded+ check (unpadded o) raw bare+ check (lenient o) raw encoded+ check (urlLenient o) raw encoded+ | (raw, encoded, bare) <- vectors+ ]+ , testCase "distinct alphabets and padding" $ do+ check (standard o) "<<???>>" "PDw/Pz8+Pg=="+ check (url o) "<<???>>" "PDw_Pz8-Pg=="+ check (unpadded o) "<<???>>" "PDw_Pz8-Pg"+ check (lenient o) "<<???>>" "PDw/Pz8+Pg=="+ check (urlLenient o) "<<???>>" "PDw_Pz8-Pg=="+ , testCase "lenient decoding of non-standard wrappers" $ do+ view (optic (lenient o)) (assertBase64 "U3!Vu") @?= "Sun"+ deconstruct (lenient o) (assertBase64 "U3!Vu") @?= "Sun"+ view (optic (urlLenient o)) (assertBase64 "PDw_!Pz8-Pg==") @?= "<<???>>"+ deconstruct (urlLenient o) (assertBase64 "PDw_!Pz8-Pg==") @?= "<<???>>"+ ]+ where+ check c raw encoded = do+ review (optic c) raw @?= assertBase64 encoded+ view (optic c) (assertBase64 encoded) @?= raw+ construct c raw @?= assertBase64 encoded+ deconstruct c (assertBase64 encoded) @?= raw++untypedSuite :: (Eq a, Show a, IsString a) => String -> Optics a -> TestTree+untypedSuite name o = testGroup name+ [ testGroup "RFC 4648 vectors"+ [ testCase (show raw) $ do+ review (standardUntyped o) raw @?= padded+ preview (standardUntyped o) padded @?= Just raw+ review (urlUntyped o) raw @?= padded+ preview (urlUntyped o) padded @?= Just raw+ preview (urlUntyped o) bare @?= Just raw+ review (unpaddedUntyped o) raw @?= bare+ preview (unpaddedUntyped o) bare @?= Just raw+ | (raw, padded, bare) <- vectors+ ]+ , testCase "distinct alphabets" $ do+ review (standardUntyped o) "<<???>>" @?= "PDw/Pz8+Pg=="+ review (urlUntyped o) "<<???>>" @?= "PDw_Pz8-Pg=="+ preview (standardUntyped o) "PDw/Pz8+Pg==" @?= Just "<<???>>"+ preview (urlUntyped o) "PDw_Pz8-Pg==" @?= Just "<<???>>"+ reject (standardUntyped o) "PDw_Pz8-Pg=="+ reject (urlUntyped o) "PDw/Pz8+Pg=="+ reject (unpaddedUntyped o) "PDw/Pz8+Pg"+ , testGroup "malformed standard encoding"+ [ testCase (show bad) $ reject (standardUntyped o) bad+ | bad <- ["%", "Z", "Zg", "Zg=", "Zg===", "=m9v", "Zm=v", "Zh==", "Zm9=", "Zg==AAAA", "Zm9v\n"]+ ]+ , testGroup "malformed URL encoding"+ [ testCase (show bad) $ reject (urlUntyped o) bad+ | bad <- ["%", "Z", "Zg=", "Zg===", "Zh==", "Zh", "Zm9", "Zm9=", "Zg==AAAA", "Zm9v\n"]+ ]+ , testGroup "malformed unpadded URL encoding"+ [ testCase (show bad) $ reject (unpaddedUntyped o) bad+ | bad <- ["%", "Z", "Zg==", "Zm8=", "Zh", "Zm9", "Zg==AAAA", "Zm9v\n"]+ ]+ , testCase "URL matching accepts optional padding; review normalizes it" $+ fmap (review (urlUntyped o)) (preview (urlUntyped o) "Zg") @?= Just "Zg=="+ ]+ where+ -- Failed matches must preserve the original encoded input.+ reject p bad = matching p bad @?= Left bad++chunkedPrismTests :: TestTree+chunkedPrismTests = testGroup "Untyped lazy input across chunk boundaries"+ [ testCase "ByteString, valid input" $ do+ preview L._Base64Untyped (bytes "Zg==") @?= Just "f"+ preview L._Base64UrlUntyped (bytes "Zg==") @?= Just "f"+ preview L._Base64UrlUnpaddedUntyped (bytes "Zg") @?= Just "f"+ , testCase "ByteString, invalid suffix" $ do+ matching L._Base64Untyped (bytes "Zg==AAAA") @?= Left (bytes "Zg==AAAA")+ matching L._Base64UrlUntyped (bytes "Zg==AAAA") @?= Left (bytes "Zg==AAAA")+ matching L._Base64UrlUnpaddedUntyped (bytes "Zm9v%") @?= Left (bytes "Zm9v%")+ , testCase "Text, valid input" $ do+ preview V._Base64Untyped (text "Zg==") @?= Just "f"+ preview V._Base64UrlUntyped (text "Zg==") @?= Just "f"+ preview V._Base64UrlUnpaddedUntyped (text "Zg") @?= Just "f"+ , testCase "Text, invalid suffix" $ do+ matching V._Base64Untyped (text "Zg==AAAA") @?= Left (text "Zg==AAAA")+ matching V._Base64UrlUntyped (text "Zg==AAAA") @?= Left (text "Zg==AAAA")+ matching V._Base64UrlUnpaddedUntyped (text "Zm9v%") @?= Left (text "Zm9v%")+ ]+ where+ bytes = BL.fromChunks . map BS.singleton . BS.unpack+ text = TL.fromChunks . map T.singleton++roundtrips :: (Arbitrary b, Show b, Eq a, Show a) => String -> Optics a -> (b -> a) -> TestTree+roundtrips name o pack = testGroup name+ [ laws "standard" (standard o)+ , laws "padded URL" (url o)+ , laws "unpadded URL" (unpadded o)+ , laws "lenient, canonical input" (lenient o)+ , laws "lenient URL, canonical input" (urlLenient o)+ , prismLaws "untyped standard" (standardUntyped o)+ , prismLaws "untyped URL, canonical padded input" (urlUntyped o)+ , prismLaws "untyped unpadded URL" (unpaddedUntyped o)+ ]+ where+ prismLaws codecName p = testGroup codecName+ [ testProperty "preview . review" $ \input ->+ let raw = pack input+ in preview (clonePrism p) (review (clonePrism p) raw) === Just raw+ , testProperty "review . preview, canonical input" $ \input ->+ let encoded = review (clonePrism p) (pack input)+ in fmap (review (clonePrism p)) (preview (clonePrism p) encoded) === Just encoded+ ]+ laws codecName c = testGroup codecName+ [ testProperty "decode . encode" $ \input ->+ let raw = pack input in view (optic c) (review (optic c) raw) === raw+ , testProperty "encode . decode" $ \input ->+ let encoded = review (optic c) (pack input)+ in review (optic c) (view (optic c) encoded) === encoded+ , testProperty "pattern match . construct" $ \input ->+ let raw = pack input in deconstruct c (construct c raw) === raw+ ]++wrapperTests :: TestTree+wrapperTests = testGroup "Base64 wrapper"+ [ testCase "payload access does not decode or validate" $+ view Wrapped._Base64 (assertBase64 "%" :: Base64 'StdPadded BS.ByteString) @?= "%"+ , testCase "type-changing payload conversion preserves the alphabet" $ do+ let original = Base64.encodeBase64' "Sun"+ converted = over Wrapped._Base64 BL.fromStrict original :: Base64 'StdPadded BL.ByteString+ view L._Base64 converted @?= "Sun"+ extractBase64 converted @?= "U3Vu"+ , testCase "Wrapped and Rewrapped instances" $ do+ let original = Base64.encodeBase64' "Sun"+ converted = over _Wrapped SBS.toShort original :: Base64 'StdPadded SBS.ShortByteString+ view S._Base64 converted @?= "Sun"+ view _Wrapped' original @?= "U3Vu"+ (review _Wrapped' "U3Vu" :: Base64 'StdPadded BS.ByteString) @?= original+ , testProperty "wrap . unwrap" $ \bytes ->+ let value = assertBase64 (BS.pack bytes) :: Base64 'NonStandard BS.ByteString+ in review Wrapped._Base64 (view Wrapped._Base64 value) === value+ , testProperty "unwrap . wrap" $ \bytes ->+ let raw = BS.pack bytes+ in view Wrapped._Base64 (review Wrapped._Base64 raw :: Base64 'NonStandard BS.ByteString) === raw+ ]++alphabetTests :: TestTree+alphabetTests = testGroup "Alphabet prisms"+ [ testGroup name+ [ testCase "construct and match" $ preview (clonePrism p) (review (clonePrism p) ()) @?= Just ()+ , testCase "select exactly its constructor" $+ map (preview (clonePrism p)) [StdPadded, UrlPadded, UrlUnpadded, NonStandard] @?=+ [if i == target then Just () else Nothing | i <- [0 .. 3 :: Int]]+ ]+ | (name, p, target) <-+ [ ("StdPadded", Wrapped._StdPadded, 0)+ , ("UrlPadded", Wrapped._UrlPadded, 1)+ , ("UrlUnpadded", Wrapped._UrlUnpadded, 2)+ , ("NonStandard", Wrapped._NonStandard, 3)+ ] :: [(String, APrism' Alphabet (), Int)]+ ]++errorTests :: TestTree+errorTests = testGroup "Error prisms"+ [ testCase "construct and select decode error" $ do+ (_DecodeError # "invalid" :: Base64Error Int) @?= DecodeError "invalid"+ preview _DecodeError (DecodeError "invalid" :: Base64Error Int) @?= Just "invalid"+ preview _DecodeError (ConversionError 42 :: Base64Error Int) @?= Nothing+ , testCase "construct and select conversion error" $ do+ (_ConversionError # 42 :: Base64Error Int) @?= ConversionError 42+ preview _ConversionError (ConversionError 42 :: Base64Error Int) @?= Just 42+ preview _ConversionError (DecodeError "invalid" :: Base64Error Int) @?= Nothing+ , testCase "change conversion error type" $ do+ over _ConversionError show (ConversionError 42 :: Base64Error Int) @?= ConversionError "42"+ over _ConversionError show (DecodeError "invalid" :: Base64Error Int) @?= DecodeError "invalid"+ ]++byteStringOptics :: Optics BS.ByteString+byteStringOptics = Optics+ (Codec B._Base64 B.Base64 (\(B.Base64 a) -> a))+ (Codec B._Base64Url B.Base64Url (\(B.Base64Url a) -> a))+ (Codec B._Base64UrlUnpadded B.Base64UrlUnpadded (\(B.Base64UrlUnpadded a) -> a))+ (Codec B._Base64Lenient B.Base64Lenient (\(B.Base64Lenient a) -> a))+ (Codec B._Base64UrlLenient B.Base64UrlLenient (\(B.Base64UrlLenient a) -> a))+ B._Base64Untyped B._Base64UrlUntyped B._Base64UrlUnpaddedUntyped++lazyByteStringOptics :: Optics BL.ByteString+lazyByteStringOptics = Optics+ (Codec L._Base64 L.Base64 (\(L.Base64 a) -> a))+ (Codec L._Base64Url L.Base64Url (\(L.Base64Url a) -> a))+ (Codec L._Base64UrlUnpadded L.Base64UrlUnpadded (\(L.Base64UrlUnpadded a) -> a))+ (Codec L._Base64Lenient L.Base64Lenient (\(L.Base64Lenient a) -> a))+ (Codec L._Base64UrlLenient L.Base64UrlLenient (\(L.Base64UrlLenient a) -> a))+ L._Base64Untyped L._Base64UrlUntyped L._Base64UrlUnpaddedUntyped++shortByteStringOptics :: Optics SBS.ShortByteString+shortByteStringOptics = Optics+ (Codec S._Base64 S.Base64 (\(S.Base64 a) -> a))+ (Codec S._Base64Url S.Base64Url (\(S.Base64Url a) -> a))+ (Codec S._Base64UrlUnpadded S.Base64UrlUnpadded (\(S.Base64UrlUnpadded a) -> a))+ (Codec S._Base64Lenient S.Base64Lenient (\(S.Base64Lenient a) -> a))+ (Codec S._Base64UrlLenient S.Base64UrlLenient (\(S.Base64UrlLenient a) -> a))+ S._Base64Untyped S._Base64UrlUntyped S._Base64UrlUnpaddedUntyped++textOptics :: Optics T.Text+textOptics = Optics+ (Codec U._Base64 U.Base64 (\(U.Base64 a) -> a))+ (Codec U._Base64Url U.Base64Url (\(U.Base64Url a) -> a))+ (Codec U._Base64UrlUnpadded U.Base64UrlUnpadded (\(U.Base64UrlUnpadded a) -> a))+ (Codec U._Base64Lenient U.Base64Lenient (\(U.Base64Lenient a) -> a))+ (Codec U._Base64UrlLenient U.Base64UrlLenient (\(U.Base64UrlLenient a) -> a))+ U._Base64Untyped U._Base64UrlUntyped U._Base64UrlUnpaddedUntyped++lazyTextOptics :: Optics TL.Text+lazyTextOptics = Optics+ (Codec V._Base64 V.Base64 (\(V.Base64 a) -> a))+ (Codec V._Base64Url V.Base64Url (\(V.Base64Url a) -> a))+ (Codec V._Base64UrlUnpadded V.Base64UrlUnpadded (\(V.Base64UrlUnpadded a) -> a))+ (Codec V._Base64Lenient V.Base64Lenient (\(V.Base64Lenient a) -> a))+ (Codec V._Base64UrlLenient V.Base64UrlLenient (\(V.Base64UrlLenient a) -> a))+ V._Base64Untyped V._Base64UrlUntyped V._Base64UrlUnpaddedUntyped++shortTextOptics :: Optics TS.ShortText+shortTextOptics = Optics+ (Codec W._Base64 W.Base64 (\(W.Base64 a) -> a))+ (Codec W._Base64Url W.Base64Url (\(W.Base64Url a) -> a))+ (Codec W._Base64UrlUnpadded W.Base64UrlUnpadded (\(W.Base64UrlUnpadded a) -> a))+ (Codec W._Base64Lenient W.Base64Lenient (\(W.Base64Lenient a) -> a))+ (Codec W._Base64UrlLenient W.Base64UrlLenient (\(W.Base64UrlLenient a) -> a))+ W._Base64Untyped W._Base64UrlUntyped W._Base64UrlUnpaddedUntyped