libjwt-typed-0.1: src/Libjwt/Decoding.hs
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
{-# OPTIONS_HADDOCK show-extensions #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | JWT decoding definition
--
-- __This module can be considered internal to the library__
-- Users should never need to implement the `Decode` typeclass or use any of the exported functions or types directly.
-- You'll only need to know of `Decode` typeclass if you want to write a function polymorphic in the type of payloads.
--
-- If you want to extend the types supported by the library, see "Libjwt.Classes"
module Libjwt.Decoding
( DecodeResult(..)
, hoistResult
, ClaimDecoder(..)
, Decode(..)
, getOrEmpty
, decodeClaimOrThrow
, decodeClaimProxied
, Decodable
, JwtIO
)
where
import Libjwt.Classes
import Libjwt.Exceptions ( MissingClaim(..) )
import Libjwt.FFI.Jwt
import Libjwt.JsonByteString
import Libjwt.NumericDate
import Control.Applicative ( Alternative )
import Control.Monad ( (<=<) )
import Control.Monad.Catch ( throwM )
import Control.Monad.Trans.Maybe
import Data.ByteString ( ByteString )
import Data.Coerce
import Data.Kind ( Constraint )
import Data.Maybe ( fromMaybe )
import Data.Proxy
newtype DecodeResult t = Result { getOptional :: JwtIO (Maybe t) }
deriving (Functor, Applicative, Monad, Alternative) via (MaybeT JwtIO)
-- | Lift pure value
hoistResult :: Maybe a -> DecodeResult a
hoistResult = Result . pure
-- | Action that returns 'mempty' if decoding has failed
getOrEmpty :: (Monoid a) => DecodeResult a -> JwtIO a
getOrEmpty (Result x) = fromMaybe mempty <$> x
-- | 'decodeClaim' through proxy
decodeClaimProxied
:: (ClaimDecoder t) => String -> proxy t -> JwtT -> DecodeResult t
decodeClaimProxied name _ = decodeClaim name
-- | Action that throws 'MissingClaim' if decoding has failed
decodeClaimOrThrow :: (ClaimDecoder t) => String -> proxy t -> JwtT -> JwtIO t
decodeClaimOrThrow name p =
maybe (throwM $ Missing name) return
<=< getOptional
. decodeClaimProxied name p
data DecoderType = Native | Derived
type family DecoderDef a :: DecoderType where
DecoderDef ByteString = 'Native
DecoderDef Bool = 'Native
DecoderDef Int = 'Native
DecoderDef NumericDate = 'Native
DecoderDef JsonByteString = 'Native
DecoderDef String = 'Derived
DecoderDef [a] = 'Native
DecoderDef _ = 'Derived
-- | Low-level definition of claims decoding.
class ClaimDecoder t where
-- | Given a pointer to /jwt_t/, try to decode the value of type @t@
decodeClaim :: String -> JwtT -> DecodeResult t
instance (DecoderDef a ~ ty, ClaimDecoder' ty a) => ClaimDecoder a where
decodeClaim = decodeClaim' (Proxy :: Proxy ty)
class ClaimDecoder' (ty :: DecoderType) t where
decodeClaim' :: proxy ty -> String -> JwtT -> DecodeResult t
instance ClaimDecoder' 'Native ByteString where
decodeClaim' _ name = Result . getGrant name
{-# INLINE decodeClaim' #-}
instance ClaimDecoder' 'Native Bool where
decodeClaim' _ name = Result . getGrantBool name
{-# INLINE decodeClaim' #-}
instance ClaimDecoder' 'Native Int where
decodeClaim' _ name = Result . getGrantInt name
{-# INLINE decodeClaim' #-}
instance ClaimDecoder' 'Native NumericDate where
decodeClaim' _ name = coerce . getGrantInt64 name
{-# INLINE decodeClaim' #-}
instance ClaimDecoder' 'Native JsonByteString where
decodeClaim' _ name = fmap jsonFromStrict . Result . getGrantAsJson name
{-# INLINE decodeClaim' #-}
fromJsonNative
:: (JsonByteString -> JwtIO (Maybe a)) -> String -> JwtT -> DecodeResult a
fromJsonNative f name =
(Result . f) <=< decodeClaim' (Proxy :: Proxy 'Native) name
{-# INLINE fromJsonNative #-}
instance JsonParser a => ClaimDecoder' 'Native [a] where
decodeClaim' _ =
fromJsonNative
$ fmap (sequence =<<)
. unsafeMapTokenizedJsonArray jsonParser
. toJsonStrict
{-# INLINE decodeClaim' #-}
instance (JwtRep b a, DecoderDef b ~ ty, ClaimDecoder' ty b) => ClaimDecoder' 'Derived a where
decodeClaim' _ name =
(hoistResult . unRep) <=< decodeClaim' (Proxy :: Proxy ty) name
type family Decodable t :: Constraint where
Decodable t = ClaimDecoder' (DecoderDef t) t
-- | Definition of claims decoding.
--
-- The only use for the user is probably to write a function that is polymorphic in the payload type
class Decode c where
-- | Construct an action that decodes the value of type @c@, given a pointer to /jwt_t/. The action may fail.
decode :: JwtT -> JwtIO c