diff --git a/lib/okapi.cabal b/lib/okapi.cabal index 840c830..5d518a7 100644 --- a/lib/okapi.cabal +++ b/lib/okapi.cabal @@ -41,6 +41,7 @@ library Okapi.Parser.Responder Okapi.Parser.Responder.AddHeader Okapi.Parser.Security + Okapi.Plug other-modules: Paths_okapi hs-source-dirs: diff --git a/lib/src/Okapi/Parser/Body.hs b/lib/src/Okapi/Parser/Body.hs index ab13bb6..5e22559 100644 --- a/lib/src/Okapi/Parser/Body.hs +++ b/lib/src/Okapi/Parser/Body.hs @@ -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 \ No newline at end of file +-- 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 \ No newline at end of file diff --git a/lib/src/Okapi/Plug.hs b/lib/src/Okapi/Plug.hs new file mode 100644 index 0000000..e3d0173 --- /dev/null +++ b/lib/src/Okapi/Plug.hs @@ -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 diff --git a/lib/src/Okapi/Route.hs b/lib/src/Okapi/Route.hs index d901b05..0319531 100644 --- a/lib/src/Okapi/Route.hs +++ b/lib/src/Okapi/Route.hs @@ -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 diff --git a/lib/src/Okapi/Server.hs b/lib/src/Okapi/Server.hs index 097d79f..68b8b0e 100644 --- a/lib/src/Okapi/Server.hs +++ b/lib/src/Okapi/Server.hs @@ -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 <> "}"