Implement the basics for Elixir-like Plugs

This commit is contained in:
Rashad Gover 2023-05-15 10:57:16 +00:00
parent ea3dedaada
commit c4591352c7
5 changed files with 669 additions and 589 deletions

View File

@ -41,6 +41,7 @@ library
Okapi.Parser.Responder
Okapi.Parser.Responder.AddHeader
Okapi.Parser.Security
Okapi.Plug
other-modules:
Paths_okapi
hs-source-dirs:

View File

@ -1,138 +1,138 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
-- {-# LANGUAGE DeriveAnyClass #-}
-- {-# LANGUAGE DeriveGeneric #-}
-- {-# LANGUAGE GADTs #-}
-- {-# LANGUAGE ImportQualifiedPost #-}
-- {-# LANGUAGE InstanceSigs #-}
-- {-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE RankNTypes #-}
module Okapi.Parser.Body where
import Control.Monad.Par qualified as Par
import Data.Aeson qualified as Aeson
import Data.Bifunctor (Bifunctor (..))
import Data.ByteString qualified as BS
import Data.ByteString.Builder qualified as Builder
import Data.ByteString.Lazy qualified as LBS
import Data.List qualified as List
import Data.List.NonEmpty (NonEmpty)
import Data.Set.NonEmpty (NESet)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import GHC.Generics qualified as Generics
import Network.HTTP.Types qualified as HTTP
import Network.Wai.Parse (RequestBodyType (UrlEncoded))
import Network.Wai.Parse qualified as WAI
import Okapi.Parser
import Okapi.Parser.Body.Multipart qualified as Multipart
import Web.Cookie qualified as Web
import Web.FormUrlEncoded qualified as Web
import Web.HttpApiData qualified as Web
-- import Control.Monad.Par qualified as Par
-- import Data.Aeson qualified as Aeson
-- import Data.Bifunctor (Bifunctor (..))
-- import Data.ByteString qualified as BS
-- import Data.ByteString.Builder qualified as Builder
-- import Data.ByteString.Lazy qualified as LBS
-- import Data.List qualified as List
-- import Data.List.NonEmpty (NonEmpty)
-- import Data.Set.NonEmpty (NESet)
-- import Data.Text qualified as Text
-- import Data.Text.Encoding qualified as Text
-- import GHC.Generics qualified as Generics
-- import Network.HTTP.Types qualified as HTTP
-- import Network.Wai.Parse (RequestBodyType (UrlEncoded))
-- import Network.Wai.Parse qualified as WAI
-- import Okapi.Parser
-- import Okapi.Parser.Body.Multipart qualified as Multipart
-- import Web.Cookie qualified as Web
-- import Web.FormUrlEncoded qualified as Web
-- import Web.HttpApiData qualified as Web
data Error
= JSONParseFail
| URLEncodedParseFail Text.Text
| URLEncodedInvalid Text.Text
| NotMultipart
| MultipartError Multipart.Error
deriving (Eq, Show, Generics.Generic)
-- data Error
-- = JSONParseFail
-- | URLEncodedParseFail Text.Text
-- | URLEncodedInvalid Text.Text
-- | NotMultipart
-- | MultipartError Multipart.Error
-- deriving (Eq, Show, Generics.Generic)
data ContentType a where
JSON :: Aeson.FromJSON a => ContentType a
URLEncoded :: Web.FromForm a => ContentType a
Multipart :: Multipart.Parser a -> ContentType a
-- data ContentType a where
-- JSON :: Aeson.FromJSON a => ContentType a
-- URLEncoded :: Web.FromForm a => ContentType a
-- Multipart :: Multipart.Parser a -> ContentType a
instance Eq (ContentType a) where
JSON == JSON = True
URLEncoded == UrlEncoded = True
Multipart _ == Multipart _ = True
_ == _ = False
-- instance Eq (ContentType a) where
-- JSON == JSON = True
-- URLEncoded == UrlEncoded = True
-- Multipart _ == Multipart _ = True
-- _ == _ = False
instance Functor ContentType
-- instance Functor ContentType
data Parser a where
FMap :: (a -> b) -> Parser a -> Parser b
Pure :: a -> Parser a
Apply :: Parser (a -> b) -> Parser a -> Parser b
None :: Parser ()
Optional :: NESet (ContentType a) -> Parser (Maybe a)
Required :: NESet (ContentType a) -> Parser a
-- data Parser a where
-- FMap :: (a -> b) -> Parser a -> Parser b
-- Pure :: a -> Parser a
-- Apply :: Parser (a -> b) -> Parser a -> Parser b
-- None :: Parser ()
-- Optional :: NESet (ContentType a) -> Parser (Maybe a)
-- Required :: NESet (ContentType a) -> Parser a
instance Functor Parser where
fmap :: (a -> b) -> Parser a -> Parser b
fmap = FMap
-- instance Functor Parser where
-- fmap :: (a -> b) -> Parser a -> Parser b
-- fmap = FMap
instance Applicative Parser where
pure = Pure
(<*>) = Apply
-- instance Applicative Parser where
-- pure = Pure
-- (<*>) = Apply
data RequestBody
= RequestBodyRaw LBS.ByteString
| RequestBodyMultipart ([WAI.Param], [WAI.File LBS.ByteString])
-- data RequestBody
-- = RequestBodyRaw LBS.ByteString
-- | RequestBodyMultipart ([WAI.Param], [WAI.File LBS.ByteString])
eval ::
Parser a ->
RequestBody ->
(Result Error a, RequestBody)
eval op state = case op of
FMap f opX ->
case eval opX state of
(Fail e, state') -> (Fail e, state')
(Ok x, state') -> (Ok $ f x, state')
Pure x -> (Ok x, state)
Apply opF opX -> case eval opF state of
(Ok f, state') -> case eval opX state' of
(Ok x, state'') -> (Ok $ f x, state'')
(Fail e, state'') -> (Fail e, state'')
(Fail e, state') -> (Fail e, state')
-- None -> (Ok (), state)
JSON -> case state of
RequestBodyRaw bs -> case Aeson.decode bs of
Nothing -> (Fail JSONParseFail, state)
Just value -> (Ok value, RequestBodyRaw mempty)
RequestBodyMultipart _ -> (Fail JSONParseFail, state)
URLEncoded -> case state of
RequestBodyRaw bs -> case Web.urlDecodeForm bs of
Left err -> (Fail $ URLEncodedInvalid err, state)
Right form -> case Web.fromForm form of
Left err -> (Fail $ URLEncodedParseFail err, state)
Right value -> (Ok value, RequestBodyRaw mempty)
RequestBodyMultipart (params, files) ->
let bsParams = map (bimap Text.decodeUtf8 Text.decodeUtf8) params
bs = Web.urlEncodeParams bsParams
in case Web.urlDecodeForm bs of
Left err -> (Fail $ URLEncodedInvalid err, state)
Right form -> case Web.fromForm form of
Left err -> (Fail $ URLEncodedParseFail err, state)
Right value -> (Ok value, RequestBodyMultipart (mempty, files))
Multipart parser -> case state of
RequestBodyRaw _ -> (Fail NotMultipart, state)
RequestBodyMultipart parts -> case Multipart.eval parser parts of
(Ok value, state') -> (Ok value, RequestBodyMultipart state')
(Fail err, state') -> (Fail $ MultipartError err, RequestBodyMultipart state')
-- eval ::
-- Parser a ->
-- RequestBody ->
-- (Result Error a, RequestBody)
-- eval op state = case op of
-- FMap f opX ->
-- case eval opX state of
-- (Fail e, state') -> (Fail e, state')
-- (Ok x, state') -> (Ok $ f x, state')
-- Pure x -> (Ok x, state)
-- Apply opF opX -> case eval opF state of
-- (Ok f, state') -> case eval opX state' of
-- (Ok x, state'') -> (Ok $ f x, state'')
-- (Fail e, state'') -> (Fail e, state'')
-- (Fail e, state') -> (Fail e, state')
-- -- None -> (Ok (), state)
-- JSON -> case state of
-- RequestBodyRaw bs -> case Aeson.decode bs of
-- Nothing -> (Fail JSONParseFail, state)
-- Just value -> (Ok value, RequestBodyRaw mempty)
-- RequestBodyMultipart _ -> (Fail JSONParseFail, state)
-- URLEncoded -> case state of
-- RequestBodyRaw bs -> case Web.urlDecodeForm bs of
-- Left err -> (Fail $ URLEncodedInvalid err, state)
-- Right form -> case Web.fromForm form of
-- Left err -> (Fail $ URLEncodedParseFail err, state)
-- Right value -> (Ok value, RequestBodyRaw mempty)
-- RequestBodyMultipart (params, files) ->
-- let bsParams = map (bimap Text.decodeUtf8 Text.decodeUtf8) params
-- bs = Web.urlEncodeParams bsParams
-- in case Web.urlDecodeForm bs of
-- Left err -> (Fail $ URLEncodedInvalid err, state)
-- Right form -> case Web.fromForm form of
-- Left err -> (Fail $ URLEncodedParseFail err, state)
-- Right value -> (Ok value, RequestBodyMultipart (mempty, files))
-- Multipart parser -> case state of
-- RequestBodyRaw _ -> (Fail NotMultipart, state)
-- RequestBodyMultipart parts -> case Multipart.eval parser parts of
-- (Ok value, state') -> (Ok value, RequestBodyMultipart state')
-- (Fail err, state') -> (Fail $ MultipartError err, RequestBodyMultipart state')
none :: Parser ()
none = None
-- none :: Parser ()
-- none = None
json :: Aeson.FromJSON a => Parser a
json = JSON
-- json :: Aeson.FromJSON a => Parser a
-- json = JSON
urlEncoded :: Web.FromForm a => Parser a
urlEncoded = URLEncoded
-- urlEncoded :: Web.FromForm a => Parser a
-- urlEncoded = URLEncoded
multipart :: Multipart.Parser a -> Parser a
multipart = Multipart
-- multipart :: Multipart.Parser a -> Parser a
-- multipart = Multipart
class Interface a where
parser :: NonEmpty (Parser a)
-- class Interface a where
-- parser :: NonEmpty (Parser a)
-- TODO: Add optional for body
-- -- TODO: Add optional for body
countOps :: Parser a -> Int
countOps path = case path of
FMap _ opX -> countOps opX
Pure _ -> 0
Apply opF opX -> countOps opF + countOps opX
JSON -> 1
URLEncoded -> 1
Multipart -> undefined
-- countOps :: Parser a -> Int
-- countOps path = case path of
-- FMap _ opX -> countOps opX
-- Pure _ -> 0
-- Apply opF opX -> countOps opF + countOps opX
-- JSON -> 1
-- URLEncoded -> 1
-- Multipart -> undefined

79
lib/src/Okapi/Plug.hs Normal file
View File

@ -0,0 +1,79 @@
{-# LANGUAGE GADTs #-}
module Okapi.Plug where
import Control.Applicative (Alternative (..))
import Data.Text
import qualified Network.Wai as WAI
data Conn = Conn {request :: WAI.Request, response :: WAI.Response}
data Error = Skipped [Text] | Halted [Text]
data Plug a where
FMap :: (a -> b) -> Plug a -> Plug b
Pure :: a -> Plug a
Apply :: Plug (a -> b) -> Plug a -> Plug b
Alt :: Plug a -> Plug a -> Plug a
Skip :: [Text] -> Plug a
Halt :: [Text] -> Plug a
instance Functor Plug where
fmap = FMap
instance Applicative Plug where
pure = Pure
(<*>) = Apply
instance Alternative Plug where
empty = Skip []
(<|>) = Alt
skip = Skip
halt = Halt
class IsRoute r
class IsQuery q
class IsBody b
class IsHeaders h
class IsResponder r
class Monad m => IOable m
data Response = Response
data Endpoint where
GET :: (IOable m, IsRoute r, IsQuery q, IsHeaders h, IsResponder res) => (r -> q -> h -> res -> m Response) -> Endpoint
POST :: (IOable m, IsRoute r, IsQuery q, IsBody b, IsHeaders h, IsResponder res) => (r -> q -> h -> b -> res -> m Response) -> Endpoint
PUT :: (IOable m, IsRoute r, IsQuery q, IsBody b, IsHeaders h, IsResponder res) => (r -> q -> h -> b -> res -> m Response) -> Endpoint
DELETE :: (IOable m, IsRoute r, IsQuery q, IsHeaders h, IsResponder res) => (r -> q -> h -> res -> m Response) -> Endpoint
get :: (IOable m, IsRoute r, IsQuery q, IsHeaders h, IsResponder res) => (r -> q -> h -> res -> m Response) -> Endpoint
get = GET
post :: (IOable m, IsRoute r, IsQuery q, IsBody b, IsHeaders h, IsResponder res) => (r -> q -> h -> b -> res -> m Response) -> Endpoint
post = POST
put :: (IOable m, IsRoute r, IsQuery q, IsBody b, IsHeaders h, IsResponder res) => (r -> q -> h -> b -> res -> m Response) -> Endpoint
put = PUT
delete :: (IOable m, IsRoute r, IsQuery q, IsHeaders h, IsResponder res) => (r -> q -> h -> res -> m Response) -> Endpoint
delete = DELETE
router :: [Endpoint] -> Plug ()
router [] = pure ()
router (h : t) = undefined
-- Filter Endpoints by method
--
scope :: [Text] -> Plug () -> Plug ()
scope path plug = undefined
eval :: Plug a -> Conn -> (Either Error a, Conn)
eval = undefined

View File

@ -1,127 +1,127 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- {-# LANGUAGE BlockArguments #-}
-- {-# LANGUAGE DataKinds #-}
-- {-# LANGUAGE DuplicateRecordFields #-}
-- {-# LANGUAGE GADTs #-}
-- {-# LANGUAGE ImportQualifiedPost #-}
-- {-# LANGUAGE ImpredicativeTypes #-}
-- {-# LANGUAGE KindSignatures #-}
-- {-# LANGUAGE LinearTypes #-}
-- {-# LANGUAGE NamedFieldPuns #-}
-- {-# LANGUAGE OverloadedRecordDot #-}
-- {-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE PolyKinds #-}
-- {-# LANGUAGE RankNTypes #-}
-- {-# LANGUAGE RecordWildCards #-}
-- {-# LANGUAGE ScopedTypeVariables #-}
-- {-# LANGUAGE StandaloneKindSignatures #-}
-- {-# LANGUAGE TypeApplications #-}
-- {-# LANGUAGE TypeFamilies #-}
-- {-# LANGUAGE TypeOperators #-}
module Okapi.Route where
import Control.Natural (type (~>))
import Control.Object (Object (..), (#))
import Data.Aeson qualified as Aeson
import Data.Bifunctor (Bifunctor (..))
import Data.ByteString qualified as BS
import Data.CaseInsensitive qualified as CI
import Data.Function ((&))
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.Kind (Type)
import Data.List (groupBy)
import Data.List qualified as List
import Data.List.Extra qualified as List
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NonEmpty
import Data.OpenApi (OpenApi (_openApiInfo))
import Data.OpenApi qualified as OAPI
import Data.OpenApi.Declare qualified as OAPI
import Data.OpenApi.Internal (OpenApiSpecVersion (..), upperOpenApiSpecVersion)
import Data.Proxy
import Data.Semigroup (Semigroup (..))
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Version qualified as Version
import Debug.Trace qualified as Debug
import Network.HTTP.Types qualified as HTTP
import Network.Wai qualified as WAI
import Network.Wai.Parse qualified as WAI
import Okapi.Parser
import Okapi.Parser.Body qualified as Body
import Okapi.Parser.Headers qualified as Headers
import Okapi.Parser.Path qualified as Path
import Okapi.Parser.Query qualified as Query
import Okapi.Parser.Responder qualified as Responder
import Okapi.Parser.Responder.AddHeader (Response, toWaiResponse)
import Okapi.Parser.Security qualified as Security
import Okapi.Parser.Security.Secure qualified as Secure
import Okapi.Request (Request)
-- import Control.Natural (type (~>))
-- import Control.Object (Object (..), (#))
-- import Data.Aeson qualified as Aeson
-- import Data.Bifunctor (Bifunctor (..))
-- import Data.ByteString qualified as BS
-- import Data.CaseInsensitive qualified as CI
-- import Data.Function ((&))
-- import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
-- import Data.Kind (Type)
-- import Data.List (groupBy)
-- import Data.List qualified as List
-- import Data.List.Extra qualified as List
-- import Data.List.NonEmpty (NonEmpty ((:|)))
-- import Data.List.NonEmpty qualified as NonEmpty
-- import Data.OpenApi (OpenApi (_openApiInfo))
-- import Data.OpenApi qualified as OAPI
-- import Data.OpenApi.Declare qualified as OAPI
-- import Data.OpenApi.Internal (OpenApiSpecVersion (..), upperOpenApiSpecVersion)
-- import Data.Proxy
-- import Data.Semigroup (Semigroup (..))
-- import Data.Text qualified as Text
-- import Data.Text.Encoding qualified as Text
-- import Data.Version qualified as Version
-- import Debug.Trace qualified as Debug
-- import Network.HTTP.Types qualified as HTTP
-- import Network.Wai qualified as WAI
-- import Network.Wai.Parse qualified as WAI
-- import Okapi.Parser
-- import Okapi.Parser.Body qualified as Body
-- import Okapi.Parser.Headers qualified as Headers
-- import Okapi.Parser.Path qualified as Path
-- import Okapi.Parser.Query qualified as Query
-- import Okapi.Parser.Responder qualified as Responder
-- import Okapi.Parser.Responder.AddHeader (Response, toWaiResponse)
-- import Okapi.Parser.Security qualified as Security
-- -- import Okapi.Parser.Security.Secure qualified as Secure
-- import Okapi.Request (Request)
type Routes :: [Type] -> Type
data Routes resources where
Nil :: Routes '[]
(:&) :: Path.Interface resource => Route resource -> Routes resources -> Routes (resource ': resources)
-- type Routes :: [Type] -> Type
-- data Routes resources where
-- Nil :: Routes '[]
-- (:&) :: Path.Interface resource => Route resource -> Routes resources -> Routes (resource ': resources)
infixr 5 :&
-- infixr 5 :&
type Append :: forall a. [a] -> [a] -> [a] -- kind signature
type family Append xs ys where -- header
Append '[] ys = ys -- clause 1
Append (x ': xs) ys = x ': Append xs ys -- clause 2
-- type Append :: forall a. [a] -> [a] -> [a] -- kind signature
-- type family Append xs ys where -- header
-- Append '[] ys = ys -- clause 1
-- Append (x ': xs) ys = x ': Append xs ys -- clause 2
appendRoutes :: Routes resources1 -> Routes resources2 -> Routes (Append resources1 resources2)
appendRoutes Nil pathItems = pathItems
appendRoutes (h :& t) pathItems = h :& appendRoutes t pathItems
-- appendRoutes :: Routes resources1 -> Routes resources2 -> Routes (Append resources1 resources2)
-- appendRoutes Nil pathItems = pathItems
-- appendRoutes (h :& t) pathItems = h :& appendRoutes t pathItems
data Route resource where
Route ::
Path.Interface resource =>
{ summary :: Maybe Text.Text,
description :: Maybe Text.Text,
get :: Maybe (GET m resource security query headers responder),
post :: Maybe (POST m resource security query body headers responder),
put :: Maybe (PUT m resource security query body headers responder),
delete :: Maybe (DELETE m resource security query headers responder)
} ->
Route resource
-- data Route resource where
-- Route ::
-- Path.Interface resource =>
-- { summary :: Maybe Text.Text,
-- description :: Maybe Text.Text,
-- get :: Maybe (GET m resource security query headers responder),
-- post :: Maybe (POST m resource security query body headers responder),
-- put :: Maybe (PUT m resource security query body headers responder),
-- delete :: Maybe (DELETE m resource security query headers responder)
-- } ->
-- Route resource
data GET m resource security query headers responder where
GET ::
(Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Headers.Interface headers, Responder.Interface responder) =>
{ summary :: Maybe Text.Text,
description :: Maybe Text.Text,
object :: Object m,
handler :: resource -> security -> query -> headers -> responder -> m Response
} ->
GET m resource security query headers responder
-- data GET m resource security query headers responder where
-- GET ::
-- (Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Headers.Interface headers, Responder.Interface responder) =>
-- { summary :: Maybe Text.Text,
-- description :: Maybe Text.Text,
-- object :: Object m,
-- handler :: resource -> security -> query -> headers -> responder -> m Response
-- } ->
-- GET m resource security query headers responder
data POST m resource security query body headers responder where
POST ::
(Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Body.Interface body, Headers.Interface headers, Responder.Interface responder) =>
{ summary :: Maybe Text.Text,
description :: Maybe Text.Text,
object :: Object m,
handler :: resource -> security -> query -> body -> headers -> responder -> m Response
} ->
POST m resource security query body headers responder
-- data POST m resource security query body headers responder where
-- POST ::
-- (Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Body.Interface body, Headers.Interface headers, Responder.Interface responder) =>
-- { summary :: Maybe Text.Text,
-- description :: Maybe Text.Text,
-- object :: Object m,
-- handler :: resource -> security -> query -> body -> headers -> responder -> m Response
-- } ->
-- POST m resource security query body headers responder
data PUT m resource security query body headers responder where
PUT ::
(Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Body.Interface body, Headers.Interface headers, Responder.Interface responder) =>
{ summary :: Maybe Text.Text,
description :: Maybe Text.Text,
object :: Object m,
handler :: resource -> security -> query -> body -> headers -> responder -> m Response
} ->
PUT m resource security query body headers responder
-- data PUT m resource security query body headers responder where
-- PUT ::
-- (Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Body.Interface body, Headers.Interface headers, Responder.Interface responder) =>
-- { summary :: Maybe Text.Text,
-- description :: Maybe Text.Text,
-- object :: Object m,
-- handler :: resource -> security -> query -> body -> headers -> responder -> m Response
-- } ->
-- PUT m resource security query body headers responder
data DELETE m resource security query headers responder where
DELETE ::
(Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Headers.Interface headers, Responder.Interface responder) =>
{ summary :: Maybe Text.Text,
description :: Maybe Text.Text,
object :: Object m,
handler :: resource -> security -> query -> headers -> responder -> m Response
} ->
DELETE m resource security query headers responder
-- data DELETE m resource security query headers responder where
-- DELETE ::
-- (Monad m, Path.Interface resource, Security.Interface security, Query.Interface query, Headers.Interface headers, Responder.Interface responder) =>
-- { summary :: Maybe Text.Text,
-- description :: Maybe Text.Text,
-- object :: Object m,
-- handler :: resource -> security -> query -> headers -> responder -> m Response
-- } ->
-- DELETE m resource security query headers responder

View File

@ -1,381 +1,381 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- {-# LANGUAGE BlockArguments #-}
-- {-# LANGUAGE DataKinds #-}
-- {-# LANGUAGE DuplicateRecordFields #-}
-- {-# LANGUAGE GADTs #-}
-- {-# LANGUAGE ImportQualifiedPost #-}
-- {-# LANGUAGE ImpredicativeTypes #-}
-- {-# LANGUAGE KindSignatures #-}
-- {-# LANGUAGE LinearTypes #-}
-- {-# LANGUAGE NamedFieldPuns #-}
-- {-# LANGUAGE OverloadedRecordDot #-}
-- {-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE PolyKinds #-}
-- {-# LANGUAGE RankNTypes #-}
-- {-# LANGUAGE RecordWildCards #-}
-- {-# LANGUAGE ScopedTypeVariables #-}
-- {-# LANGUAGE StandaloneKindSignatures #-}
-- {-# LANGUAGE TypeApplications #-}
-- {-# LANGUAGE TypeFamilies #-}
-- {-# LANGUAGE TypeOperators #-}
module Okapi.Server where
import Control.Natural (type (~>))
import Control.Object (Object (..), (#))
import Data.Aeson qualified as Aeson
import Data.Bifunctor (Bifunctor (..))
import Data.ByteString qualified as BS
import Data.CaseInsensitive qualified as CI
import Data.Function ((&))
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.Kind (Type)
import Data.List (groupBy)
import Data.List qualified as List
import Data.List.Extra qualified as List
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NonEmpty
import Data.OpenApi (OpenApi (_openApiInfo))
import Data.OpenApi qualified as OAPI
import Data.OpenApi.Declare qualified as OAPI
import Data.OpenApi.Internal (OpenApiSpecVersion (..), upperOpenApiSpecVersion)
import Data.Proxy
import Data.Semigroup (Semigroup (..))
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Version qualified as Version
import Debug.Trace qualified as Debug
import Network.HTTP.Types qualified as HTTP
import Network.Wai qualified as WAI
import Network.Wai.Parse qualified as WAI
import Okapi.Parser
import Okapi.Parser.Body qualified as Body
import Okapi.Parser.Headers qualified as Headers
import Okapi.Parser.Path qualified as Path
import Okapi.Parser.Query qualified as Query
import Okapi.Parser.Responder qualified as Responder
import Okapi.Parser.Responder.AddHeader (Response, toWaiResponse)
import Okapi.Parser.Security qualified as Security
import Okapi.Parser.Security.Secure qualified as Secure
import Okapi.Request (Request)
import Okapi.Route (DELETE (..), GET (..), POST (..), PUT (..), Route (..), Routes (..))
-- import Control.Natural (type (~>))
-- import Control.Object (Object (..), (#))
-- import Data.Aeson qualified as Aeson
-- import Data.Bifunctor (Bifunctor (..))
-- import Data.ByteString qualified as BS
-- import Data.CaseInsensitive qualified as CI
-- import Data.Function ((&))
-- import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
-- import Data.Kind (Type)
-- import Data.List (groupBy)
-- import Data.List qualified as List
-- import Data.List.Extra qualified as List
-- import Data.List.NonEmpty (NonEmpty ((:|)))
-- import Data.List.NonEmpty qualified as NonEmpty
-- import Data.OpenApi (OpenApi (_openApiInfo))
-- import Data.OpenApi qualified as OAPI
-- import Data.OpenApi.Declare qualified as OAPI
-- import Data.OpenApi.Internal (OpenApiSpecVersion (..), upperOpenApiSpecVersion)
-- import Data.Proxy
-- import Data.Semigroup (Semigroup (..))
-- import Data.Text qualified as Text
-- import Data.Text.Encoding qualified as Text
-- import Data.Version qualified as Version
-- import Debug.Trace qualified as Debug
-- import Network.HTTP.Types qualified as HTTP
-- import Network.Wai qualified as WAI
-- import Network.Wai.Parse qualified as WAI
-- import Okapi.Parser
-- import Okapi.Parser.Body qualified as Body
-- import Okapi.Parser.Headers qualified as Headers
-- import Okapi.Parser.Path qualified as Path
-- import Okapi.Parser.Query qualified as Query
-- import Okapi.Parser.Responder qualified as Responder
-- import Okapi.Parser.Responder.AddHeader (Response, toWaiResponse)
-- import Okapi.Parser.Security qualified as Security
-- import Okapi.Parser.Security.Secure qualified as Secure
-- import Okapi.Request (Request)
-- import Okapi.Route (DELETE (..), GET (..), POST (..), PUT (..), Route (..), Routes (..))
data Server resources = Server
{ info :: OAPI.Info,
url :: [Text.Text],
description :: Maybe Text.Text,
routes :: Routes resources
}
-- data Server resources = Server
-- { info :: OAPI.Info,
-- url :: [Text.Text],
-- description :: Maybe Text.Text,
-- routes :: Routes resources
-- }
toApplication :: Server resources -> WAI.Application
toApplication Server {url, routes} request respond = do
let reqPath = List.dropPrefix url $ WAI.pathInfo request
reqQuery = WAI.queryString request
reqHeaders = WAI.requestHeaders request
reqSecurity = Secure.State reqQuery reqHeaders [] -- TODO: Get cookies out of headers here instead of empty list
if reqPath == WAI.pathInfo request && not (null url)
then respond $ WAI.responseLBS HTTP.status404 mempty mempty -- TODO: If the incoming request URL doesn't have correct prefix. 404?
else case getPathItemByPath reqPath routes of
None -> respond $ WAI.responseLBS HTTP.status404 mempty mempty
Some (Route {get, post, put, delete}, resourceParam) -> case HTTP.parseMethod $ WAI.requestMethod request of
Left _ -> respond $ WAI.responseLBS HTTP.status501 mempty mempty -- TODO: Return 501 Not Implemented error
Right reqMethod ->
case reqMethod of
HTTP.GET -> case get of
Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
Just (GET {handler, object}) ->
case evalSecurity (sortSecurity Security.parser) reqSecurity of
(Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
response <- object # handler resourceParam securityParam queryParam headersParam responderParam
respond $ toWaiResponse response
_ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
_ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
HTTP.POST -> case post of
Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
Just (POST {handler, object}) ->
case evalSecurity (sortSecurity Security.parser) reqSecurity of
(Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
bodyResult <- evalBody (sortBody Body.parser) request
case bodyResult of
(Ok bodyParam, _) -> do
response <- object # handler resourceParam securityParam queryParam bodyParam headersParam responderParam
respond $ toWaiResponse response
_ -> respond $ WAI.responseLBS HTTP.status400 mempty mempty -- TODO: Return 400 for now but can be more specific depending on content-type, etc.
_ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
_ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
HTTP.PUT -> case put of
Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
Just (PUT {handler, object}) ->
case evalSecurity (sortSecurity Security.parser) reqSecurity of
(Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
bodyResult <- evalBody (sortBody Body.parser) request
case bodyResult of
(Ok bodyParam, _) -> do
response <- object # handler resourceParam securityParam queryParam bodyParam headersParam responderParam
respond $ toWaiResponse response
_ -> respond $ WAI.responseLBS HTTP.status400 mempty mempty -- TODO: Return 400 for now but can be more specific depending on content-type, etc.
_ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
_ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
HTTP.DELETE -> case delete of
Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
Just (DELETE {handler, object}) ->
case evalSecurity (sortSecurity Security.parser) reqSecurity of
(Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
response <- object # handler resourceParam securityParam queryParam headersParam responderParam
respond $ toWaiResponse response
_ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
_ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
_ -> respond $ WAI.responseLBS HTTP.status501 mempty mempty -- TODO: Implement cases for remaing Standard HTTP methods
-- toApplication :: Server resources -> WAI.Application
-- toApplication Server {url, routes} request respond = do
-- let reqPath = List.dropPrefix url $ WAI.pathInfo request
-- reqQuery = WAI.queryString request
-- reqHeaders = WAI.requestHeaders request
-- reqSecurity = Secure.State reqQuery reqHeaders [] -- TODO: Get cookies out of headers here instead of empty list
-- if reqPath == WAI.pathInfo request && not (null url)
-- then respond $ WAI.responseLBS HTTP.status404 mempty mempty -- TODO: If the incoming request URL doesn't have correct prefix. 404?
-- else case getPathItemByPath reqPath routes of
-- None -> respond $ WAI.responseLBS HTTP.status404 mempty mempty
-- Some (Route {get, post, put, delete}, resourceParam) -> case HTTP.parseMethod $ WAI.requestMethod request of
-- Left _ -> respond $ WAI.responseLBS HTTP.status501 mempty mempty -- TODO: Return 501 Not Implemented error
-- Right reqMethod ->
-- case reqMethod of
-- HTTP.GET -> case get of
-- Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
-- Just (GET {handler, object}) ->
-- case evalSecurity (sortSecurity Security.parser) reqSecurity of
-- (Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
-- ((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
-- response <- object # handler resourceParam securityParam queryParam headersParam responderParam
-- respond $ toWaiResponse response
-- _ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
-- _ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
-- HTTP.POST -> case post of
-- Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
-- Just (POST {handler, object}) ->
-- case evalSecurity (sortSecurity Security.parser) reqSecurity of
-- (Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
-- ((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
-- bodyResult <- evalBody (sortBody Body.parser) request
-- case bodyResult of
-- (Ok bodyParam, _) -> do
-- response <- object # handler resourceParam securityParam queryParam bodyParam headersParam responderParam
-- respond $ toWaiResponse response
-- _ -> respond $ WAI.responseLBS HTTP.status400 mempty mempty -- TODO: Return 400 for now but can be more specific depending on content-type, etc.
-- _ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
-- _ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
-- HTTP.PUT -> case put of
-- Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
-- Just (PUT {handler, object}) ->
-- case evalSecurity (sortSecurity Security.parser) reqSecurity of
-- (Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
-- ((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
-- bodyResult <- evalBody (sortBody Body.parser) request
-- case bodyResult of
-- (Ok bodyParam, _) -> do
-- response <- object # handler resourceParam securityParam queryParam bodyParam headersParam responderParam
-- respond $ toWaiResponse response
-- _ -> respond $ WAI.responseLBS HTTP.status400 mempty mempty -- TODO: Return 400 for now but can be more specific depending on content-type, etc.
-- _ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
-- _ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
-- HTTP.DELETE -> case delete of
-- Nothing -> respond $ WAI.responseLBS HTTP.status405 mempty mempty
-- Just (DELETE {handler, object}) ->
-- case evalSecurity (sortSecurity Security.parser) reqSecurity of
-- (Ok securityParam, _) -> case (Query.eval Query.parser reqQuery, Headers.eval Headers.parser reqHeaders, Responder.eval Responder.parser ()) of
-- ((Ok queryParam, _), (Ok headersParam, _), (Ok responderParam, _)) -> do
-- response <- object # handler resourceParam securityParam queryParam headersParam responderParam
-- respond $ toWaiResponse response
-- _ -> respond $ WAI.responseLBS HTTP.status422 mempty mempty -- TODO: Return 422 Unprocessable Content based on errors returned by Scripts
-- _ -> respond $ WAI.responseLBS HTTP.status401 mempty mempty -- TODO: Return 401 Unauthorized
-- _ -> respond $ WAI.responseLBS HTTP.status501 mempty mempty -- TODO: Implement cases for remaing Standard HTTP methods
data Option where
None :: Option
Some :: forall resource. (Route resource, resource) -> Option
-- data Option where
-- None :: Option
-- Some :: forall resource. (Route resource, resource) -> Option
getPathItemByPath :: [Text.Text] -> Routes resources -> Option
getPathItemByPath reqPath Nil = None
getPathItemByPath reqPath (route@(Route @resource _ _ _ _ _ _) :& t) = case Path.eval (Path.parser @resource) reqPath of
(Ok resourceParam, _) -> Some (route, resourceParam)
_ -> getPathItemByPath reqPath t
-- getPathItemByPath :: [Text.Text] -> Routes resources -> Option
-- getPathItemByPath reqPath Nil = None
-- getPathItemByPath reqPath (route@(Route @resource _ _ _ _ _ _) :& t) = case Path.eval (Path.parser @resource) reqPath of
-- (Ok resourceParam, _) -> Some (route, resourceParam)
-- _ -> getPathItemByPath reqPath t
evalBody :: NonEmpty (Body.Parser a) -> WAI.Request -> IO (Result [Body.Error] a, Body.RequestBody)
evalBody (h :| t) request = do
state <- case WAI.getRequestBodyType request of
Just (WAI.Multipart _boundary) -> Body.RequestBodyMultipart <$> WAI.parseRequestBodyEx WAI.defaultParseRequestBodyOptions WAI.lbsBackEnd request
_ -> Body.RequestBodyRaw <$> WAI.lazyRequestBody request
case first (first pure) $ Body.eval h state of
(ok@(Ok _), s) -> return (ok, s)
_ -> return $ loop state t
where
loop :: Body.RequestBody -> [Body.Parser a] -> (Result [Body.Error] a, Body.RequestBody)
loop state [] = (Fail [], state)
loop state (h : t) = case first (first pure) $ Body.eval h state of
(ok@(Ok _), state') -> (ok, state')
_ -> loop state t
-- evalBody :: NonEmpty (Body.Parser a) -> WAI.Request -> IO (Result [Body.Error] a, Body.RequestBody)
-- evalBody (h :| t) request = do
-- state <- case WAI.getRequestBodyType request of
-- Just (WAI.Multipart _boundary) -> Body.RequestBodyMultipart <$> WAI.parseRequestBodyEx WAI.defaultParseRequestBodyOptions WAI.lbsBackEnd request
-- _ -> Body.RequestBodyRaw <$> WAI.lazyRequestBody request
-- case first (first pure) $ Body.eval h state of
-- (ok@(Ok _), s) -> return (ok, s)
-- _ -> return $ loop state t
-- where
-- loop :: Body.RequestBody -> [Body.Parser a] -> (Result [Body.Error] a, Body.RequestBody)
-- loop state [] = (Fail [], state)
-- loop state (h : t) = case first (first pure) $ Body.eval h state of
-- (ok@(Ok _), state') -> (ok, state')
-- _ -> loop state t
sortBody :: NonEmpty (Body.Parser a) -> NonEmpty (Body.Parser a)
sortBody = NonEmpty.sortBy comparer
where
comparer :: Body.Parser a -> Body.Parser a -> Ordering
comparer Body.None Body.None = EQ
comparer (Body.FMap _ Body.None) (Body.FMap _ Body.None) = EQ
comparer (Body.FMap _ Body.None) _ = GT
comparer _ (Body.FMap _ Body.None) = LT
comparer Body.None _body = GT
comparer _body Body.None = LT
-- sortBody :: NonEmpty (Body.Parser a) -> NonEmpty (Body.Parser a)
-- sortBody = NonEmpty.sortBy comparer
-- where
-- comparer :: Body.Parser a -> Body.Parser a -> Ordering
-- comparer Body.None Body.None = EQ
-- comparer (Body.FMap _ Body.None) (Body.FMap _ Body.None) = EQ
-- comparer (Body.FMap _ Body.None) _ = GT
-- comparer _ (Body.FMap _ Body.None) = LT
-- comparer Body.None _body = GT
-- comparer _body Body.None = LT
evalSecurity :: NonEmpty (Security.Parser a) -> Secure.State -> (Result Security.Error a, Secure.State)
evalSecurity (h :| t) state = case Security.eval h state of
(ok@(Ok _), s) -> (ok, s)
_ -> loop state t
where
loop :: Secure.State -> [Security.Parser a] -> (Result Security.Error a, Secure.State)
loop state [] = (Fail $ Security.SecureError Secure.ParseFail, state)
loop state (h : t) = case Security.eval h state of
(ok@(Ok _), state') -> (ok, state')
_ -> loop state t
-- evalSecurity :: NonEmpty (Security.Parser a) -> Secure.State -> (Result Security.Error a, Secure.State)
-- evalSecurity (h :| t) state = case Security.eval h state of
-- (ok@(Ok _), s) -> (ok, s)
-- _ -> loop state t
-- where
-- loop :: Secure.State -> [Security.Parser a] -> (Result Security.Error a, Secure.State)
-- loop state [] = (Fail $ Security.SecureError Secure.ParseFail, state)
-- loop state (h : t) = case Security.eval h state of
-- (ok@(Ok _), state') -> (ok, state')
-- _ -> loop state t
sortSecurity :: NonEmpty (Security.Parser a) -> NonEmpty (Security.Parser a)
sortSecurity = NonEmpty.sortBy comparer
where
comparer :: Security.Parser a -> Security.Parser a -> Ordering
comparer Security.None Security.None = EQ
comparer (Security.FMap _ Security.None) (Security.FMap _ Security.None) = EQ
comparer Security.None _ = GT
comparer (Security.FMap _ Security.None) _ = GT
comparer _ Security.None = LT
comparer _ (Security.FMap _ Security.None) = LT
-- sortSecurity :: NonEmpty (Security.Parser a) -> NonEmpty (Security.Parser a)
-- sortSecurity = NonEmpty.sortBy comparer
-- where
-- comparer :: Security.Parser a -> Security.Parser a -> Ordering
-- comparer Security.None Security.None = EQ
-- comparer (Security.FMap _ Security.None) (Security.FMap _ Security.None) = EQ
-- comparer Security.None _ = GT
-- comparer (Security.FMap _ Security.None) _ = GT
-- comparer _ Security.None = LT
-- comparer _ (Security.FMap _ Security.None) = LT
toOpenAPI ::
Server resource ->
OAPI.OpenApi
toOpenAPI Server {info, description, routes, url} =
mempty
{ OAPI._openApiInfo = info,
OAPI._openApiServers =
[ OAPI.Server
(Text.intercalate "/" url)
description
mempty
],
OAPI._openApiPaths = pathItemsToOpenAPIPaths routes,
OAPI._openApiOpenapi = OpenApiSpecVersion {getVersion = Version.Version [3, 0, 3] []}
}
-- toOpenAPI ::
-- Server resource ->
-- OAPI.OpenApi
-- toOpenAPI Server {info, description, routes, url} =
-- mempty
-- { OAPI._openApiInfo = info,
-- OAPI._openApiServers =
-- [ OAPI.Server
-- (Text.intercalate "/" url)
-- description
-- mempty
-- ],
-- OAPI._openApiPaths = pathItemsToOpenAPIPaths routes,
-- OAPI._openApiOpenapi = OpenApiSpecVersion {getVersion = Version.Version [3, 0, 3] []}
-- }
pathItemsToOpenAPIPaths :: Routes resources -> InsOrdHashMap.InsOrdHashMap FilePath OAPI.PathItem
pathItemsToOpenAPIPaths Nil = InsOrdHashMap.fromList []
pathItemsToOpenAPIPaths (h :& t) = let (filePath, pathItem) = toOpenAPIPathItem h in InsOrdHashMap.insert filePath pathItem $ pathItemsToOpenAPIPaths t
-- pathItemsToOpenAPIPaths :: Routes resources -> InsOrdHashMap.InsOrdHashMap FilePath OAPI.PathItem
-- pathItemsToOpenAPIPaths Nil = InsOrdHashMap.fromList []
-- pathItemsToOpenAPIPaths (h :& t) = let (filePath, pathItem) = toOpenAPIPathItem h in InsOrdHashMap.insert filePath pathItem $ pathItemsToOpenAPIPaths t
toOpenAPIPathItem :: Route resource -> (FilePath, OAPI.PathItem)
toOpenAPIPathItem (Route @resource summary description get post put delete) = (pathName, pathItem)
where
pathName :: FilePath
pathName = renderPath $ Path.parser @resource
-- toOpenAPIPathItem :: Route resource -> (FilePath, OAPI.PathItem)
-- toOpenAPIPathItem (Route @resource summary description get post put delete) = (pathName, pathItem)
-- where
-- pathName :: FilePath
-- pathName = renderPath $ Path.parser @resource
pathItem :: OAPI.PathItem
pathItem =
mempty
{ OAPI._pathItemSummary = summary,
OAPI._pathItemDescription = description,
OAPI._pathItemGet = fmap toGetOperation get,
OAPI._pathItemPost = fmap toPostOperation post,
OAPI._pathItemPut = fmap toPutOperation put,
OAPI._pathItemDelete = fmap toDeleteOperation delete
}
-- pathItem :: OAPI.PathItem
-- pathItem =
-- mempty
-- { OAPI._pathItemSummary = summary,
-- OAPI._pathItemDescription = description,
-- OAPI._pathItemGet = fmap toGetOperation get,
-- OAPI._pathItemPost = fmap toPostOperation post,
-- OAPI._pathItemPut = fmap toPutOperation put,
-- OAPI._pathItemDelete = fmap toDeleteOperation delete
-- }
toGetOperation :: GET m resource security query headers responder -> OAPI.Operation
toGetOperation (GET @_ @resource @security @query @headers @responder summary description _ _) =
mempty
{ OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
OAPI._operationResponses = toResponses $ Responder.parser @responder,
OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
OAPI._operationSummary = summary,
OAPI._operationDescription = description
}
-- toGetOperation :: GET m resource security query headers responder -> OAPI.Operation
-- toGetOperation (GET @_ @resource @security @query @headers @responder summary description _ _) =
-- mempty
-- { OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
-- OAPI._operationResponses = toResponses $ Responder.parser @responder,
-- OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
-- OAPI._operationSummary = summary,
-- OAPI._operationDescription = description
-- }
toPostOperation :: POST m resource security query body headers responder -> OAPI.Operation
toPostOperation (POST @_ @resource @security @query @body @headers @responder summary description _ _) =
mempty
{ OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
OAPI._operationRequestBody = toOpenAPIRequestBody $ Body.parser @body,
OAPI._operationResponses = toResponses $ Responder.parser @responder,
OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
OAPI._operationSummary = summary,
OAPI._operationDescription = description
}
-- toPostOperation :: POST m resource security query body headers responder -> OAPI.Operation
-- toPostOperation (POST @_ @resource @security @query @body @headers @responder summary description _ _) =
-- mempty
-- { OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
-- OAPI._operationRequestBody = toOpenAPIRequestBody $ Body.parser @body,
-- OAPI._operationResponses = toResponses $ Responder.parser @responder,
-- OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
-- OAPI._operationSummary = summary,
-- OAPI._operationDescription = description
-- }
toPutOperation :: PUT m resource security query body headers responder -> OAPI.Operation
toPutOperation (PUT @_ @resource @security @query @body @headers @responder summary description _ _) =
mempty
{ OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
OAPI._operationRequestBody = toOpenAPIRequestBody $ Body.parser @body,
OAPI._operationResponses = toResponses $ Responder.parser @responder,
OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
OAPI._operationSummary = summary,
OAPI._operationDescription = description
}
-- toPutOperation :: PUT m resource security query body headers responder -> OAPI.Operation
-- toPutOperation (PUT @_ @resource @security @query @body @headers @responder summary description _ _) =
-- mempty
-- { OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
-- OAPI._operationRequestBody = toOpenAPIRequestBody $ Body.parser @body,
-- OAPI._operationResponses = toResponses $ Responder.parser @responder,
-- OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
-- OAPI._operationSummary = summary,
-- OAPI._operationDescription = description
-- }
toDeleteOperation :: DELETE m resource security query headers responder -> OAPI.Operation
toDeleteOperation (DELETE @_ @resource @security @query @headers @responder summary description _ _) =
mempty
{ OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
OAPI._operationResponses = toResponses $ Responder.parser @responder,
OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
OAPI._operationSummary = summary,
OAPI._operationDescription = description
}
-- toDeleteOperation :: DELETE m resource security query headers responder -> OAPI.Operation
-- toDeleteOperation (DELETE @_ @resource @security @query @headers @responder summary description _ _) =
-- mempty
-- { OAPI._operationParameters = toParameters (Path.parser @resource, Query.parser @query, Headers.parser @headers),
-- OAPI._operationResponses = toResponses $ Responder.parser @responder,
-- OAPI._operationSecurity = toSecurityRequirements $ Security.parser @security,
-- OAPI._operationSummary = summary,
-- OAPI._operationDescription = description
-- }
toParameters :: (Path.Parser resource, Query.Parser q, Headers.Parser h) -> [OAPI.Referenced OAPI.Param]
toParameters (path, query, headers) = pathParameters path <> queryParameters query <> headersParameters headers
where
pathParameters :: Path.Parser resource -> [OAPI.Referenced OAPI.Param]
pathParameters path = case path of
Path.FMap f p -> pathParameters p
Path.Pure _ -> mempty
Path.Apply pf px -> pathParameters pf <> pathParameters px
Path.Static _ -> mempty
Path.Param @p name ->
[ OAPI.Inline $
mempty
{ OAPI._paramName = name,
OAPI._paramRequired = Just True,
OAPI._paramIn = OAPI.ParamPath,
OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
}
]
-- toParameters :: (Path.Parser resource, Query.Parser q, Headers.Parser h) -> [OAPI.Referenced OAPI.Param]
-- toParameters (path, query, headers) = pathParameters path <> queryParameters query <> headersParameters headers
-- where
-- pathParameters :: Path.Parser resource -> [OAPI.Referenced OAPI.Param]
-- pathParameters path = case path of
-- Path.FMap f p -> pathParameters p
-- Path.Pure _ -> mempty
-- Path.Apply pf px -> pathParameters pf <> pathParameters px
-- Path.Static _ -> mempty
-- Path.Param @p name ->
-- [ OAPI.Inline $
-- mempty
-- { OAPI._paramName = name,
-- OAPI._paramRequired = Just True,
-- OAPI._paramIn = OAPI.ParamPath,
-- OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
-- }
-- ]
queryParameters :: Query.Parser q -> [OAPI.Referenced OAPI.Param]
queryParameters query = case query of
Query.FMap f q -> queryParameters q
Query.Pure _ -> mempty
Query.Apply pf px -> queryParameters pf <> queryParameters px
Query.Param @p name ->
[ OAPI.Inline $
mempty
{ OAPI._paramName = Text.decodeUtf8 name,
OAPI._paramRequired = Just True,
OAPI._paramIn = OAPI.ParamQuery,
OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
}
]
Query.Flag name ->
[ OAPI.Inline $
mempty
{ OAPI._paramName = Text.decodeUtf8 name,
OAPI._paramRequired = Just True,
OAPI._paramIn = OAPI.ParamQuery,
OAPI._paramAllowEmptyValue = Just True
}
]
Query.Optional @p query' -> case query' of
Query.Param _ -> do
param <- queryParameters query'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
Query.Flag _ -> do
param <- queryParameters query'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
_ -> queryParameters query'
Query.Option @p def query' -> case query' of
Query.Param _ -> do
param <- queryParameters query'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
_ -> queryParameters query'
-- queryParameters :: Query.Parser q -> [OAPI.Referenced OAPI.Param]
-- queryParameters query = case query of
-- Query.FMap f q -> queryParameters q
-- Query.Pure _ -> mempty
-- Query.Apply pf px -> queryParameters pf <> queryParameters px
-- Query.Param @p name ->
-- [ OAPI.Inline $
-- mempty
-- { OAPI._paramName = Text.decodeUtf8 name,
-- OAPI._paramRequired = Just True,
-- OAPI._paramIn = OAPI.ParamQuery,
-- OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
-- }
-- ]
-- Query.Flag name ->
-- [ OAPI.Inline $
-- mempty
-- { OAPI._paramName = Text.decodeUtf8 name,
-- OAPI._paramRequired = Just True,
-- OAPI._paramIn = OAPI.ParamQuery,
-- OAPI._paramAllowEmptyValue = Just True
-- }
-- ]
-- Query.Optional @p query' -> case query' of
-- Query.Param _ -> do
-- param <- queryParameters query'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
-- Query.Flag _ -> do
-- param <- queryParameters query'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
-- _ -> queryParameters query'
-- Query.Option @p def query' -> case query' of
-- Query.Param _ -> do
-- param <- queryParameters query'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
-- _ -> queryParameters query'
headersParameters :: Headers.Parser h -> [OAPI.Referenced OAPI.Param]
headersParameters headers = case headers of
Headers.FMap f h -> headersParameters h
Headers.Pure _ -> mempty
Headers.Apply pf px -> headersParameters pf <> headersParameters px
Headers.Param @p name ->
[ OAPI.Inline $
mempty
{ OAPI._paramName = Text.decodeUtf8 $ CI.original name,
OAPI._paramRequired = Just True,
OAPI._paramIn = OAPI.ParamHeader,
OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
}
]
Headers.Cookie @p name ->
[ OAPI.Inline $
mempty
{ OAPI._paramName = Text.decodeUtf8 name,
OAPI._paramRequired = Just True,
OAPI._paramIn = OAPI.ParamCookie,
OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
}
]
Headers.Optional @p headers' -> case headers' of
Headers.Param _ -> do
param <- headersParameters headers'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
Headers.Cookie _ -> do
param <- headersParameters headers'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
_ -> headersParameters headers'
Headers.Option @p def headers' -> case headers' of
Headers.Param _ -> do
param <- headersParameters headers'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
Headers.Cookie _ -> do
param <- headersParameters headers'
pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
_ -> headersParameters headers'
-- headersParameters :: Headers.Parser h -> [OAPI.Referenced OAPI.Param]
-- headersParameters headers = case headers of
-- Headers.FMap f h -> headersParameters h
-- Headers.Pure _ -> mempty
-- Headers.Apply pf px -> headersParameters pf <> headersParameters px
-- Headers.Param @p name ->
-- [ OAPI.Inline $
-- mempty
-- { OAPI._paramName = Text.decodeUtf8 $ CI.original name,
-- OAPI._paramRequired = Just True,
-- OAPI._paramIn = OAPI.ParamHeader,
-- OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
-- }
-- ]
-- Headers.Cookie @p name ->
-- [ OAPI.Inline $
-- mempty
-- { OAPI._paramName = Text.decodeUtf8 name,
-- OAPI._paramRequired = Just True,
-- OAPI._paramIn = OAPI.ParamCookie,
-- OAPI._paramSchema = Just $ OAPI.Inline $ OAPI._namedSchemaSchema $ OAPI.undeclare $ OAPI.declareNamedSchema @p Proxy
-- }
-- ]
-- Headers.Optional @p headers' -> case headers' of
-- Headers.Param _ -> do
-- param <- headersParameters headers'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
-- Headers.Cookie _ -> do
-- param <- headersParameters headers'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False}) param
-- _ -> headersParameters headers'
-- Headers.Option @p def headers' -> case headers' of
-- Headers.Param _ -> do
-- param <- headersParameters headers'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
-- Headers.Cookie _ -> do
-- param <- headersParameters headers'
-- pure $ fmap (\param -> param {OAPI._paramRequired = Just False, OAPI._paramSchema = fmap (fmap (\schema -> schema {OAPI._schemaDefault = Just $ Aeson.toJSON def})) param._paramSchema}) param
-- _ -> headersParameters headers'
toSecurityRequirements :: NonEmpty (Security.Parser s) -> [OAPI.SecurityRequirement]
toSecurityRequirements security = []
-- toSecurityRequirements :: NonEmpty (Security.Parser s) -> [OAPI.SecurityRequirement]
-- toSecurityRequirements security = []
toOpenAPIRequestBody :: NonEmpty (Body.Parser b) -> Maybe (OAPI.Referenced OAPI.RequestBody)
toOpenAPIRequestBody body = Nothing
-- toOpenAPIRequestBody :: NonEmpty (Body.Parser b) -> Maybe (OAPI.Referenced OAPI.RequestBody)
-- toOpenAPIRequestBody body = Nothing
toResponses :: Responder.Parser r -> OAPI.Responses
toResponses responder = mempty
-- toResponses :: Responder.Parser r -> OAPI.Responses
-- toResponses responder = mempty
renderPath :: Path.Parser a -> FilePath
renderPath path = case path of
Path.FMap f p -> renderPath p
Path.Pure _ -> mempty
Path.Apply pf px -> renderPath pf <> renderPath px
Path.Static t -> "/" <> Text.unpack t
Path.Param @p name -> "/{" <> Text.unpack name <> "}"
-- renderPath :: Path.Parser a -> FilePath
-- renderPath path = case path of
-- Path.FMap f p -> renderPath p
-- Path.Pure _ -> mempty
-- Path.Apply pf px -> renderPath pf <> renderPath px
-- Path.Static t -> "/" <> Text.unpack t
-- Path.Param @p name -> "/{" <> Text.unpack name <> "}"