mirror of
https://github.com/monadicsystems/okapi.git
synced 2024-11-22 17:04:17 +03:00
Add working calculator API example
This commit is contained in:
parent
d35f0d359d
commit
44c5c713c1
@ -36,6 +36,7 @@ library
|
|||||||
Okapi.App
|
Okapi.App
|
||||||
Okapi.Route
|
Okapi.Route
|
||||||
Okapi.Response
|
Okapi.Response
|
||||||
|
Example
|
||||||
other-modules:
|
other-modules:
|
||||||
Paths_okapi
|
Paths_okapi
|
||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
|
106
lib/src/Example.hs
Normal file
106
lib/src/Example.hs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
{-# LANGUAGE ApplicativeDo #-}
|
||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE ImportQualifiedPost #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedRecordDot #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE PolyKinds #-}
|
||||||
|
{-# LANGUAGE QualifiedDo #-}
|
||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE StandaloneKindSignatures #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
||||||
|
|
||||||
|
{-# HLINT ignore "Use if" #-}
|
||||||
|
|
||||||
|
module Example where
|
||||||
|
|
||||||
|
import Control.Natural qualified as Natural
|
||||||
|
import Data.Binary.Builder qualified as Builder
|
||||||
|
import Data.ByteString.Lazy qualified as LBS
|
||||||
|
import Data.ByteString.Lazy.Char8 qualified as LBSChar8
|
||||||
|
import Data.Kind
|
||||||
|
import Data.List qualified as List
|
||||||
|
import Data.Text qualified as Text
|
||||||
|
import Data.Type.Equality qualified as Equality
|
||||||
|
import Data.Typeable qualified as Typeable
|
||||||
|
import Data.Vault.Lazy qualified as Vault
|
||||||
|
import GHC.Exts qualified as Exts
|
||||||
|
import GHC.Generics qualified as Generics
|
||||||
|
import GHC.Natural qualified as Natural
|
||||||
|
import Network.HTTP.Types qualified as HTTP
|
||||||
|
import Network.Wai qualified as Wai
|
||||||
|
import Network.Wai.Handler.Warp qualified as Warp
|
||||||
|
import Okapi.App {- qualified as App -}
|
||||||
|
import Okapi.Response {- qualified as Response -}
|
||||||
|
import Web.HttpApiData qualified as Web
|
||||||
|
|
||||||
|
data Operator = Add | Sub | Mul | Div | Sq | Neg deriving (Show)
|
||||||
|
|
||||||
|
isUnary :: Operator -> Bool
|
||||||
|
isUnary Sq = True
|
||||||
|
isUnary Neg = True
|
||||||
|
isUnary _ = False
|
||||||
|
|
||||||
|
instance Web.FromHttpApiData Operator where
|
||||||
|
parseUrlPiece "add" = Right Add
|
||||||
|
parseUrlPiece "sub" = Right Sub
|
||||||
|
parseUrlPiece "minus" = Right Sub
|
||||||
|
parseUrlPiece "mul" = Right Mul
|
||||||
|
parseUrlPiece "div" = Right Div
|
||||||
|
parseUrlPiece "neg" = Right Neg
|
||||||
|
parseUrlPiece "sq" = Right Sq
|
||||||
|
parseUrlPiece "square" = Right Sq
|
||||||
|
parseUrlPiece _ = Left "Can't parse operator..."
|
||||||
|
|
||||||
|
unaryF =
|
||||||
|
responder @200 @'[] @Text.Text @Int
|
||||||
|
. responder @500 @'[] @Text.Text @Text.Text
|
||||||
|
. method HTTP.GET id
|
||||||
|
|
||||||
|
binaryF =
|
||||||
|
param @Int
|
||||||
|
. responder @200 @'[] @Text.Text @Int
|
||||||
|
. responder @500 @'[] @Text.Text @Text.Text
|
||||||
|
. responder @403 @'[] @Text.Text @Text.Text
|
||||||
|
. method HTTP.GET id
|
||||||
|
|
||||||
|
calc =
|
||||||
|
lit "calc"
|
||||||
|
. param @Operator
|
||||||
|
. param @Int
|
||||||
|
$ choice
|
||||||
|
[ unaryF \operator x ok wrongArgs _req -> return
|
||||||
|
$ case operator of
|
||||||
|
Sq -> ok noHeaders (x * x)
|
||||||
|
Neg -> ok noHeaders (x * (-1))
|
||||||
|
_ -> wrongArgs noHeaders $ (Text.pack $ show operator) <> " needs two arguments."
|
||||||
|
, binaryF \operator x y ok wrongArgs divByZeroErr _req -> do
|
||||||
|
return
|
||||||
|
$ case operator of
|
||||||
|
Add -> ok noHeaders (x + y)
|
||||||
|
Sub -> ok noHeaders (x - y)
|
||||||
|
Mul -> ok noHeaders (x * y)
|
||||||
|
Div ->
|
||||||
|
if y == 0
|
||||||
|
then divByZeroErr noHeaders "You can't divide by 0."
|
||||||
|
else ok noHeaders (div x y)
|
||||||
|
_ -> wrongArgs noHeaders $ (Text.pack $ show operator) <> " needs one argument."
|
||||||
|
]
|
||||||
|
|
||||||
|
main =
|
||||||
|
Warp.run 8003 $ calc `withDefault` \_ resp ->
|
||||||
|
resp $ Wai.responseLBS HTTP.status404 [] "Not Found..."
|
@ -45,7 +45,7 @@ import Data.Typeable qualified as Typeable
|
|||||||
import Data.Vault.Lazy qualified as Vault
|
import Data.Vault.Lazy qualified as Vault
|
||||||
import GHC.Exts qualified as Exts
|
import GHC.Exts qualified as Exts
|
||||||
import GHC.Generics qualified as Generics
|
import GHC.Generics qualified as Generics
|
||||||
import GHC.Natural qualified as Natural
|
import GHC.TypeNats qualified as Nat
|
||||||
import Network.HTTP.Types qualified as HTTP
|
import Network.HTTP.Types qualified as HTTP
|
||||||
import Network.Wai qualified as Wai
|
import Network.Wai qualified as Wai
|
||||||
import Network.Wai.Handler.Warp qualified as Warp
|
import Network.Wai.Handler.Warp qualified as Warp
|
||||||
@ -125,9 +125,10 @@ data Node (r :: [Type]) where
|
|||||||
Node r ->
|
Node r ->
|
||||||
Node r
|
Node r
|
||||||
Responder ::
|
Responder ::
|
||||||
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type) (r :: [Type]).
|
forall (status :: Nat.Nat) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type) (r :: [Type]).
|
||||||
( Response.ContentType contentType
|
( Response.ContentType contentType
|
||||||
, Response.ToContentType contentType resultType
|
, Response.ToContentType contentType resultType
|
||||||
|
, Nat.KnownNat status
|
||||||
, Typeable.Typeable status
|
, Typeable.Typeable status
|
||||||
, Typeable.Typeable headerKeys
|
, Typeable.Typeable headerKeys
|
||||||
, Typeable.Typeable contentType
|
, Typeable.Typeable contentType
|
||||||
@ -144,14 +145,13 @@ data Node (r :: [Type]) where
|
|||||||
Handler r env ->
|
Handler r env ->
|
||||||
Node r
|
Node r
|
||||||
|
|
||||||
{-
|
combine ::
|
||||||
smush ::
|
|
||||||
forall (r :: [Type]).
|
forall (r :: [Type]).
|
||||||
(Typeable.Typeable r) =>
|
(Typeable.Typeable r) =>
|
||||||
Node r ->
|
Node r ->
|
||||||
Node r ->
|
Node r ->
|
||||||
Maybe (Node r)
|
Maybe (Node r)
|
||||||
smush a1 a2 = case (a1, a2) of
|
combine n1 n2 = case (n1, n2) of
|
||||||
(Choice @r1 children1, Choice @r2 children2) -> case (Typeable.eqT @r1 @r2) of
|
(Choice @r1 children1, Choice @r2 children2) -> case (Typeable.eqT @r1 @r2) of
|
||||||
Just Typeable.Refl -> Just $ choice @r1 (children1 <> children2)
|
Just Typeable.Refl -> Just $ choice @r1 (children1 <> children2)
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
@ -179,9 +179,9 @@ smush a1 a2 = case (a1, a2) of
|
|||||||
(Body @a1 @r1 child1, Body @a2 @r2 child2) -> case (Typeable.eqT @a1 @a2, Typeable.eqT @r1 @r2) of
|
(Body @a1 @r1 child1, Body @a2 @r2 child2) -> case (Typeable.eqT @a1 @a2, Typeable.eqT @r1 @r2) of
|
||||||
(Just Typeable.Refl, Just Typeable.Refl) -> Just $ body @a1 @r1 $ choice @(r1 :-> a1) [child1, child2]
|
(Just Typeable.Refl, Just Typeable.Refl) -> Just $ body @a1 @r1 $ choice @(r1 :-> a1) [child1, child2]
|
||||||
(_, _) -> Nothing
|
(_, _) -> Nothing
|
||||||
(Apply @t1 @r1 tag1 atom1, Apply @t2 @r2 tag2 atom2) -> case (Typeable.eqT @t1 @t2, Typeable.eqT @r1 @r2) of
|
(Apply @t1 @r1 tag1 node1, Apply @t2 @r2 tag2 node2) -> case (Typeable.eqT @t1 @t2, Typeable.eqT @r1 @r2) of
|
||||||
(Just Typeable.Refl, Just Typeable.Refl) -> case tag1 == tag2 of
|
(Just Typeable.Refl, Just Typeable.Refl) -> case tag1 == tag2 of
|
||||||
True -> Just $ apply @t1 @r1 tag1 $ choice @r1 [atom1, atom2]
|
True -> Just $ apply @t1 @r1 tag1 $ choice @r1 [node1, node2]
|
||||||
False -> Nothing
|
False -> Nothing
|
||||||
(_, _) -> Nothing
|
(_, _) -> Nothing
|
||||||
(Responder @s1 @hk1 @ct1 @rt1 @r1 child1, Responder @s2 @hk2 @ct2 @rt2 @r2 child2) -> case (Typeable.eqT @s1 @s2, Typeable.eqT @hk1 @hk2, Typeable.eqT @ct1 @ct2, Typeable.eqT @rt1 @rt2, Typeable.eqT @r1 @r2) of
|
(Responder @s1 @hk1 @ct1 @rt1 @r1 child1, Responder @s2 @hk2 @ct2 @rt2 @r2 child2) -> case (Typeable.eqT @s1 @s2, Typeable.eqT @hk1 @hk2, Typeable.eqT @ct1 @ct2, Typeable.eqT @rt1 @rt2, Typeable.eqT @r1 @r2) of
|
||||||
@ -191,19 +191,18 @@ smush a1 a2 = case (a1, a2) of
|
|||||||
(a1', Choice @r2 children) -> Just $ choice @r2 (a1' : children)
|
(a1', Choice @r2 children) -> Just $ choice @r2 (a1' : children)
|
||||||
-- Method is not comparable
|
-- Method is not comparable
|
||||||
(_, _) -> Nothing
|
(_, _) -> Nothing
|
||||||
-}
|
|
||||||
-- smushes :: Node r -> Node r
|
flatten :: (Typeable.Typeable r) => Node r -> Node r
|
||||||
-- smushes (Choice []) = Choice []
|
flatten (Choice [node]) = node
|
||||||
-- smushes (Choice singleton@[atom]) = Choice singleton
|
flatten (Choice (node1 : node2 : nodes)) = case node1 `combine` node2 of
|
||||||
-- smushes (Choice (atom1 : atom2 : atoms)) = case atom1 `smush` atom2 of
|
Just newNode -> flatten $ choice (newNode : nodes)
|
||||||
-- Just newAtom -> smushes $ Choice (newAtom : atoms)
|
Nothing ->
|
||||||
-- Nothing ->
|
choice
|
||||||
-- List.concat
|
[ flatten $ choice (node1 : nodes)
|
||||||
-- [ smushes (atom1 : atoms)
|
, flatten $ choice (node2 : nodes)
|
||||||
-- , smushes (atom2 : atoms)
|
, flatten $ choice nodes
|
||||||
-- , smushes atoms
|
]
|
||||||
-- ]
|
flatten node = node
|
||||||
-- smushes atom = atom
|
|
||||||
|
|
||||||
argsTest :: Handler '[] IO
|
argsTest :: Handler '[] IO
|
||||||
argsTest = \request -> do
|
argsTest = \request -> do
|
||||||
@ -307,9 +306,10 @@ scope ::
|
|||||||
scope tag children = apply @t @r tag $ route @a @r children
|
scope tag children = apply @t @r tag $ route @a @r children
|
||||||
|
|
||||||
responder ::
|
responder ::
|
||||||
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type) (r :: [Type]).
|
forall (status :: Nat.Nat) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type) (r :: [Type]).
|
||||||
( Response.ContentType contentType
|
( Response.ContentType contentType
|
||||||
, Response.ToContentType contentType resultType
|
, Response.ToContentType contentType resultType
|
||||||
|
, Nat.KnownNat status
|
||||||
, Typeable.Typeable status
|
, Typeable.Typeable status
|
||||||
, Typeable.Typeable headerKeys
|
, Typeable.Typeable headerKeys
|
||||||
, Typeable.Typeable contentType
|
, Typeable.Typeable contentType
|
||||||
@ -351,13 +351,6 @@ data HelloWorldBody = HelloWorldBody deriving (Typeable.Typeable)
|
|||||||
|
|
||||||
data ByeWorldBody = ByeWorldBody {error :: Text.Text, randomN :: Int} deriving (Typeable.Typeable)
|
data ByeWorldBody = ByeWorldBody {error :: Text.Text, randomN :: Int} deriving (Typeable.Typeable)
|
||||||
|
|
||||||
textToBytes :: Text.Text -> LBSChar8.ByteString
|
|
||||||
textToBytes = undefined
|
|
||||||
|
|
||||||
instance Response.ContentType Text.Text where
|
|
||||||
contentTypeName = "text/plain"
|
|
||||||
contentTypeBody text = Response.BodyBytes $ textToBytes text
|
|
||||||
|
|
||||||
instance Response.ToContentType Text.Text HelloWorldBody where
|
instance Response.ToContentType Text.Text HelloWorldBody where
|
||||||
toContentType HelloWorldBody = "Hello World! :)"
|
toContentType HelloWorldBody = "Hello World! :)"
|
||||||
|
|
||||||
@ -366,14 +359,17 @@ instance Response.ToContentType Text.Text ByeWorldBody where
|
|||||||
|
|
||||||
-- test :: _
|
-- test :: _
|
||||||
test =
|
test =
|
||||||
myResponders
|
lit "some"
|
||||||
. lit "some"
|
. myOtherResponders
|
||||||
. lit "world"
|
. lit "world"
|
||||||
. param @Float
|
. param @Float
|
||||||
$ method
|
$ method
|
||||||
HTTP.GET
|
HTTP.GET
|
||||||
id
|
id
|
||||||
\helloWorld byeWorld float req -> do
|
testHandle
|
||||||
|
|
||||||
|
-- testHandle :: _
|
||||||
|
testHandle = \helloWorld byeWorld watWorld float (req :: Wai.Request) -> do
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
test2 =
|
test2 =
|
||||||
@ -387,18 +383,15 @@ test2 =
|
|||||||
undefined
|
undefined
|
||||||
]
|
]
|
||||||
, param @Float
|
, param @Float
|
||||||
$ method HTTP.PUT id \req -> do
|
$ method HTTP.PUT id \float req -> do
|
||||||
undefined
|
undefined
|
||||||
, choice
|
, choice
|
||||||
[ lit "lol"
|
[ somePath $ method HTTP.GET id \helloWorld byeWorld n -> do
|
||||||
. lit "foo"
|
|
||||||
. param @Int
|
|
||||||
$ method HTTP.GET id \n req -> do
|
|
||||||
undefined
|
undefined
|
||||||
, lit "bar"
|
, lit "bar"
|
||||||
. match @Int 10
|
. match @Int 10
|
||||||
$ method HTTP.PATCH id \req -> do
|
. somePath
|
||||||
undefined
|
$ method HTTP.PATCH id undefined
|
||||||
, lit "baz"
|
, lit "baz"
|
||||||
$ choice
|
$ choice
|
||||||
[ method HTTP.GET id \req -> do
|
[ method HTTP.GET id \req -> do
|
||||||
@ -408,11 +401,73 @@ test2 =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
where
|
||||||
|
somePath =
|
||||||
|
lit "lol"
|
||||||
|
. lit "foo"
|
||||||
|
. myResponders
|
||||||
|
. param @Int
|
||||||
|
|
||||||
myResponders =
|
myResponders =
|
||||||
responder @200 @'["HELLO-HEADER"] @Text.Text @HelloWorldBody
|
responder @200 @'["HELLO-HEADER"] @Text.Text @HelloWorldBody
|
||||||
. responder @204 @'["BYE-HEADER"] @Text.Text @ByeWorldBody
|
. responder @204 @'["BYE-HEADER"] @Text.Text @ByeWorldBody
|
||||||
|
|
||||||
|
myOtherResponders =
|
||||||
|
responder @200 @'["HELLO-HEADER"] @Text.Text @HelloWorldBody
|
||||||
|
. responder @500 @'["BYE-HEADER"] @Text.Text @ByeWorldBody
|
||||||
|
. responder @404 @'["WAT-HEADER"] @Text.Text @HelloWorldBody
|
||||||
|
|
||||||
|
helloWorldApp =
|
||||||
|
helloWorld `withDefault` \_ resp ->
|
||||||
|
resp $ Wai.responseLBS HTTP.status404 [] "Not Found..."
|
||||||
|
|
||||||
|
helloWorld = (lit "hello" . lit "world" . responder @200 @'[] @Text.Text @Text.Text . method HTTP.GET id) \greeting req -> undefined
|
||||||
|
|
||||||
|
helloWorldApp' =
|
||||||
|
helloWorld' `withDefault` \_ resp ->
|
||||||
|
resp $ Wai.responseLBS HTTP.status404 [] "Not Found..."
|
||||||
|
|
||||||
|
helloWorld' =
|
||||||
|
(responder @200 @'[] @Text.Text @Text.Text . method HTTP.GET id) \greet _req -> do
|
||||||
|
return $ greet Response.NoHeaders "Hello World!"
|
||||||
|
|
||||||
|
helloWorldApp'' =
|
||||||
|
helloWorld'' `withDefault` \_ resp ->
|
||||||
|
resp $ Wai.responseLBS HTTP.status404 [] "Not Found..."
|
||||||
|
|
||||||
|
helloWorldPath'' =
|
||||||
|
lit "hello"
|
||||||
|
. lit "world"
|
||||||
|
. responder @200 @'[] @Text.Text @Text.Text
|
||||||
|
. method HTTP.GET id
|
||||||
|
|
||||||
|
helloWorld'' = helloWorldPath'' \greeting _req -> do
|
||||||
|
return $ greeting Response.NoHeaders "Hello World!"
|
||||||
|
|
||||||
|
worldApp =
|
||||||
|
world `withDefault` \_ resp ->
|
||||||
|
resp $ Wai.responseLBS HTTP.status404 [] "Not Found..."
|
||||||
|
|
||||||
|
world =
|
||||||
|
choice
|
||||||
|
[ helloWorldPathz \hi _req -> do
|
||||||
|
return $ hi Response.NoHeaders "Hello World!"
|
||||||
|
, byeWorldPathz \bye _req -> do
|
||||||
|
return $ bye Response.NoHeaders "Bye World..."
|
||||||
|
]
|
||||||
|
|
||||||
|
helloWorldPathz =
|
||||||
|
lit "hello"
|
||||||
|
. lit "world"
|
||||||
|
. responder @200 @'[] @Text.Text @Text.Text
|
||||||
|
. method HTTP.GET id
|
||||||
|
|
||||||
|
byeWorldPathz =
|
||||||
|
lit "bye"
|
||||||
|
. lit "world"
|
||||||
|
. responder @500 @'[] @Text.Text @Text.Text
|
||||||
|
. method HTTP.GET id
|
||||||
|
|
||||||
testHandler1 :: Bool -> Wai.Request -> IO Wai.Response
|
testHandler1 :: Bool -> Wai.Request -> IO Wai.Response
|
||||||
testHandler1 x request = do
|
testHandler1 x request = do
|
||||||
return $ Wai.responseLBS HTTP.status200 [] $ LBSChar8.pack $ show x
|
return $ Wai.responseLBS HTTP.status200 [] $ LBSChar8.pack $ show x
|
||||||
@ -444,13 +499,98 @@ myFunc = fillHandler handlerTest argsTest
|
|||||||
argsTest = HCons True (HCons 5 (HCons 5.8 HNil))
|
argsTest = HCons True (HCons 5 (HCons 5.8 HNil))
|
||||||
|
|
||||||
withDefault :: Node '[] -> Wai.Middleware
|
withDefault :: Node '[] -> Wai.Middleware
|
||||||
withDefault = undefined
|
withDefault = withDefaultLoop id HNil
|
||||||
|
|
||||||
-- withDefaultLoop id HNil
|
|
||||||
|
|
||||||
withDefaultLoop :: Wai.Middleware -> HList args -> Node args -> Wai.Middleware
|
withDefaultLoop :: Wai.Middleware -> HList args -> Node args -> Wai.Middleware
|
||||||
withDefaultLoop = undefined
|
withDefaultLoop middleware args root backup request respond = case root of
|
||||||
|
Choice [] -> backup request respond
|
||||||
|
Choice (node : nodes) -> case node of
|
||||||
|
Choice subNodes -> withDefaultLoop middleware args (choice (subNodes <> nodes)) backup request respond
|
||||||
|
Match value subNode ->
|
||||||
|
case Wai.pathInfo request of
|
||||||
|
[] -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
(pathHead : pathTail) ->
|
||||||
|
if pathHead == Web.toUrlPiece value
|
||||||
|
then do
|
||||||
|
let newRequest = request{Wai.pathInfo = pathTail}
|
||||||
|
withDefaultLoop middleware args subNode backup newRequest respond
|
||||||
|
else withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Param @p subNode ->
|
||||||
|
case Wai.pathInfo request of
|
||||||
|
[] -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
(pathHead : pathTail) ->
|
||||||
|
case Web.parseUrlPiece @p pathHead of
|
||||||
|
Left _ -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Right value -> do
|
||||||
|
let newRequest = request{Wai.pathInfo = pathTail}
|
||||||
|
withDefaultLoop middleware (snoc args value) subNode backup newRequest respond
|
||||||
|
Responder @s @hk @ct @rt @r subNode ->
|
||||||
|
let callback = Response.makeResponder @s @hk @ct @rt
|
||||||
|
in withDefaultLoop
|
||||||
|
middleware
|
||||||
|
(snoc args callback)
|
||||||
|
(choice [subNode])
|
||||||
|
(withDefaultLoop middleware args (choice nodes) backup)
|
||||||
|
request
|
||||||
|
respond
|
||||||
|
Method stdMethod transformation handler ->
|
||||||
|
case HTTP.parseMethod $ Wai.requestMethod request of
|
||||||
|
Left _ -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Right stdMethod' ->
|
||||||
|
if stdMethod == stdMethod' && List.null (Wai.pathInfo request)
|
||||||
|
then
|
||||||
|
middleware
|
||||||
|
( \request' respond' -> do
|
||||||
|
response <- transformation $ fillHandler handler args request'
|
||||||
|
respond' response
|
||||||
|
)
|
||||||
|
request
|
||||||
|
respond
|
||||||
|
else withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
root' -> withDefaultLoop middleware args (choice [root']) backup request respond
|
||||||
|
{-
|
||||||
|
Match value subNode ->
|
||||||
|
case Wai.pathInfo request of
|
||||||
|
[] -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
(pathHead : pathTail) ->
|
||||||
|
if pathHead == Web.toUrlPiece value
|
||||||
|
then do
|
||||||
|
let newRequest = request{Wai.pathInfo = pathTail}
|
||||||
|
withDefaultLoop middleware args subNode backup newRequest respond
|
||||||
|
else withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Param @p subNode ->
|
||||||
|
case Wai.pathInfo request of
|
||||||
|
[] -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
(pathHead : pathTail) ->
|
||||||
|
case Web.parseUrlPiece @p pathHead of
|
||||||
|
Left _ -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Right value -> do
|
||||||
|
let newRequest = request{Wai.pathInfo = pathTail}
|
||||||
|
withDefaultLoop middleware (snoc args value) subNode backup newRequest respond
|
||||||
|
Responder @s @hk @ct @rt @r subNode ->
|
||||||
|
let callback = Response.makeResponder @s @hk @ct @rt
|
||||||
|
in withDefaultLoop
|
||||||
|
middleware
|
||||||
|
(snoc args callback)
|
||||||
|
(choice [subNode])
|
||||||
|
(withDefaultLoop middleware args (choice nodes) backup)
|
||||||
|
request
|
||||||
|
respond
|
||||||
|
Method stdMethod transformation handler ->
|
||||||
|
case HTTP.parseMethod $ Wai.requestMethod request of
|
||||||
|
Left _ -> withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
Right stdMethod' ->
|
||||||
|
if stdMethod == stdMethod' && List.null (Wai.pathInfo request)
|
||||||
|
then
|
||||||
|
middleware
|
||||||
|
( \request' respond' -> do
|
||||||
|
response <- transformation $ fillHandler handler args request'
|
||||||
|
respond' response
|
||||||
|
)
|
||||||
|
request
|
||||||
|
respond
|
||||||
|
else withDefaultLoop middleware args (choice nodes) backup request respond
|
||||||
|
-}
|
||||||
{-
|
{-
|
||||||
withDefaultLoop middleware args tree backup request respond = case tree of
|
withDefaultLoop middleware args tree backup request respond = case tree of
|
||||||
[] -> backup request respond
|
[] -> backup request respond
|
||||||
|
@ -27,11 +27,14 @@ module Okapi.Response where
|
|||||||
import Control.Natural qualified as Natural
|
import Control.Natural qualified as Natural
|
||||||
import Data.Binary.Builder qualified as Builder
|
import Data.Binary.Builder qualified as Builder
|
||||||
import Data.ByteString.Lazy qualified as LBS
|
import Data.ByteString.Lazy qualified as LBS
|
||||||
|
import Data.ByteString.Lazy.Char8 qualified as LBSChar8
|
||||||
import Data.Functor.Identity qualified as Identity
|
import Data.Functor.Identity qualified as Identity
|
||||||
import Data.Kind
|
import Data.Kind
|
||||||
import Data.List qualified as List
|
import Data.List qualified as List
|
||||||
import Data.List.NonEmpty qualified as NonEmpty
|
import Data.List.NonEmpty qualified as NonEmpty
|
||||||
import Data.Text qualified as Text
|
import Data.Text qualified as Text
|
||||||
|
import Data.Text.Lazy qualified as LText
|
||||||
|
import Data.Text.Lazy.Encoding qualified as Text
|
||||||
import Data.Tree qualified as Tree
|
import Data.Tree qualified as Tree
|
||||||
import Data.Type.Equality qualified as Equality
|
import Data.Type.Equality qualified as Equality
|
||||||
import Data.Typeable qualified as Typeable
|
import Data.Typeable qualified as Typeable
|
||||||
@ -39,6 +42,7 @@ import Data.Vault.Lazy qualified as Vault
|
|||||||
import GHC.Exts qualified as Exts
|
import GHC.Exts qualified as Exts
|
||||||
import GHC.Generics qualified as Generics
|
import GHC.Generics qualified as Generics
|
||||||
import GHC.Natural qualified as Natural
|
import GHC.Natural qualified as Natural
|
||||||
|
import GHC.TypeNats qualified as Nat
|
||||||
import Network.HTTP.Types qualified as HTTP
|
import Network.HTTP.Types qualified as HTTP
|
||||||
import Network.Wai qualified as Wai
|
import Network.Wai qualified as Wai
|
||||||
import Okapi.Headers qualified as Headers
|
import Okapi.Headers qualified as Headers
|
||||||
@ -49,6 +53,9 @@ import Web.HttpApiData qualified as Web
|
|||||||
class ToHeader a where
|
class ToHeader a where
|
||||||
toHeader :: a -> LBS.ByteString
|
toHeader :: a -> LBS.ByteString
|
||||||
|
|
||||||
|
instance ToHeader LBS.ByteString where
|
||||||
|
toHeader = id
|
||||||
|
|
||||||
type Elem :: Exts.Symbol -> [Exts.Symbol] -> Bool
|
type Elem :: Exts.Symbol -> [Exts.Symbol] -> Bool
|
||||||
type family Elem x ys where
|
type family Elem x ys where
|
||||||
Elem x '[] = 'False
|
Elem x '[] = 'False
|
||||||
@ -64,6 +71,9 @@ data Headers (headerKeys :: [Exts.Symbol]) where
|
|||||||
Headers headerKeys ->
|
Headers headerKeys ->
|
||||||
Headers (headerKey : headerKeys)
|
Headers (headerKey : headerKeys)
|
||||||
|
|
||||||
|
noHeaders :: Headers '[]
|
||||||
|
noHeaders = NoHeaders
|
||||||
|
|
||||||
insertHeader ::
|
insertHeader ::
|
||||||
forall (headerKey :: Exts.Symbol) headerValue (headerKeys :: [Exts.Symbol]).
|
forall (headerKey :: Exts.Symbol) headerValue (headerKeys :: [Exts.Symbol]).
|
||||||
(ToHeader headerValue) =>
|
(ToHeader headerValue) =>
|
||||||
@ -111,23 +121,51 @@ data Body
|
|||||||
= BodyStream Wai.StreamingBody
|
= BodyStream Wai.StreamingBody
|
||||||
| BodyBuilder Builder.Builder
|
| BodyBuilder Builder.Builder
|
||||||
| BodyBytes LBS.ByteString
|
| BodyBytes LBS.ByteString
|
||||||
| BodyFile FilePath Wai.FilePart
|
| BodyFile FilePath (Maybe Wai.FilePart)
|
||||||
|
|
||||||
class ContentType a where
|
class ContentType a where
|
||||||
contentTypeName :: LBS.ByteString
|
contentTypeName :: LBS.ByteString
|
||||||
contentTypeBody :: a -> Body
|
contentTypeBody :: a -> Body
|
||||||
|
|
||||||
|
instance ContentType Text.Text where
|
||||||
|
contentTypeName = "text/plain"
|
||||||
|
contentTypeBody = BodyBytes . Text.encodeUtf8 . LText.fromStrict
|
||||||
|
|
||||||
class (ContentType a) => ToContentType a b | b -> a where
|
class (ContentType a) => ToContentType a b | b -> a where
|
||||||
toContentType :: b -> a
|
toContentType :: b -> a
|
||||||
|
|
||||||
|
instance ToContentType Text.Text Text.Text where
|
||||||
|
toContentType = id
|
||||||
|
|
||||||
|
instance ToContentType Text.Text Int where
|
||||||
|
toContentType = Text.pack . show
|
||||||
|
|
||||||
data Response where
|
data Response where
|
||||||
Response ::
|
Response ::
|
||||||
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type).
|
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type).
|
||||||
(ContentType contentType, ToContentType contentType resultType, Typeable.Typeable headerKeys, Typeable.Typeable resultType) =>
|
(ContentType contentType, ToContentType contentType resultType, Typeable.Typeable headerKeys, Typeable.Typeable resultType) =>
|
||||||
Response
|
Response
|
||||||
|
|
||||||
|
toWaiResponseHeaders ::
|
||||||
|
forall (headerKeys :: [Exts.Symbol]).
|
||||||
|
Headers headerKeys ->
|
||||||
|
HTTP.ResponseHeaders
|
||||||
|
toWaiResponseHeaders headers = []
|
||||||
|
|
||||||
|
natToStatus :: Nat.Nat -> HTTP.Status
|
||||||
|
natToStatus n = toEnum $ fromEnum n
|
||||||
|
|
||||||
makeResponder ::
|
makeResponder ::
|
||||||
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type).
|
forall (status :: Natural.Natural) (headerKeys :: [Exts.Symbol]) (contentType :: Type) (resultType :: Type).
|
||||||
(ContentType contentType, ToContentType contentType resultType, Typeable.Typeable headerKeys, Typeable.Typeable resultType) =>
|
(Nat.KnownNat status, ContentType contentType, ToContentType contentType resultType, Typeable.Typeable headerKeys, Typeable.Typeable resultType) =>
|
||||||
(Headers headerKeys -> resultType -> Wai.Response)
|
(Headers headerKeys -> resultType -> Wai.Response)
|
||||||
makeResponder = undefined
|
makeResponder = \_ result ->
|
||||||
|
let status = natToStatus $ Nat.natVal @status Typeable.Proxy
|
||||||
|
contentType = toContentType result
|
||||||
|
bodyType = contentTypeBody contentType
|
||||||
|
name = contentTypeName @contentType
|
||||||
|
in case bodyType of
|
||||||
|
BodyBytes bytes -> Wai.responseLBS status [] bytes
|
||||||
|
BodyBuilder builder -> Wai.responseBuilder status [] builder
|
||||||
|
BodyStream stream -> Wai.responseStream status [] stream
|
||||||
|
BodyFile path part -> Wai.responseFile status [] path part
|
||||||
|
Loading…
Reference in New Issue
Block a user