libjwt-typed-0.1: src/Libjwt/Encoding.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 FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | JWT encoding definition
--
-- __This module can be considered internal to the library__
-- Users should never need to implement the `Encode` typeclass or use any of the exported functions or types directly.
-- You'll only need to know of `Encode` 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.Encoding
( EncodeResult
, Encode(..)
, ClaimEncoder(..)
, nullEncode
)
where
import Libjwt.Classes
import Libjwt.FFI.Jwt
import Libjwt.JsonByteString
import Libjwt.NumericDate
import Data.ByteString ( ByteString )
import Data.ByteString.Builder ( Builder
, char7
, string7
, lazyByteString
)
import Data.ByteString.Builder.Extra ( toLazyByteStringWith
, safeStrategy
)
import Data.ByteString.Lazy ( toStrict )
import Data.Coerce ( coerce )
import Data.Proxy ( Proxy(..) )
type EncodeResult = JwtIO ()
-- | Do not perform any action. It is used to encode things like empty lists or /Nothing/
nullEncode :: b -> EncodeResult
nullEncode = const $ return ()
data EncoderType = Native | Spec | Derived
type family EncoderDef a :: EncoderType where
EncoderDef (Maybe a) = 'Spec
EncoderDef ByteString = 'Native
EncoderDef Bool = 'Native
EncoderDef Int = 'Native
EncoderDef NumericDate = 'Native
EncoderDef JsonByteString = 'Native
EncoderDef String = 'Derived
EncoderDef [a] = 'Spec
EncoderDef _ = 'Derived
-- | Low-level definition of JWT claims encoding.
class ClaimEncoder t where
-- | Given a pointer to /jwt_t/, mutate the structure it points to to encode the value as a named claim
-- It relies on the functions exported from "Libjwt.FFI.Jwt" to perform an /impure/ effect of /encoding/
encodeClaim :: String -> t -> JwtT -> EncodeResult
instance (EncoderDef a ~ ty, ClaimEncoder' ty a) => ClaimEncoder a where
encodeClaim = encodeClaim' (Proxy :: Proxy ty)
class ClaimEncoder' (ty :: EncoderType) t where
encodeClaim' :: proxy ty -> String -> t -> JwtT -> EncodeResult
instance ClaimEncoder a => ClaimEncoder' 'Spec (Maybe a) where
encodeClaim' _ name (Just val) = encodeClaim name val
encodeClaim' _ _ Nothing = nullEncode
instance JsonBuilder a => ClaimEncoder' 'Spec [a] where
encodeClaim' _ _ [] = nullEncode
encodeClaim' _ name as = fromJson name $ jsonBuilder as
instance ClaimEncoder' 'Native ByteString where
encodeClaim' _ = addGrant
instance ClaimEncoder' 'Native Bool where
encodeClaim' _ = addGrantBool
instance ClaimEncoder' 'Native Int where
encodeClaim' _ = addGrantInt
instance ClaimEncoder' 'Native NumericDate where
encodeClaim' _ name = addGrantInt64 name . coerce
{-# INLINE encodeClaim' #-}
fromJson :: String -> Builder -> JwtT -> JwtIO ()
fromJson name =
addGrantsFromJson
. toStrict
. toLazyByteStringWith (safeStrategy 64 512) mempty
. encodeAsObject1
where
encodeAsObject1 = objectBrackets . ((fieldName <> char7 ':') <>)
where
objectBrackets bs = char7 '{' <> bs <> char7 '}'
fieldName = char7 '"' <> string7 name <> char7 '"'
instance ClaimEncoder' 'Native JsonByteString where
encodeClaim' _ name = fromJson name . lazyByteString . toJson
instance (JwtRep b a, EncoderDef b ~ ty, ClaimEncoder' ty b) => ClaimEncoder' 'Derived a where
encodeClaim' _ name = encodeClaim' (Proxy :: Proxy ty) name . rep
-- | Definition of claims encoding.
--
-- The only use for the user is probably to write a function that is polymorphic in the payload type.
class Encode c where
-- | Perform the encoding as /impure/ action
encode :: c -> JwtT -> EncodeResult