@@ -1,7 +1,5 @@
11module PureStack.Server
22 ( Server
3− , Response(..)
4− , ResponseBody(..)
53 , class ToResponse
64 , toResponse
75 , class FromRequest
@@ -20,24 +18,20 @@ module PureStack.Server
2018 , serveQuery
2119 , class ParsePathPiece
2220 , parsePathPiece
23− , class FromHeaders
24− , fromHeaders
25− , class FromHeader
26− , fromHeader
2721 ) where
2822
2923import Prelude
3024import PureStack.Route
3125
3226import Bun.Request (Request)
33−import Bun.Request as Bun
3427import Bun.Request as Request
35−import Bun.Response as Bun.Response
28+import Bun.Response (Response)
29+import Bun.Response as Response
3630import Control.Alternative (class Plus, empty)
3731import Control.Monad.Except (ExceptT, runExceptT)
38−import Data.Argonaut (class DecodeJson, class EncodeJson, Json, decodeJson, encodeJson)
32+import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson)
3933import Data.Array as Array
40−import Data.Either (Either(..), either)
34+import Data.Either (Either(..))
4135import Data.Int as Int
4236import Data.Map (Map)
4337import Data.Map as Map
@@ -49,24 +43,15 @@ import Data.Traversable (traverse)
4943import Data.URL as URL
5044import Effect.Aff (Aff)
5145import Effect.Aff.Class (liftAff)
52−import Foreign.Object (Object)
53−import Foreign.Object as Object
5446import Prim.Row as Row
5547import Prim.RowList (class RowToList, RowList)
5648import Prim.RowList as RowList
57−import PureStack.Cookie (Cookie)
58−import PureStack.Cookie as Cookie
5949import Record as Record
6050import Record.Builder (Builder)
6151import Record.Builder as Builder
6252import Type.Proxy (Proxy(..))
6353
64−type Route =
65− { path :: Array String
66− , verb :: String
67− , query :: Map String (Array String)
68− , originalRequest :: Bun.Request
69− }
54+type Route = { path :: Array String, verb :: String, query :: Map String (Array String) }
7055
7156routeFromRequest :: Request -> Route
7257routeFromRequest req =
@@ -83,81 +68,180 @@ routeFromRequest req =
8368 , query: case url of
8469 Nothing -> Map.empty
8570 Just u -> URL.query u
86− , originalRequest: req
8771 }
8872
8973type Server a = ExceptT Response Aff a
9074
9175newtype Nt m = Nt (forall x. m x -> Server x)
9276
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)
77+class ServeRoute :: forall k. k -> Type -> (Type -> Type) -> Constraint
78+class ServeRoute route handler m | route -> handler, handler -> m where
79+ serveRoute :: Route -> Maybe (Nt m -> handler -> Request -> Server Response)
9680
97−instance (ServeRoute rest handler middlewares m, IsSymbol path) => ServeRoute (path / rest) handler middlewares m where
81+instance (ServeRoute rest handler m, IsSymbol path) => ServeRoute (path / rest) handler m where
9882 serveRoute route@{ path } = do
9983 { head: h, tail } <- Array.uncons path
10084 assert $ h == (reflectSymbol @path Proxy)
10185 serveRoute @rest route { path = tail }
10286
10387else instance
104− ( ServeRoute rest handler middlewares m
88+ ( ServeRoute rest handler m
10589 , ServeQuery row list
10690 , RowToList row list
10791 ) =>
108− ServeRoute (Record row / rest) (Record row -> handler) middlewares m where
92+ ServeRoute (Record row / rest) (Record row -> handler) m where
10993 serveRoute route@{ query } = do
11094 builder <- serveQuery @row @list query
11195 serveRoute @rest route <#> (\f nt handler req -> f nt (handler $ Builder.buildFromScratch builder) req)
11296
113−else instance
114− ( ServeRoute rest handler middlewares m
115− , IsSymbol field
116− , Row.Cons field (Bun.Request -> m x) tail middlewares
117− ) =>
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
124− )
125−
126−else instance (ServeRoute rest handler middlewares m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) middlewares m where
97+else instance (ServeRoute rest handler m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) m where
12798 serveRoute route@{ path } = do
12899 { head: h, tail } <- Array.uncons path
129100 t <- parsePathPiece @t h
130− serveRoute @rest route { path = tail } <#> (\f nt handler mws req -> f nt (handler t) mws req)
101+ serveRoute @rest route { path = tail } <#> (\f nt handler req -> f nt (handler t) req)
102+
103+else instance ToResponse resp => ServeRoute (GET resp) (m resp) m where
104+ serveRoute { path, verb } = do
105+ assert $ verb == "GET"
106+ assert $ path == []
107+ pure $ \(Nt nt) handler _req -> do
108+ resp <- nt handler
109+ pure $ toResponse resp
131110
132−else instance MethodName (meth Unit Unit) => ServeRoute (meth Unit Unit) (m Unit) middlewares m where
111+else instance ServeRoute (POST Unit Unit) (m Unit) m where
133112 serveRoute { path, verb } = do
134− assert $ verb == methodName @(meth Unit Unit)
113+ assert $ verb == "POST"
135114 assert $ path == []
136− pure $ \(Nt nt) handler _middlewares _req -> do
115+ pure $ \(Nt nt) handler _req -> do
137116 nt handler
138117 pure $ ok
139−else instance (ToResponse resp, MethodName (meth Unit resp)) => ServeRoute (meth Unit resp) (m resp) middlewares m where
118+else instance (ToResponse resp) => ServeRoute (POST Unit resp) (m resp) m where
140119 serveRoute { path, verb } = do
141− assert $ verb == methodName @(meth Unit resp)
120+ assert $ verb == "POST"
142121 assert $ path == []
143− pure $ \(Nt nt) handler _middlewares _req -> do
122+ pure $ \(Nt nt) handler _req -> do
144123 resp <- nt handler
145124 pure $ toResponse resp
146−else instance (FromRequest req, MethodName (meth resp Unit)) => ServeRoute (meth resp Unit) (req -> m Unit) middlewares m where
125+else instance (FromRequest req) => ServeRoute (POST resp Unit) (req -> m Unit) m where
147126 serveRoute { path, verb } = do
148− assert $ verb == methodName @(meth resp Unit)
127+ assert $ verb == "POST"
149128 assert $ path == []
150− pure $ \(Nt nt) handler _middlewares req -> do
129+ pure $ \(Nt nt) handler req -> do
151130 liftAff (fromRequest req) >>= case _ of
152131 Nothing -> pure $ badRequest
153132 Just r -> do
154133 nt $ handler r
155134 pure $ ok
156−else instance (FromRequest req, ToResponse resp, MethodName (meth req resp)) => ServeRoute (meth req resp) (req -> m resp) middlewares m where
135+else instance (FromRequest req, ToResponse resp) => ServeRoute (POST req resp) (req -> m resp) m where
157136 serveRoute { path, verb } = do
158− assert $ verb == methodName @(meth req resp)
137+ assert $ verb == "POST"
159138 assert $ path == []
160− pure $ \(Nt nt) handler _middlewares req -> do
139+ pure $ \(Nt nt) handler req -> do
140+ liftAff (fromRequest req) >>= case _ of
141+ Nothing -> pure $ badRequest
142+ Just r -> do
143+ resp <- nt $ handler r
144+ pure $ toResponse resp
145+
146+else instance ServeRoute (DELETE Unit Unit) (m Unit) m where
147+ serveRoute { path, verb } = do
148+ assert $ verb == "DELETE"
149+ assert $ path == []
150+ pure $ \(Nt nt) handler _req -> do
151+ nt handler
152+ pure $ ok
153+else instance (ToResponse resp) => ServeRoute (DELETE Unit resp) (m resp) m where
154+ serveRoute { path, verb } = do
155+ assert $ verb == "DELETE"
156+ assert $ path == []
157+ pure $ \(Nt nt) handler _req -> do
158+ resp <- nt handler
159+ pure $ toResponse resp
160+else instance (FromRequest req) => ServeRoute (DELETE resp Unit) (req -> m Unit) m where
161+ serveRoute { path, verb } = do
162+ assert $ verb == "DELETE"
163+ assert $ path == []
164+ pure $ \(Nt nt) handler req -> do
165+ liftAff (fromRequest req) >>= case _ of
166+ Nothing -> pure $ badRequest
167+ Just r -> do
168+ nt $ handler r
169+ pure $ ok
170+else instance (FromRequest req, ToResponse resp) => ServeRoute (DELETE req resp) (req -> m resp) m where
171+ serveRoute { path, verb } = do
172+ assert $ verb == "DELETE"
173+ assert $ path == []
174+ pure $ \(Nt nt) handler req -> do
175+ liftAff (fromRequest req) >>= case _ of
176+ Nothing -> pure $ badRequest
177+ Just r -> do
178+ resp <- nt $ handler r
179+ pure $ toResponse resp
180+
181+else instance ServeRoute (PUT Unit Unit) (m Unit) m where
182+ serveRoute { path, verb } = do
183+ assert $ verb == "PUT"
184+ assert $ path == []
185+ pure $ \(Nt nt) handler _req -> do
186+ nt handler
187+ pure $ ok
188+else instance (ToResponse resp) => ServeRoute (PUT Unit resp) (m resp) m where
189+ serveRoute { path, verb } = do
190+ assert $ verb == "PUT"
191+ assert $ path == []
192+ pure $ \(Nt nt) handler _req -> do
193+ resp <- nt handler
194+ pure $ toResponse resp
195+else instance (FromRequest req) => ServeRoute (PUT resp Unit) (req -> m Unit) m where
196+ serveRoute { path, verb } = do
197+ assert $ verb == "PUT"
198+ assert $ path == []
199+ pure $ \(Nt nt) handler req -> do
200+ liftAff (fromRequest req) >>= case _ of
201+ Nothing -> pure $ badRequest
202+ Just r -> do
203+ nt $ handler r
204+ pure $ ok
205+else instance (FromRequest req, ToResponse resp) => ServeRoute (PUT req resp) (req -> m resp) m where
206+ serveRoute { path, verb } = do
207+ assert $ verb == "PUT"
208+ assert $ path == []
209+ pure $ \(Nt nt) handler req -> do
210+ liftAff (fromRequest req) >>= case _ of
211+ Nothing -> pure $ badRequest
212+ Just r -> do
213+ resp <- nt $ handler r
214+ pure $ toResponse resp
215+
216+else instance ServeRoute (PATCH Unit Unit) (m Unit) m where
217+ serveRoute { path, verb } = do
218+ assert $ verb == "PATCH"
219+ assert $ path == []
220+ pure $ \(Nt nt) handler _req -> do
221+ nt handler
222+ pure $ ok
223+else instance (ToResponse resp) => ServeRoute (PATCH Unit resp) (m resp) m where
224+ serveRoute { path, verb } = do
225+ assert $ verb == "PATCH"
226+ assert $ path == []
227+ pure $ \(Nt nt) handler _req -> do
228+ resp <- nt handler
229+ pure $ toResponse resp
230+else instance (FromRequest req) => ServeRoute (PATCH resp Unit) (req -> m Unit) m where
231+ serveRoute { path, verb } = do
232+ assert $ verb == "PATCH"
233+ assert $ path == []
234+ pure $ \(Nt nt) handler req -> do
235+ liftAff (fromRequest req) >>= case _ of
236+ Nothing -> pure $ badRequest
237+ Just r -> do
238+ nt $ handler r
239+ pure $ ok
240+else instance (FromRequest req, ToResponse resp) => ServeRoute (PATCH req resp) (req -> m resp) m where
241+ serveRoute { path, verb } = do
242+ assert $ verb == "PATCH"
243+ assert $ path == []
244+ pure $ \(Nt nt) handler req -> do
161245 liftAff (fromRequest req) >>= case _ of
162246 Nothing -> pure $ badRequest
163247 Just r -> do
@@ -165,12 +249,12 @@ else instance (FromRequest req, ToResponse resp, MethodName (meth req resp)) =>
165249 pure $ toResponse resp
166250
167251else instance
168− ( ServeAPI routes list handlers middlewares m
252+ ( ServeAPI routes list handlers m
169253 , RowToList routes list
170254 ) =>
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
255+ ServeRoute routes (Record handlers) m where
256+ serveRoute route = pure $ \nt handlers req ->
257+ serveAPI @routes @list nt handlers route req
174258
175259class ServeQuery :: Row Type -> RowList Type -> Constraint
176260class ServeQuery row list | list -> row where
@@ -228,18 +312,11 @@ else instance
228312 r <- serveQuery @tail @rest m
229313 pure $ Builder.insert (Proxy @field) v <<< r
230314
231−data ResponseBody
232− = JsonResponseBody Json
233− | EmptyResponseBody
234− | StringResponseBody String
235−
236−newtype Response = Response { body :: ResponseBody, status :: Int, statusText :: String, headers :: Object String }
237−
238315class ToResponse r where
239316 toResponse :: r -> Response
240317
241318instance EncodeJson (Record row) => ToResponse (Record row) where
242− toResponse rec = Response { body: JsonResponseBody $ encodeJson rec, status: 200, statusText: "OK", headers: Object.empty }
319+ toResponse rec = Response.json (encodeJson rec) { status: 200, statusText: "OK", headers: [] }
243320
244321instance ToResponse Response where
245322 toResponse resp = resp
@@ -255,84 +332,6 @@ instance DecodeJson (Record row) => FromRequest (Record row) where
255332instance FromRequest Request where
256333 fromRequest req = pure $ Just req
257334
258−instance FromRequest Unit where
259− fromRequest _ = pure $ Just unit
260−
261−instance
262− ( FromRequest req
263− , FromHeader header
264− ) =>
265− FromRequest (Headers (Object header) req) where
266− fromRequest req = do
267− r <- fromRequest req
268− pure $ do
269− headers <- traverse fromHeader (Request.headers req)
270− r' <- r
271− pure $ Headers headers r'
272−
273−instance
274− ( RowToList headers headersList
275− , FromRequest req
276− , FromHeaders headers headersList
277− ) =>
278− FromRequest (Headers (Record headers) req) where
279− fromRequest req = do
280− r <- fromRequest req
281− pure $ do
282− headers <- fromHeaders @headers @headersList (Request.headers req)
283− r' <- r
284− pure $ Headers (Builder.buildFromScratch headers) r'
285−
286−class FromHeader t where
287− fromHeader :: String -> Maybe t
288−
289−instance FromHeader Cookie where
290− fromHeader = Cookie.parse >>> case _ of
291− Left _ -> Nothing
292− Right x -> Just x
293−
294−instance FromHeader String where
295− fromHeader = Just
296−
297−instance FromHeader Int where
298− fromHeader = Int.fromString
299−
300−class FromHeaders :: Row Type -> RowList Type -> Constraint
301−class FromHeaders row list | list -> row where
302− fromHeaders :: Object String -> Maybe (Builder {} (Record row))
303−
304−instance FromHeaders () RowList.Nil where
305− fromHeaders _ = Just $ identity
306−
307−instance
308− ( FromHeader t
309− , IsSymbol field
310− , FromHeaders tail rest
311− , Row.Cons field (Maybe t) tail row
312− , Row.Lacks field tail
313− ) =>
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
319− Just t -> do
320− v <- fromHeader t
321− pure $ Builder.insert (Proxy @field) (Just v) <<< r
322−
323−else instance
324− ( FromHeader t
325− , IsSymbol field
326− , FromHeaders tail rest
327− , Row.Cons field t tail row
328− , Row.Lacks field tail
329− ) =>
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
335−
336335class ParsePathPiece t where
337336 parsePathPiece :: String -> Maybe t
338337
@@ -357,54 +356,51 @@ assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
357356assert true = pure unit
358357assert false = empty
359358
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
359+class ServeAPI :: Row Type -> RowList Type -> Row Type -> (Type -> Type) -> Constraint
360+class ServeAPI row list handlers m | row -> handlers, handlers -> m where
361+ serveAPI :: Nt m -> Record handlers -> Route -> Request -> Server Response
363362
364−instance ServeAPI row RowList.Nil () middlewares m where
365− serveAPI _ _ _ _ _ = pure notFound
363+instance ServeAPI row RowList.Nil () m where
364+ serveAPI _ _ _ _ = pure notFound
366365
367366instance
368− ( ServeRoute t handler middlewares m
367+ ( ServeRoute t handler m
369368 , Row.Cons field t tail row
370− , ServeAPI row rest restHandlers middlewares m
369+ , ServeAPI row rest restHandlers m
371370 , IsSymbol field
372371 , Row.Cons field handler restHandlers handlers
373372 , Row.Lacks field restHandlers
374373 ) =>
375− ServeAPI row (RowList.Cons field t rest) handlers middlewares m where
376− serveAPI nt handlers middlewares route req =
374+ ServeAPI row (RowList.Cons field t rest) handlers m where
375+ serveAPI nt handlers route req =
377376 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
377+ Nothing -> serveAPI @row @rest @restHandlers nt (Record.delete (Proxy @field) handlers) route req
378+ Just f -> f nt (Record.get (Proxy @field) handlers) req
380379
381380notFound :: Response
382−notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
381+notFound = Response.string "" { status: 404, statusText: "Not Found", headers: [] }
383382
384383internalServerError :: Response
385−internalServerError = Response { body: EmptyResponseBody, status: 500, statusText: "Internal Server Error", headers: Object.empty }
384+internalServerError = Response.string "" { status: 500, statusText: "Internal Server Error", headers: [] }
386385
387386badRequest :: Response
388−badRequest = Response { body: EmptyResponseBody, status: 400, statusText: "Bad Request", headers: Object.empty }
387+badRequest = Response.string "" { status: 400, statusText: "Bad Request", headers: [] }
389388
390389ok :: Response
391−ok = Response { body: EmptyResponseBody, status: 200, statusText: "OK", headers: Object.empty }
390+ok = Response.string "" { status: 200, statusText: "OK", headers: [] }
392391
393392-- | The first argument is used to run an arbitrary monad in handlers. To use the 'Server' monad
394393-- | instead you can just pass `identity` as the first argument.
395394run
396− :: forall @row list handlers m middlewares
395+ :: forall @row list handlers m
397396 . RowToList row list
398− => ServeAPI row list handlers middlewares m
397+ => ServeAPI row list handlers m
399398 => (forall x. m x -> Server x)
400− -> { handlers :: Record handlers, middlewares :: Record middlewares }
399+ -> Record handlers
401400 -> Request
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 }
407− pure $ case body of
408− EmptyResponseBody -> Bun.Response.string "" opts
409− JsonResponseBody json -> Bun.Response.json json opts
410− StringResponseBody string -> Bun.Response.string string opts
401+ -> Aff Response
402+run nt handlers req = do
403+ res <- runExceptT $ serveAPI @row @list @handlers (Nt nt) handlers (routeFromRequest req) req
404+ pure case res of
405+ Left resp -> resp
406+ Right resp -> resp