@@ -20,12 +20,17 @@ module PureStack.Server
20 , serveQuery
20 , serveQuery
21 , class ParsePathPiece
21 , class ParsePathPiece
22 , parsePathPiece
22 , parsePathPiece
25import Prelude
29import Prelude
26import PureStack.Route
30import PureStack.Route
28import Bun.Request (Request)
32import Bun.Request (Request)
33+import Bun.Request as Bun
29import Bun.Request as Request
34import Bun.Request as Request
30import Bun.Response as Bun.Response
35import Bun.Response as Bun.Response
31import Control.Alternative (class Plus, empty)
36import Control.Alternative (class Plus, empty)
@@ -49,12 +54,19 @@ import Foreign.Object as Object
49import Prim.Row as Row
54import Prim.Row as Row
50import Prim.RowList (class RowToList, RowList)
55import Prim.RowList (class RowToList, RowList)
51import Prim.RowList as RowList
56import Prim.RowList as RowList
57+import PureStack.Cookie (Cookie)
58+import PureStack.Cookie as Cookie
52import Record as Record
59import Record as Record
53import Record.Builder (Builder)
60import Record.Builder (Builder)
54import Record.Builder as Builder
61import Record.Builder as Builder
55import Type.Proxy (Proxy(..))
62import Type.Proxy (Proxy(..))
65+ { path :: Array String
57−type Route = { path :: Array String, verb :: String, query :: Map String (Array String) }
67+ , query :: Map String (Array String)
68+ , originalRequest :: Bun.Request
59routeFromRequest :: Request -> Route
71routeFromRequest :: Request -> Route
60routeFromRequest req =
72routeFromRequest req =
@@ -71,180 +83,81 @@ routeFromRequest req =
71 , query: case url of
83 , query: case url of
72 Nothing -> Map.empty
84 Nothing -> Map.empty
73 Just u -> URL.query u
85 Just u -> URL.query u
86+ , originalRequest: req
76type Server a = ExceptT Response Aff a
89type Server a = ExceptT Response Aff a
78newtype Nt m = Nt (forall x. m x -> Server x)
91newtype Nt m = Nt (forall x. m x -> Server x)
80−class ServeRoute :: forall k. k -> Type -> (Type -> Type) -> Constraint
93+class ServeRoute :: forall k. k -> Type -> Row Type -> (Type -> Type) -> Constraint
81−class ServeRoute route handler m | route -> handler, handler -> m where
94+class ServeRoute route handler middlewares m | route middlewares -> handler, handler -> m where
82− serveRoute :: Route -> Maybe (Nt m -> handler -> Request -> Server Response)
95+ serveRoute :: Route -> Maybe (Nt m -> handler -> Record middlewares -> Request -> Server Response)
84−instance (ServeRoute rest handler m, IsSymbol path) => ServeRoute (path / rest) handler m where
97+instance (ServeRoute rest handler middlewares m, IsSymbol path) => ServeRoute (path / rest) handler middlewares m where
85 serveRoute route@{ path } = do
98 serveRoute route@{ path } = do
86 { head: h, tail } <- Array.uncons path
99 { head: h, tail } <- Array.uncons path
87 assert $ h == (reflectSymbol @path Proxy)
100 assert $ h == (reflectSymbol @path Proxy)
88 serveRoute @rest route { path = tail }
101 serveRoute @rest route { path = tail }
90else instance
103else instance
91− ( ServeRoute rest handler m
104+ ( ServeRoute rest handler middlewares m
92 , ServeQuery row list
105 , ServeQuery row list
93 , RowToList row list
106 , RowToList row list
95− ServeRoute (Record row / rest) (Record row -> handler) m where
108+ ServeRoute (Record row / rest) (Record row -> handler) middlewares m where
96 serveRoute route@{ query } = do
109 serveRoute route@{ query } = do
97 builder <- serveQuery @row @list query
110 builder <- serveQuery @row @list query
98 serveRoute @rest route <#> (\f nt handler req -> f nt (handler $ Builder.buildFromScratch builder) req)
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
100−else instance (ServeRoute rest handler m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) m where
126+else instance (ServeRoute rest handler middlewares m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) middlewares m where
101 serveRoute route@{ path } = do
127 serveRoute route@{ path } = do
102 { head: h, tail } <- Array.uncons path
128 { head: h, tail } <- Array.uncons path
103 t <- parsePathPiece @t h
129 t <- parsePathPiece @t h
104− serveRoute @rest route { path = tail } <#> (\f nt handler req -> f nt (handler t) req)
130+ serveRoute @rest route { path = tail } <#> (\f nt handler mws req -> f nt (handler t) mws req)
106−else instance ToResponse resp => ServeRoute (GET resp) (m resp) m where
107− serveRoute { path, verb } = do
108− assert $ verb == "GET"
110− pure $ \(Nt nt) handler _req -> do
112− pure $ toResponse resp
114−else instance ServeRoute (POST Unit Unit) (m Unit) m where
115− serveRoute { path, verb } = do
116− assert $ verb == "POST"
118− pure $ \(Nt nt) handler _req -> do
121−else instance (ToResponse resp) => ServeRoute (POST Unit resp) (m resp) m where
122− serveRoute { path, verb } = do
123− assert $ verb == "POST"
125− pure $ \(Nt nt) handler _req -> do
127− pure $ toResponse resp
128−else instance (FromRequest req) => ServeRoute (POST resp Unit) (req -> m Unit) m where
129− serveRoute { path, verb } = do
130− assert $ verb == "POST"
132− pure $ \(Nt nt) handler req -> do
133− liftAff (fromRequest req) >>= case _ of
134− Nothing -> pure $ badRequest
138−else instance (FromRequest req, ToResponse resp) => ServeRoute (POST req resp) (req -> m resp) m where
139− serveRoute { path, verb } = do
140− assert $ verb == "POST"
142− pure $ \(Nt nt) handler req -> do
143− liftAff (fromRequest req) >>= case _ of
144− Nothing -> pure $ badRequest
146− resp <- nt $ handler r
147− pure $ toResponse resp
149−else instance ServeRoute (DELETE Unit Unit) (m Unit) m where
132+else instance MethodName (meth Unit Unit) => ServeRoute (meth Unit Unit) (m Unit) middlewares m where
150 serveRoute { path, verb } = do
133 serveRoute { path, verb } = do
151− assert $ verb == "DELETE"
134+ assert $ verb == methodName @(meth Unit Unit)
152 assert $ path == []
135 assert $ path == []
153− pure $ \(Nt nt) handler _req -> do
136+ pure $ \(Nt nt) handler _middlewares _req -> do
154 nt handler
137 nt handler
155 pure $ ok
138 pure $ ok
156−else instance (ToResponse resp) => ServeRoute (DELETE Unit resp) (m resp) m where
139+else instance (ToResponse resp, MethodName (meth Unit resp)) => ServeRoute (meth Unit resp) (m resp) middlewares m where
157 serveRoute { path, verb } = do
140 serveRoute { path, verb } = do
158− assert $ verb == "DELETE"
141+ assert $ verb == methodName @(meth Unit resp)
159 assert $ path == []
142 assert $ path == []
160− pure $ \(Nt nt) handler _req -> do
143+ pure $ \(Nt nt) handler _middlewares _req -> do
161 resp <- nt handler
144 resp <- nt handler
162 pure $ toResponse resp
145 pure $ toResponse resp
163−else instance (FromRequest req) => ServeRoute (DELETE resp Unit) (req -> m Unit) m where
146+else instance (FromRequest req, MethodName (meth resp Unit)) => ServeRoute (meth resp Unit) (req -> m Unit) middlewares m where
164 serveRoute { path, verb } = do
147 serveRoute { path, verb } = do
165− assert $ verb == "DELETE"
148+ assert $ verb == methodName @(meth resp Unit)
166 assert $ path == []
149 assert $ path == []
167− pure $ \(Nt nt) handler req -> do
150+ pure $ \(Nt nt) handler _middlewares req -> do
168 liftAff (fromRequest req) >>= case _ of
151 liftAff (fromRequest req) >>= case _ of
169 Nothing -> pure $ badRequest
152 Nothing -> pure $ badRequest
170 Just r -> do
153 Just r -> do
171 nt $ handler r
154 nt $ handler r
172 pure $ ok
155 pure $ ok
173−else instance (FromRequest req, ToResponse resp) => ServeRoute (DELETE req resp) (req -> m resp) m where
156+else instance (FromRequest req, ToResponse resp, MethodName (meth req resp)) => ServeRoute (meth req resp) (req -> m resp) middlewares m where
174 serveRoute { path, verb } = do
157 serveRoute { path, verb } = do
175− assert $ verb == "DELETE"
158+ assert $ verb == methodName @(meth req resp)
176 assert $ path == []
159 assert $ path == []
177− pure $ \(Nt nt) handler req -> do
160+ pure $ \(Nt nt) handler _middlewares req -> do
178− liftAff (fromRequest req) >>= case _ of
179− Nothing -> pure $ badRequest
181− resp <- nt $ handler r
182− pure $ toResponse resp
184−else instance ServeRoute (PUT Unit Unit) (m Unit) m where
185− serveRoute { path, verb } = do
186− assert $ verb == "PUT"
188− pure $ \(Nt nt) handler _req -> do
191−else instance (ToResponse resp) => ServeRoute (PUT Unit resp) (m resp) m where
192− serveRoute { path, verb } = do
193− assert $ verb == "PUT"
195− pure $ \(Nt nt) handler _req -> do
197− pure $ toResponse resp
198−else instance (FromRequest req) => ServeRoute (PUT resp Unit) (req -> m Unit) m where
199− serveRoute { path, verb } = do
200− assert $ verb == "PUT"
202− pure $ \(Nt nt) handler req -> do
203− liftAff (fromRequest req) >>= case _ of
204− Nothing -> pure $ badRequest
208−else instance (FromRequest req, ToResponse resp) => ServeRoute (PUT req resp) (req -> m resp) m where
209− serveRoute { path, verb } = do
210− assert $ verb == "PUT"
212− pure $ \(Nt nt) handler req -> do
213− liftAff (fromRequest req) >>= case _ of
214− Nothing -> pure $ badRequest
216− resp <- nt $ handler r
217− pure $ toResponse resp
219−else instance ServeRoute (PATCH Unit Unit) (m Unit) m where
220− serveRoute { path, verb } = do
221− assert $ verb == "PATCH"
223− pure $ \(Nt nt) handler _req -> do
226−else instance (ToResponse resp) => ServeRoute (PATCH Unit resp) (m resp) m where
227− serveRoute { path, verb } = do
228− assert $ verb == "PATCH"
230− pure $ \(Nt nt) handler _req -> do
232− pure $ toResponse resp
233−else instance (FromRequest req) => ServeRoute (PATCH resp Unit) (req -> m Unit) m where
234− serveRoute { path, verb } = do
235− assert $ verb == "PATCH"
237− pure $ \(Nt nt) handler req -> do
238− liftAff (fromRequest req) >>= case _ of
239− Nothing -> pure $ badRequest
243−else instance (FromRequest req, ToResponse resp) => ServeRoute (PATCH req resp) (req -> m resp) m where
244− serveRoute { path, verb } = do
245− assert $ verb == "PATCH"
247− pure $ \(Nt nt) handler req -> do
248 liftAff (fromRequest req) >>= case _ of
161 liftAff (fromRequest req) >>= case _ of
249 Nothing -> pure $ badRequest
162 Nothing -> pure $ badRequest
250 Just r -> do
163 Just r -> do
@@ -252,12 +165,12 @@ else instance (FromRequest req, ToResponse resp) => ServeRoute (PATCH req resp)
252 pure $ toResponse resp
165 pure $ toResponse resp
254else instance
167else instance
255− ( ServeAPI routes list handlers m
168+ ( ServeAPI routes list handlers middlewares m
256 , RowToList routes list
169 , RowToList routes list
258− ServeRoute routes (Record handlers) m where
171+ ServeRoute routes (Record handlers) middlewares m where
259− serveRoute route = pure $ \nt handlers req ->
172+ serveRoute route = pure $ \nt handlers middlewares req ->
260− serveAPI @routes @list nt handlers route req
173+ serveAPI @routes @list nt handlers middlewares route req
262class ServeQuery :: Row Type -> RowList Type -> Constraint
175class ServeQuery :: Row Type -> RowList Type -> Constraint
263class ServeQuery row list | list -> row where
176class ServeQuery row list | list -> row where
@@ -342,6 +255,84 @@ instance DecodeJson (Record row) => FromRequest (Record row) where
342instance FromRequest Request where
255instance FromRequest Request where
343 fromRequest req = pure $ Just req
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
345class ParsePathPiece t where
336class ParsePathPiece t where
346 parsePathPiece :: String -> Maybe t
337 parsePathPiece :: String -> Maybe t
@@ -366,26 +357,26 @@ assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
366assert true = pure unit
357assert true = pure unit
367assert false = empty
358assert false = empty
369−class ServeAPI :: Row Type -> RowList Type -> Row Type -> (Type -> Type) -> Constraint
360+class ServeAPI :: Row Type -> RowList Type -> Row Type -> Row Type -> (Type -> Type) -> Constraint
370−class ServeAPI row list handlers m | row -> handlers, handlers -> m where
361+class ServeAPI row list handlers middlewares m | row -> handlers, handlers -> m where
371− serveAPI :: Nt m -> Record handlers -> Route -> Request -> Server Response
362+ serveAPI :: Nt m -> Record handlers -> Record middlewares -> Route -> Request -> Server Response
373−instance ServeAPI row RowList.Nil () m where
364+instance ServeAPI row RowList.Nil () middlewares m where
374− serveAPI _ _ _ _ = pure notFound
365+ serveAPI _ _ _ _ _ = pure notFound
377− ( ServeRoute t handler m
368+ ( ServeRoute t handler middlewares m
378 , Row.Cons field t tail row
369 , Row.Cons field t tail row
379− , ServeAPI row rest restHandlers m
370+ , ServeAPI row rest restHandlers middlewares m
380 , IsSymbol field
371 , IsSymbol field
381 , Row.Cons field handler restHandlers handlers
372 , Row.Cons field handler restHandlers handlers
382 , Row.Lacks field restHandlers
373 , Row.Lacks field restHandlers
384− ServeAPI row (RowList.Cons field t rest) handlers m where
375+ ServeAPI row (RowList.Cons field t rest) handlers middlewares m where
385− serveAPI nt handlers route req =
376+ serveAPI nt handlers middlewares route req =
386 case serveRoute @t route of
377 case serveRoute @t route of
387− Nothing -> serveAPI @row @rest @restHandlers nt (Record.delete (Proxy @field) handlers) route req
378+ Nothing -> serveAPI @row @rest @restHandlers @middlewares nt (Record.delete (Proxy @field) handlers) middlewares route req
388− Just f -> f nt (Record.get (Proxy @field) handlers) req
379+ Just f -> f nt (Record.get (Proxy @field) handlers) middlewares req
390notFound :: Response
381notFound :: Response
391notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
382notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
@@ -402,16 +393,16 @@ ok = Response { body: EmptyResponseBody, status: 200, statusText: "OK", headers:
402-- | The first argument is used to run an arbitrary monad in handlers. To use the 'Server' monad
393-- | The first argument is used to run an arbitrary monad in handlers. To use the 'Server' monad
403-- | instead you can just pass `identity` as the first argument.
394-- | instead you can just pass `identity` as the first argument.
405− :: forall @row list handlers m
396+ :: forall @row list handlers m middlewares
406 . RowToList row list
397 . RowToList row list
407− => ServeAPI row list handlers m
398+ => ServeAPI row list handlers middlewares m
408 => (forall x. m x -> Server x)
399 => (forall x. m x -> Server x)
409− -> Record handlers
400+ -> { handlers :: Record handlers, middlewares :: Record middlewares }
410 -> Request
401 -> Request
411 -> Aff Bun.Response.Response
402 -> Aff Bun.Response.Response
412−run nt handlers req = do
403+run nt { handlers, middlewares } req = do
413 Response { body, headers, status, statusText } <-
404 Response { body, headers, status, statusText } <-
414− map (either identity identity) $ runExceptT $ serveAPI @row @list @handlers (Nt nt) handlers (routeFromRequest req) req
405+ map (either identity identity) $ runExceptT $ serveAPI @row @list @handlers (Nt nt) handlers middlewares (routeFromRequest req) req
415 let opts = { headers, status, statusText }
406 let opts = { headers, status, statusText }
416 pure $ case body of
407 pure $ case body of
417 EmptyResponseBody -> Bun.Response.string "" opts
408 EmptyResponseBody -> Bun.Response.string "" opts