@@ -1,7 +1,5 @@
1module PureStack.Server
1module PureStack.Server
5 , class ToResponse
3 , class ToResponse
6 , toResponse
4 , toResponse
7 , class FromRequest
5 , class FromRequest
@@ -20,24 +18,20 @@ module PureStack.Server
20 , serveQuery
18 , serveQuery
21 , class ParsePathPiece
19 , class ParsePathPiece
22 , parsePathPiece
20 , parsePathPiece
29import Prelude
23import Prelude
30import PureStack.Route
24import PureStack.Route
32import Bun.Request (Request)
26import Bun.Request (Request)
33−import Bun.Request as Bun
34import Bun.Request as Request
27import Bun.Request as Request
35−import Bun.Response as Bun.Response
28+import Bun.Response (Response)
29+import Bun.Response as Response
36import Control.Alternative (class Plus, empty)
30import Control.Alternative (class Plus, empty)
37import Control.Monad.Except (ExceptT, runExceptT)
31import 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)
39import Data.Array as Array
33import Data.Array as Array
40−import Data.Either (Either(..), either)
34+import Data.Either (Either(..))
41import Data.Int as Int
35import Data.Int as Int
42import Data.Map (Map)
36import Data.Map (Map)
43import Data.Map as Map
37import Data.Map as Map
@@ -49,24 +43,15 @@ import Data.Traversable (traverse)
49import Data.URL as URL
43import Data.URL as URL
50import Effect.Aff (Aff)
44import Effect.Aff (Aff)
51import Effect.Aff.Class (liftAff)
45import Effect.Aff.Class (liftAff)
52−import Foreign.Object (Object)
53−import Foreign.Object as Object
54import Prim.Row as Row
46import Prim.Row as Row
55import Prim.RowList (class RowToList, RowList)
47import Prim.RowList (class RowToList, RowList)
56import Prim.RowList as RowList
48import Prim.RowList as RowList
57−import PureStack.Cookie (Cookie)
58−import PureStack.Cookie as Cookie
59import Record as Record
49import Record as Record
60import Record.Builder (Builder)
50import Record.Builder (Builder)
61import Record.Builder as Builder
51import Record.Builder as Builder
62import Type.Proxy (Proxy(..))
52import Type.Proxy (Proxy(..))
65− { path :: Array String
67− , query :: Map String (Array String)
54+type Route = { path :: Array String, verb :: String, query :: Map String (Array String) }
68− , originalRequest :: Bun.Request
71routeFromRequest :: Request -> Route
56routeFromRequest :: Request -> Route
72routeFromRequest req =
57routeFromRequest req =
@@ -83,94 +68,185 @@ routeFromRequest req =
83 , query: case url of
68 , query: case url of
84 Nothing -> Map.empty
69 Nothing -> Map.empty
85 Just u -> URL.query u
70 Just u -> URL.query u
86− , originalRequest: req
89type Server a = ExceptT Response Aff a
73type Server a = ExceptT Response Aff a
91newtype Nt m = Nt (forall x. m x -> Server x)
75newtype Nt m = Nt (forall x. m x -> Server x)
93−class ServeRoute :: forall k. k -> Type -> Row Type -> (Type -> Type) -> Constraint
77+class ServeRoute :: forall k. k -> Type -> (Type -> Type) -> Constraint
94−class ServeRoute route handler middlewares m | route middlewares -> handler, handler -> m where
78+class ServeRoute route handler m | route -> handler, handler -> m where
95− serveRoute :: Route -> Maybe (Nt m -> handler -> Record middlewares -> Request -> Server Response)
79+ serveRoute :: Route -> Maybe (Nt m -> handler -> Request -> Server Response)
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
98 serveRoute route@{ path } = do
82 serveRoute route@{ path } = do
99 { head: h, tail } <- Array.uncons path
83 { head: h, tail } <- Array.uncons path
100 assert $ h == (reflectSymbol @path Proxy)
84 assert $ h == (reflectSymbol @path Proxy)
101 serveRoute @rest route { path = tail }
85 serveRoute @rest route { path = tail }
103else instance
87else instance
104− ( ServeRoute rest handler middlewares m
88+ ( ServeRoute rest handler m
105 , ServeQuery row list
89 , ServeQuery row list
106 , RowToList row list
90 , RowToList row list
108− ServeRoute (Record row / rest) (Record row -> handler) middlewares m where
92+ ServeRoute (Record row / rest) (Record row -> handler) m where
109 serveRoute route@{ query } = do
93 serveRoute route@{ query } = do
110 builder <- serveQuery @row @list query
94 builder <- serveQuery @row @list query
111 serveRoute @rest route <#> (\f nt handler req -> f nt (handler $ Builder.buildFromScratch builder) req)
95 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
97+else instance (ServeRoute rest handler m, ParsePathPiece t) => ServeRoute (t / rest) (t -> handler) m where
127 serveRoute route@{ path } = do
98 serveRoute route@{ path } = do
128 { head: h, tail } <- Array.uncons path
99 { head: h, tail } <- Array.uncons path
129 t <- parsePathPiece @t h
100 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)
103+else instance ToResponse resp => ServeRoute (GET resp) (m resp) m where
104+ serveRoute { path, verb } = do
105+ assert $ verb == "GET"
107+ pure $ \(Nt nt) handler _req -> do
109+ pure $ toResponse resp
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
133 serveRoute { path, verb } = do
112 serveRoute { path, verb } = do
134− assert $ verb == methodName @(meth Unit Unit)
113+ assert $ verb == "POST"
135 assert $ path == []
114 assert $ path == []
136− pure $ \(Nt nt) handler _middlewares _req -> do
115+ pure $ \(Nt nt) handler _req -> do
137 nt handler
116 nt handler
138 pure $ ok
117 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
140 serveRoute { path, verb } = do
119 serveRoute { path, verb } = do
141− assert $ verb == methodName @(meth Unit resp)
120+ assert $ verb == "POST"
142 assert $ path == []
121 assert $ path == []
143− pure $ \(Nt nt) handler _middlewares _req -> do
122+ pure $ \(Nt nt) handler _req -> do
144 resp <- nt handler
123 resp <- nt handler
145 pure $ toResponse resp
124 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
147 serveRoute { path, verb } = do
126 serveRoute { path, verb } = do
148− assert $ verb == methodName @(meth resp Unit)
127+ assert $ verb == "POST"
149 assert $ path == []
128 assert $ path == []
150− pure $ \(Nt nt) handler _middlewares req -> do
129+ pure $ \(Nt nt) handler req -> do
151 liftAff (fromRequest req) >>= case _ of
130 liftAff (fromRequest req) >>= case _ of
152 Nothing -> pure $ badRequest
131 Nothing -> pure $ badRequest
153 Just r -> do
132 Just r -> do
154 nt $ handler r
133 nt $ handler r
155 pure $ ok
134 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
157 serveRoute { path, verb } = do
136 serveRoute { path, verb } = do
158− assert $ verb == methodName @(meth req resp)
137+ assert $ verb == "POST"
159 assert $ path == []
138 assert $ path == []
160− pure $ \(Nt nt) handler _middlewares req -> do
139+ pure $ \(Nt nt) handler req -> do
161 liftAff (fromRequest req) >>= case _ of
140 liftAff (fromRequest req) >>= case _ of
162 Nothing -> pure $ badRequest
141 Nothing -> pure $ badRequest
163 Just r -> do
142 Just r -> do
164 resp <- nt $ handler r
143 resp <- nt $ handler r
165 pure $ toResponse resp
144 pure $ toResponse resp
167−else instance
146+else instance ServeRoute (DELETE Unit Unit) (m Unit) m where
168− ( ServeAPI routes list handlers middlewares m
147+ serveRoute { path, verb } = do
169− , RowToList routes list
148+ assert $ verb == "DELETE"
170− ) =>
149+ assert $ path == []
171− ServeRoute routes (Record handlers) middlewares m where
150+ pure $ \(Nt nt) handler _req -> do
172− serveRoute route = pure $ \nt handlers middlewares req ->
151+ nt handler
173− serveAPI @routes @list nt handlers middlewares route req
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"
157+ pure $ \(Nt nt) handler _req -> do
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"
164+ pure $ \(Nt nt) handler req -> do
165+ liftAff (fromRequest req) >>= case _ of
166+ Nothing -> pure $ badRequest
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"
174+ pure $ \(Nt nt) handler req -> do
175+ liftAff (fromRequest req) >>= case _ of
176+ Nothing -> pure $ badRequest
178+ resp <- nt $ handler r
179+ pure $ toResponse resp
181+else instance ServeRoute (PUT Unit Unit) (m Unit) m where
182+ serveRoute { path, verb } = do
183+ assert $ verb == "PUT"
185+ pure $ \(Nt nt) handler _req -> do
188+else instance (ToResponse resp) => ServeRoute (PUT Unit resp) (m resp) m where
189+ serveRoute { path, verb } = do
190+ assert $ verb == "PUT"
192+ pure $ \(Nt nt) handler _req -> do
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"
199+ pure $ \(Nt nt) handler req -> do
200+ liftAff (fromRequest req) >>= case _ of
201+ Nothing -> pure $ badRequest
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"
209+ pure $ \(Nt nt) handler req -> do
210+ liftAff (fromRequest req) >>= case _ of
211+ Nothing -> pure $ badRequest
213+ resp <- nt $ handler r
214+ pure $ toResponse resp
216+else instance ServeRoute (PATCH Unit Unit) (m Unit) m where
217+ serveRoute { path, verb } = do
218+ assert $ verb == "PATCH"
220+ pure $ \(Nt nt) handler _req -> do
223+else instance (ToResponse resp) => ServeRoute (PATCH Unit resp) (m resp) m where
224+ serveRoute { path, verb } = do
225+ assert $ verb == "PATCH"
227+ pure $ \(Nt nt) handler _req -> do
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"
234+ pure $ \(Nt nt) handler req -> do
235+ liftAff (fromRequest req) >>= case _ of
236+ Nothing -> pure $ badRequest
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"
244+ pure $ \(Nt nt) handler req -> do
245+ liftAff (fromRequest req) >>= case _ of
246+ Nothing -> pure $ badRequest
248+ resp <- nt $ handler r
249+ pure $ toResponse resp
175class ServeQuery :: Row Type -> RowList Type -> Constraint
251class ServeQuery :: Row Type -> RowList Type -> Constraint
176class ServeQuery row list | list -> row where
252class ServeQuery row list | list -> row where
@@ -228,18 +304,11 @@ else instance
228 r <- serveQuery @tail @rest m
304 r <- serveQuery @tail @rest m
229 pure $ Builder.insert (Proxy @field) v <<< r
305 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 }
238class ToResponse r where
307class ToResponse r where
239 toResponse :: r -> Response
308 toResponse :: r -> Response
241instance EncodeJson (Record row) => ToResponse (Record row) where
310instance EncodeJson (Record row) => ToResponse (Record row) where
242− toResponse rec = Response { body: JsonResponseBody $ encodeJson rec, status: 200, statusText: "OK", headers: Object.empty }
311+ toResponse rec = Response.json (encodeJson rec) { status: 200, statusText: "OK", headers: [] }
244instance ToResponse Response where
313instance ToResponse Response where
245 toResponse resp = resp
314 toResponse resp = resp
@@ -255,84 +324,6 @@ instance DecodeJson (Record row) => FromRequest (Record row) where
255instance FromRequest Request where
324instance FromRequest Request where
256 fromRequest req = pure $ Just req
325 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
336class ParsePathPiece t where
327class ParsePathPiece t where
337 parsePathPiece :: String -> Maybe t
328 parsePathPiece :: String -> Maybe t
@@ -357,54 +348,51 @@ assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
357assert true = pure unit
348assert true = pure unit
358assert false = empty
349assert false = empty
360−class ServeAPI :: Row Type -> RowList Type -> Row Type -> Row Type -> (Type -> Type) -> Constraint
351+class ServeAPI :: Row Type -> RowList Type -> Row Type -> (Type -> Type) -> Constraint
361−class ServeAPI row list handlers middlewares m | row -> handlers, handlers -> m where
352+class ServeAPI row list handlers m | row -> handlers, handlers -> m where
362− serveAPI :: Nt m -> Record handlers -> Record middlewares -> Route -> Request -> Server Response
353+ serveAPI :: Nt m -> Record handlers -> Route -> Request -> Server Response
364−instance ServeAPI row RowList.Nil () middlewares m where
355+instance ServeAPI row RowList.Nil () m where
365− serveAPI _ _ _ _ _ = pure notFound
356+ serveAPI _ _ _ _ = pure notFound
368− ( ServeRoute t handler middlewares m
359+ ( ServeRoute t handler m
369 , Row.Cons field t tail row
360 , Row.Cons field t tail row
370− , ServeAPI row rest restHandlers middlewares m
361+ , ServeAPI row rest restHandlers m
371 , IsSymbol field
362 , IsSymbol field
372 , Row.Cons field handler restHandlers handlers
363 , Row.Cons field handler restHandlers handlers
373 , Row.Lacks field restHandlers
364 , Row.Lacks field restHandlers
375− ServeAPI row (RowList.Cons field t rest) handlers middlewares m where
366+ ServeAPI row (RowList.Cons field t rest) handlers m where
376− serveAPI nt handlers middlewares route req =
367+ serveAPI nt handlers route req =
377 case serveRoute @t route of
368 case serveRoute @t route of
378− Nothing -> serveAPI @row @rest @restHandlers @middlewares nt (Record.delete (Proxy @field) handlers) middlewares route req
369+ Nothing -> serveAPI @row @rest @restHandlers nt (Record.delete (Proxy @field) handlers) route req
379− Just f -> f nt (Record.get (Proxy @field) handlers) middlewares req
370+ Just f -> f nt (Record.get (Proxy @field) handlers) req
381notFound :: Response
372notFound :: Response
382−notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
373+notFound = Response.string "" { status: 404, statusText: "Not Found", headers: [] }
384internalServerError :: Response
375internalServerError :: Response
385−internalServerError = Response { body: EmptyResponseBody, status: 500, statusText: "Internal Server Error", headers: Object.empty }
376+internalServerError = Response.string "" { status: 500, statusText: "Internal Server Error", headers: [] }
387badRequest :: Response
378badRequest :: Response
388−badRequest = Response { body: EmptyResponseBody, status: 400, statusText: "Bad Request", headers: Object.empty }
379+badRequest = Response.string "" { status: 400, statusText: "Bad Request", headers: [] }
390ok :: Response
381ok :: Response
391−ok = Response { body: EmptyResponseBody, status: 200, statusText: "OK", headers: Object.empty }
382+ok = Response.string "" { status: 200, statusText: "OK", headers: [] }
393-- | The first argument is used to run an arbitrary monad in handlers. To use the 'Server' monad
384-- | 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.
385-- | instead you can just pass `identity` as the first argument.
396− :: forall @row list handlers m middlewares
387+ :: forall @row list handlers m
397 . RowToList row list
388 . RowToList row list
398− => ServeAPI row list handlers middlewares m
389+ => ServeAPI row list handlers m
399 => (forall x. m x -> Server x)
390 => (forall x. m x -> Server x)
400− -> { handlers :: Record handlers, middlewares :: Record middlewares }
391+ -> Record handlers
401 -> Request
392 -> Request
402− -> Aff Bun.Response.Response
393+ -> Aff Response
403−run nt { handlers, middlewares } req = do
394+run nt handlers 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
395+ res <- runExceptT $ serveAPI @row @list @handlers (Nt nt) handlers (routeFromRequest req) req
406− let opts = { headers, status, statusText }
407− pure $ case body of
396+ pure case res of
408− EmptyResponseBody -> Bun.Response.string "" opts
397+ Left resp -> resp
409− JsonResponseBody json -> Bun.Response.json json opts
398+ Right resp -> resp
410− StringResponseBody string -> Bun.Response.string string opts