@@ -1,410 +0,0 @@
1−module PureStack.Server
11− , internalServerError
13− -------------------------------------------------------------------------------------------------
21− , class ParsePathPiece
30−import PureStack.Route
32−import Bun.Request (Request)
33−import Bun.Request as Bun
34−import Bun.Request as Request
35−import Bun.Response as Bun.Response
36−import Control.Alternative (class Plus, empty)
37−import Control.Monad.Except (ExceptT, runExceptT)
38−import Data.Argonaut (class DecodeJson, class EncodeJson, Json, decodeJson, encodeJson)
39−import Data.Array as Array
40−import Data.Either (Either(..), either)
41−import Data.Int as Int
43−import Data.Map as Map
44−import Data.Maybe (Maybe(..))
45−import Data.Number as Number
46−import Data.String as String
47−import Data.Symbol (class IsSymbol, reflectSymbol)
48−import Data.Traversable (traverse)
49−import Data.URL as URL
50−import Effect.Aff (Aff)
51−import Effect.Aff.Class (liftAff)
52−import Foreign.Object (Object)
53−import Foreign.Object as Object
54−import Prim.Row as Row
55−import Prim.RowList (class RowToList, RowList)
56−import Prim.RowList as RowList
57−import PureStack.Cookie (Cookie)
58−import PureStack.Cookie as Cookie
59−import Record as Record
60−import Record.Builder (Builder)
61−import Record.Builder as Builder
62−import Type.Proxy (Proxy(..))
65− { path :: Array String
67− , query :: Map String (Array String)
68− , originalRequest :: Bun.Request
71−routeFromRequest :: Request -> Route
72−routeFromRequest req =
74− url = URL.fromString $ Request.url req
76− { verb: Request.method req
79− Just u -> case URL.path u of
81− URL.PathAbsolute p -> p
82− URL.PathRelative p -> p
85− Just u -> URL.query u
86− , originalRequest: req
89−type Server a = ExceptT Response Aff a
91−newtype Nt m = Nt (forall x. m x -> Server x)
93−class ServeRoute :: forall k. k -> Type -> Row Type -> (Type -> Type) -> Constraint
94−class ServeRoute route handler middlewares m | route middlewares -> handler, handler -> m where
95− serveRoute :: Route -> Maybe (Nt m -> handler -> Record middlewares -> Request -> Server Response)
97−instance (ServeRoute rest handler middlewares m, IsSymbol path) => ServeRoute (path / rest) handler middlewares m where
98− serveRoute route@{ path } = do
99− { head: h, tail } <- Array.uncons path
100− assert $ h == (reflectSymbol @path Proxy)
101− serveRoute @rest route { path = tail }
104− ( ServeRoute rest handler middlewares m
105− , ServeQuery row list
106− , RowToList row list
108− ServeRoute (Record row / rest) (Record row -> handler) middlewares m where
109− serveRoute route@{ query } = do
110− builder <- serveQuery @row @list query
111− serveRoute @rest route <#> (\f nt handler req -> f nt (handler $ Builder.buildFromScratch builder) req)
114− ( ServeRoute rest handler middlewares m
116− , Row.Cons field (Bun.Request -> m x) tail middlewares
118− ServeRoute (Middleware field / rest) (x -> handler) middlewares m where
119− serveRoute route@{ originalRequest } = do
120− serveRoute @rest route <#>
121− ( \f nt@(Nt ntf) handler middlewares req -> do
122− x <- ntf $ Record.get (Proxy @field) middlewares originalRequest
123− f nt (handler x) middlewares req
126−else instance (ServeRoute rest handler middlewares m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) middlewares m where
127− serveRoute route@{ path } = do
128− { head: h, tail } <- Array.uncons path
129− t <- parsePathPiece @t h
130− serveRoute @rest route { path = tail } <#> (\f nt handler mws req -> f nt (handler t) mws req)
132−else instance MethodName (meth Unit Unit) => ServeRoute (meth Unit Unit) (m Unit) middlewares m where
133− serveRoute { path, verb } = do
134− assert $ verb == methodName @(meth Unit Unit)
136− pure $ \(Nt nt) handler _middlewares _req -> do
139−else instance (ToResponse resp, MethodName (meth Unit resp)) => ServeRoute (meth Unit resp) (m resp) middlewares m where
140− serveRoute { path, verb } = do
141− assert $ verb == methodName @(meth Unit resp)
143− pure $ \(Nt nt) handler _middlewares _req -> do
145− pure $ toResponse resp
146−else instance (FromRequest req, MethodName (meth resp Unit)) => ServeRoute (meth resp Unit) (req -> m Unit) middlewares m where
147− serveRoute { path, verb } = do
148− assert $ verb == methodName @(meth resp Unit)
150− pure $ \(Nt nt) handler _middlewares req -> do
151− liftAff (fromRequest req) >>= case _ of
152− Nothing -> pure $ badRequest
156−else instance (FromRequest req, ToResponse resp, MethodName (meth req resp)) => ServeRoute (meth req resp) (req -> m resp) middlewares m where
157− serveRoute { path, verb } = do
158− assert $ verb == methodName @(meth req resp)
160− pure $ \(Nt nt) handler _middlewares req -> do
161− liftAff (fromRequest req) >>= case _ of
162− Nothing -> pure $ badRequest
164− resp <- nt $ handler r
165− pure $ toResponse resp
168− ( ServeAPI routes list handlers middlewares m
169− , RowToList routes list
171− ServeRoute routes (Record handlers) middlewares m where
172− serveRoute route = pure $ \nt handlers middlewares req ->
173− serveAPI @routes @list nt handlers middlewares route req
175−class ServeQuery :: Row Type -> RowList Type -> Constraint
176−class ServeQuery row list | list -> row where
177− serveQuery :: Map String (Array String) -> Maybe (Builder (Record ()) (Record row))
179−instance ServeQuery () RowList.Nil where
180− serveQuery _ = Just $ identity
184− , ServeQuery tail rest
186− , Row.Cons field (Array t) tail row
187− , Row.Lacks field tail
189− ServeQuery row (RowList.Cons field (Array t) rest) where
191− r <- serveQuery @tail @rest m
192− case Map.lookup (reflectSymbol @field Proxy) m of
193− Nothing -> pure $ Builder.insert (Proxy @field) [] <<< r
195− v <- parsePathPiece @t `traverse` a
196− pure $ Builder.insert (Proxy @field) v <<< r
200− , ServeQuery tail rest
202− , Row.Cons field (Maybe t) tail row
203− , Row.Lacks field tail
205− ServeQuery row (RowList.Cons field (Maybe t) rest) where
207− r <- serveQuery @tail @rest m
208− case Map.lookup (reflectSymbol @field Proxy) m of
209− Nothing -> pure $ Builder.insert (Proxy @field) Nothing <<< r
211− { head, tail } <- Array.uncons a
213− v <- parsePathPiece @t head
214− pure $ Builder.insert (Proxy @field) (Just v) <<< r
218− , ServeQuery tail rest
220− , Row.Cons field t tail row
221− , Row.Lacks field tail
223− ServeQuery row (RowList.Cons field t rest) where
225− { head, tail } <- Map.lookup (reflectSymbol @field Proxy) m >>= Array.uncons
227− v <- parsePathPiece @t head
228− r <- serveQuery @tail @rest m
229− pure $ Builder.insert (Proxy @field) v <<< r
232− = JsonResponseBody Json
234− | StringResponseBody String
236−newtype Response = Response { body :: ResponseBody, status :: Int, statusText :: String, headers :: Object String }
238−class ToResponse r where
239− toResponse :: r -> Response
241−instance EncodeJson (Record row) => ToResponse (Record row) where
242− toResponse rec = Response { body: JsonResponseBody $ encodeJson rec, status: 200, statusText: "OK", headers: Object.empty }
244−instance ToResponse Response where
245− toResponse resp = resp
247−class FromRequest r where
248− fromRequest :: Request -> Aff (Maybe r)
250−instance DecodeJson (Record row) => FromRequest (Record row) where
251− fromRequest req = Request.json req <#> \j -> case decodeJson j of
255−instance FromRequest Request where
256− fromRequest req = pure $ Just req
258−instance FromRequest Unit where
259− fromRequest _ = pure $ Just unit
265− FromRequest (Headers (Object header) req) where
266− fromRequest req = do
267− r <- fromRequest req
269− headers <- traverse fromHeader (Request.headers req)
271− pure $ Headers headers r'
274− ( RowToList headers headersList
276− , FromHeaders headers headersList
278− FromRequest (Headers (Record headers) req) where
279− fromRequest req = do
280− r <- fromRequest req
282− headers <- fromHeaders @headers @headersList (Request.headers req)
284− pure $ Headers (Builder.buildFromScratch headers) r'
286−class FromHeader t where
287− fromHeader :: String -> Maybe t
289−instance FromHeader Cookie where
290− fromHeader = Cookie.parse >>> case _ of
294−instance FromHeader String where
297−instance FromHeader Int where
298− fromHeader = Int.fromString
300−class FromHeaders :: Row Type -> RowList Type -> Constraint
301−class FromHeaders row list | list -> row where
302− fromHeaders :: Object String -> Maybe (Builder {} (Record row))
304−instance FromHeaders () RowList.Nil where
305− fromHeaders _ = Just $ identity
310− , FromHeaders tail rest
311− , Row.Cons field (Maybe t) tail row
312− , Row.Lacks field tail
314− FromHeaders row (RowList.Cons field (Maybe t) rest) where
315− fromHeaders headers = do
316− r <- fromHeaders @tail @rest headers
317− case Object.lookup (reflectSymbol @field Proxy) headers of
318− Nothing -> pure $ Builder.insert (Proxy @field) Nothing <<< r
321− pure $ Builder.insert (Proxy @field) (Just v) <<< r
326− , FromHeaders tail rest
327− , Row.Cons field t tail row
328− , Row.Lacks field tail
330− FromHeaders row (RowList.Cons field t rest) where
331− fromHeaders headers = do
332− t <- Object.lookup (reflectSymbol @field Proxy) headers >>= fromHeader
333− r <- fromHeaders @tail @rest headers
334− pure $ Builder.insert (Proxy @field) t <<< r
336−class ParsePathPiece t where
337− parsePathPiece :: String -> Maybe t
339−instance ParsePathPiece String where
340− parsePathPiece = Just
342−instance ParsePathPiece Int where
343− parsePathPiece = Int.fromString
345−instance ParsePathPiece Number where
346− parsePathPiece = Number.fromString
348−instance ParsePathPiece Boolean where
349− parsePathPiece s = case String.toLower s of
352− "false" -> Just false
356−assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
357−assert true = pure unit
360−class ServeAPI :: Row Type -> RowList Type -> Row Type -> Row Type -> (Type -> Type) -> Constraint
361−class ServeAPI row list handlers middlewares m | row -> handlers, handlers -> m where
362− serveAPI :: Nt m -> Record handlers -> Record middlewares -> Route -> Request -> Server Response
364−instance ServeAPI row RowList.Nil () middlewares m where
365− serveAPI _ _ _ _ _ = pure notFound
368− ( ServeRoute t handler middlewares m
369− , Row.Cons field t tail row
370− , ServeAPI row rest restHandlers middlewares m
372− , Row.Cons field handler restHandlers handlers
373− , Row.Lacks field restHandlers
375− ServeAPI row (RowList.Cons field t rest) handlers middlewares m where
376− serveAPI nt handlers middlewares route req =
377− case serveRoute @t route of
378− Nothing -> serveAPI @row @rest @restHandlers @middlewares nt (Record.delete (Proxy @field) handlers) middlewares route req
379− Just f -> f nt (Record.get (Proxy @field) handlers) middlewares req
382−notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
384−internalServerError :: Response
385−internalServerError = Response { body: EmptyResponseBody, status: 500, statusText: "Internal Server Error", headers: Object.empty }
387−badRequest :: Response
388−badRequest = Response { body: EmptyResponseBody, status: 400, statusText: "Bad Request", headers: Object.empty }
391−ok = Response { body: EmptyResponseBody, status: 200, statusText: "OK", headers: Object.empty }
393−-- | The first argument is used to run an arbitrary monad in handlers. To use the 'Server' monad
394−-- | instead you can just pass `identity` as the first argument.
396− :: forall @row list handlers m middlewares
397− . RowToList row list
398− => ServeAPI row list handlers middlewares m
399− => (forall x. m x -> Server x)
400− -> { handlers :: Record handlers, middlewares :: Record middlewares }
402− -> Aff Bun.Response.Response
403−run nt { handlers, middlewares } req = do
404− Response { body, headers, status, statusText } <-
405− map (either identity identity) $ runExceptT $ serveAPI @row @list @handlers (Nt nt) handlers middlewares (routeFromRequest req) req
406− let opts = { headers, status, statusText }
408− EmptyResponseBody -> Bun.Response.string "" opts
409− JsonResponseBody json -> Bun.Response.json json opts
410− StringResponseBody string -> Bun.Response.string string opts