{-# LANGUAGE ConstraintKinds  #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies     #-}
{-# LANGUAGE TypeOperators    #-}
-- | Define servant servers from record types. Generics for the win.
--
-- The usage is simple, if you only need a collection of routes.  First you
-- define a record with field types prefixed by a parameter `route`:
--
-- @
-- data Routes route = Routes
--     { _get :: route :- Capture "id" Int :> Get '[JSON] String
--     , _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
--     }
--   deriving ('Generic')
-- @
--
-- You can get a 'Proxy' of the server using
--
-- @
-- api :: Proxy (ToServantApi Routes)
-- api = genericApi (Proxy :: Proxy Routes)
-- @
--
-- Using 'genericApi' is better as it checks that instances exists,
-- i.e. you get better error messages than simply using 'Proxy' value.
--
-- __Note:__ in 0.14 series this module isn't re-exported from 'Servant.API'.
--
-- "Servant.API.Generic" is based on @servant-generic@ package by
-- [Patrick Chilton](https://github.com/chpatrick)
--
-- @since 0.14.1
module Servant.API.Generic (
    GenericMode (..),
    GenericServant,
    ToServant,
    toServant,
    fromServant,
    -- * AsApi
    AsApi,
    ToServantApi,
    genericApi,
    -- * Utility
    GServantProduct,
    -- * re-exports
    Generic (Rep),
  ) where

-- Based on servant-generic licensed under MIT License
--
-- Copyright (c) 2017 Patrick Chilton
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.

import           Data.Proxy
                 (Proxy (..))
import           GHC.Generics
                 ((:*:) (..), Generic (..), K1 (..), M1 (..))

import           Servant.API.Alternative

-- | A constraint alias, for work with 'mode' and 'routes'.
type GenericServant routes mode = (GenericMode mode, Generic (routes mode), GServantProduct (Rep (routes mode)))

-- | A class with a type family that applies an appropriate type family to the @api@
-- parameter.  For example, 'AsApi' will leave @api@ untouched, while
-- @'AsServerT' m@ will produce @'ServerT' api m@.
class GenericMode mode where
    type mode :- api :: *

infixl 0 :-

-- | Turns a generic product type into a tree of `:<|>` combinators.
type ToServant routes mode = GToServant (Rep (routes mode))

type ToServantApi routes = ToServant routes AsApi

-- | See `ToServant`, but at value-level.
toServant
    :: GenericServant routes mode
    => routes mode -> ToServant routes mode
toServant :: forall (routes :: * -> *) mode.
GenericServant routes mode =>
routes mode -> ToServant routes mode
toServant = Rep (routes mode) Any -> GToServant (Rep (routes mode))
forall p. Rep (routes mode) p -> GToServant (Rep (routes mode))
forall (f :: * -> *) p. GServantProduct f => f p -> GToServant f
gtoServant (Rep (routes mode) Any -> GToServant (Rep (routes mode)))
-> (routes mode -> Rep (routes mode) Any)
-> routes mode
-> GToServant (Rep (routes mode))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. routes mode -> Rep (routes mode) Any
forall x. routes mode -> Rep (routes mode) x
forall a x. Generic a => a -> Rep a x
from

-- | Inverse of `toServant`.
--
-- This can be used to turn 'generated' values such as client functions into records.
--
-- You may need to provide a type signature for the /output/ type (your record type).
fromServant
    :: GenericServant routes mode
    => ToServant routes mode -> routes mode
fromServant :: forall (routes :: * -> *) mode.
GenericServant routes mode =>
ToServant routes mode -> routes mode
fromServant = Rep (routes mode) Any -> routes mode
forall a x. Generic a => Rep a x -> a
forall x. Rep (routes mode) x -> routes mode
to (Rep (routes mode) Any -> routes mode)
-> (GToServant (Rep (routes mode)) -> Rep (routes mode) Any)
-> GToServant (Rep (routes mode))
-> routes mode
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GToServant (Rep (routes mode)) -> Rep (routes mode) Any
forall p. GToServant (Rep (routes mode)) -> Rep (routes mode) p
forall (f :: * -> *) p. GServantProduct f => GToServant f -> f p
gfromServant

-- | A type that specifies that an API record contains an API definition. Only useful at type-level.
data AsApi
instance GenericMode AsApi where
    type AsApi :- api = api

-- | Get a 'Proxy' of an API type.
genericApi
    :: GenericServant routes AsApi
    => Proxy routes
    -> Proxy (ToServantApi routes)
genericApi :: forall (routes :: * -> *).
GenericServant routes AsApi =>
Proxy routes -> Proxy (ToServantApi routes)
genericApi Proxy routes
_ = Proxy (GToServant (Rep (routes AsApi)))
forall {k} (t :: k). Proxy t
Proxy

-------------------------------------------------------------------------------
-- Class
-------------------------------------------------------------------------------


class GServantProduct f where
    type GToServant f
    gtoServant   :: f p -> GToServant f
    gfromServant :: GToServant f -> f p

instance GServantProduct f => GServantProduct (M1 i c f) where
    type GToServant (M1 i c f) = GToServant f
    gtoServant :: forall p. M1 i c f p -> GToServant (M1 i c f)
gtoServant   = f p -> GToServant f
forall p. f p -> GToServant f
forall (f :: * -> *) p. GServantProduct f => f p -> GToServant f
gtoServant (f p -> GToServant f)
-> (M1 i c f p -> f p) -> M1 i c f p -> GToServant f
forall b c a. (b -> c) -> (a -> b) -> a -> c
. M1 i c f p -> f p
forall k i (c :: Meta) (f :: k -> *) (p :: k). M1 i c f p -> f p
unM1
    gfromServant :: forall p. GToServant (M1 i c f) -> M1 i c f p
gfromServant = f p -> M1 i c f p
forall k i (c :: Meta) (f :: k -> *) (p :: k). f p -> M1 i c f p
M1 (f p -> M1 i c f p)
-> (GToServant f -> f p) -> GToServant f -> M1 i c f p
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GToServant f -> f p
forall p. GToServant f -> f p
forall (f :: * -> *) p. GServantProduct f => GToServant f -> f p
gfromServant

instance (GServantProduct l, GServantProduct r) => GServantProduct (l :*: r) where
    type GToServant (l :*: r) = GToServant l :<|> GToServant r
    gtoServant :: forall p. (:*:) l r p -> GToServant (l :*: r)
gtoServant   (l p
l :*: r p
r)  = l p -> GToServant l
forall p. l p -> GToServant l
forall (f :: * -> *) p. GServantProduct f => f p -> GToServant f
gtoServant l p
l GToServant l -> GToServant r -> GToServant l :<|> GToServant r
forall a b. a -> b -> a :<|> b
:<|> r p -> GToServant r
forall p. r p -> GToServant r
forall (f :: * -> *) p. GServantProduct f => f p -> GToServant f
gtoServant r p
r
    gfromServant :: forall p. GToServant (l :*: r) -> (:*:) l r p
gfromServant (GToServant l
l :<|> GToServant r
r) = GToServant l -> l p
forall p. GToServant l -> l p
forall (f :: * -> *) p. GServantProduct f => GToServant f -> f p
gfromServant GToServant l
l l p -> r p -> (:*:) l r p
forall k (f :: k -> *) (g :: k -> *) (p :: k).
f p -> g p -> (:*:) f g p
:*: GToServant r -> r p
forall p. GToServant r -> r p
forall (f :: * -> *) p. GServantProduct f => GToServant f -> f p
gfromServant GToServant r
r

instance GServantProduct (K1 i c) where
    type GToServant (K1 i c) = c
    gtoServant :: forall p. K1 i c p -> GToServant (K1 i c)
gtoServant   = K1 i c p -> c
K1 i c p -> GToServant (K1 i c)
forall k i c (p :: k). K1 i c p -> c
unK1
    gfromServant :: forall p. GToServant (K1 i c) -> K1 i c p
gfromServant = c -> K1 i c p
GToServant (K1 i c) -> K1 i c p
forall k i c (p :: k). c -> K1 i c p
K1