packages feed

servant-routes-0.1.0.0: src/Servant/API/Routes/Internal/Request.hs

{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_HADDOCK not-home #-}

{- |
Module      : Servant.API.Routes.Internal.Request
Copyright   : (c) Frederick Pringle, 2025
License     : BSD-3-Clause
Maintainer  : freddyjepringle@gmail.com

Internal module, subject to change.
-}
module Servant.API.Routes.Internal.Request
  ( Request (..)
  , unRequest
  , AllTypeable (..)
  )
where

import Data.Aeson
import Data.Function (on)
import Data.Kind (Type)
import Data.List (nub, sort)
import Data.Typeable
import Lens.Micro.TH
import "this" Servant.API.Routes.Internal.Some as S
import "this" Servant.API.Routes.Utils

{- | A representation of the request /body/(s) that a Servant endpoint expects.

Under the hood, 'Request' is a @'Some' 'TypeRep'@.
This allows for the possibility that an endpoint might expect the request body
to parse as several different types (multiple 'Servant.API.ReqBody''s).

Note that this type doesn't include any information about the headers that an
endpoint expects, since those are independent of the request body.
-}
newtype Request = Request {_unRequest :: Some TypeRep}
  deriving (Show) via Some TypeRep

makeLenses ''Request

instance ToJSON Request where
  toJSON = someToJSONAs typeRepToJSON "all_of" . _unRequest

instance Eq Request where
  (==) = eqSome ((==) `on` (sort . nub)) `on` _unRequest

instance Semigroup Request where
  Request b1 <> Request b2 = Request (appendSome (:) (flip (:)) b1 b2)

instance Monoid Request where
  mempty = Request S.None

{- | This class does 2 things:

- It lets us get a term-level list of 'TypeRep's from a type-level list of types, all of
  which have 'Typeable' instances.
- More impressively, its instances enforce that 'typeReps' will only type-check for type-level
  lists of length 2 or more. This is because 'AllTypeable' will only ever be used by
  'Servant.API.Routes.Request.allOfRequests', which is the only way to construct a
  'Many' @'Request' and thus lets us enforce the invariant that its list arguments will always
  have more than 1 element. This lets us make sure that there's only ever one way to represent a list of
  'TypeRep's using 'Request'.

  Of course, someone might import this Internal module and define a @'Typeable' a => 'AllTypeable' '[a]@
  instance. Don't do that.
-}
class AllTypeable (as :: [Type]) where
  typeReps :: [TypeRep]

instance (Typeable a, Typeable b) => AllTypeable '[a, b] where
  typeReps = [typeRepOf @a, typeRepOf @b]

instance (Typeable a, AllTypeable (b ': c ': as)) => AllTypeable (a ': b ': c ': as) where
  typeReps = typeRepOf @a : typeReps @(b ': c ': as)