mfmts-1.0.0.0: multibase/MultiFormats/MultiBase.hs
-- | Module : MultiFormats.MultiBase
-- Description : Functions for working with multibase data.
-- Copyright : Zoey McBride (c) 2026
-- License : AGPL-3.0-or-later
-- Maintainer : zoeymcbride@mailbox.org
-- Stability : experimental
module MultiFormats.MultiBase
( MultiBase (prefixChar, baseEncoder, baseDecoder),
mkMultiBase,
-- * Decoders
decodeFromPrefix,
decodeCIDv0,
-- * Query functions for MultiBase information
hasValidPrefix,
resolvePrefix,
resolveMultiBase,
)
where
import Data.BaseSystem (BaseSystem, Decoder, Encoder)
import Data.BaseSystem qualified as Bases
import Data.BaseSystems qualified as Bases
import Data.Function (on)
import Data.List (uncons)
import Data.Map.Strict qualified as Map
import Data.Maybe (isJust)
import Text.Printf (printf)
data MultiBase = MultiBase
{ prefixChar :: Char,
baseEncoder :: Encoder,
baseDecoder :: Decoder,
baseName :: String
}
instance Eq MultiBase where
(==) = (==) `on` prefixChar
instance Ord MultiBase where
compare = compare `on` prefixChar
instance Show MultiBase where
show mb = printf "multi%s('%c')" (baseName mb) (prefixChar mb)
-- | Implements encoder/decoder for multibase systems when the prefix is known
-- ahead of time.
instance BaseSystem MultiBase where
encoder mb bytes = prefixChar mb : baseEncoder mb bytes
decoder _ = decodeFromPrefix
-- | Constructs a Multibase from a BaseSystem.
mkMultiBase :: (Show b, BaseSystem b) => b -> Char -> MultiBase
mkMultiBase bs prefix =
MultiBase
{ prefixChar = prefix,
baseEncoder = Bases.encoder bs,
baseDecoder = Bases.decoder bs,
baseName = show bs
}
-- | Resolves a MultiBase system from a Char.
resolvePrefix :: Char -> Maybe MultiBase
resolvePrefix prefix = Map.lookup prefix prefixTable <*> pure prefix
where
-- https://github.com/multiformats/multibase/blob/master/multibase.csv
prefixTable =
Map.fromList
[ ('0', mkMultiBase Bases.base2),
('9', mkMultiBase Bases.base10),
('f', mkMultiBase Bases.base16lower),
('F', mkMultiBase Bases.base16upper),
('b', mkMultiBase Bases.base32lowerNP),
('B', mkMultiBase Bases.base32upperNP),
('c', mkMultiBase Bases.base32lower),
('C', mkMultiBase Bases.base32upper),
('v', mkMultiBase Bases.base32hexlowerNP),
('V', mkMultiBase Bases.base32hexupperNP),
('t', mkMultiBase Bases.base32hexlower),
('T', mkMultiBase Bases.base32hexupper),
('z', mkMultiBase Bases.base58btc),
('m', mkMultiBase Bases.base64NP),
('M', mkMultiBase Bases.base64),
('u', mkMultiBase Bases.base64urlNP),
('U', mkMultiBase Bases.base64url)
]
-- | Resolves a MultiBase system from the first Char in a String.
resolveMultiBase :: String -> Maybe MultiBase
resolveMultiBase str = uncons str >>= \(ch, _) -> resolvePrefix ch
-- | Checks if a given char is an implemented MultiBase prefix.
hasValidPrefix :: String -> Bool
hasValidPrefix [] = False
hasValidPrefix (prefix : _) = isJust $ resolvePrefix prefix
-- | Provides decoder for MultiBase strings with any implemented prefix
-- character.
decodeFromPrefix :: Decoder
decodeFromPrefix str =
uncons str >>= \(prefix, rest) -> do
decode <- baseDecoder <$> resolvePrefix prefix
decode rest
-- | Decodes for CIDv0 from base58btc w/o MultiBase prefix.
decodeCIDv0 :: Decoder
decodeCIDv0 = Bases.decoder Bases.base58btc