@@ -1,101 +1,245 @@
1module PureStack.Server
1module PureStack.Server
3− , serve
3+ , Response(..)
11+ , internalServerError
13+ -------------------------------------------------------------------------------------------------
4 , class ServeAPI
14 , class ServeAPI
5 , serveAPI
15 , serveAPI
21+ , class ParsePathPiece
9import Prelude
29import Prelude
10import PureStack.Route
30import PureStack.Route
12import Bun.Request (Request)
32import Bun.Request (Request)
33+import Bun.Request as Bun
13import Bun.Request as Request
34import Bun.Request as Request
14−import Bun.Response (Response)
35+import Bun.Response as Bun.Response
15−import Bun.Response as Response
16import Control.Alternative (class Plus, empty)
36import Control.Alternative (class Plus, empty)
17−import Control.Monad.Except (ExceptT, runExceptT, throwError)
37+import Control.Monad.Except (ExceptT, runExceptT)
18−import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson)
38+import Data.Argonaut (class DecodeJson, class EncodeJson, Json, decodeJson, encodeJson)
19import Data.Array as Array
39import Data.Array as Array
20−import Data.Either (Either(..))
40+import Data.Either (Either(..), either)
41+import Data.Int as Int
43+import Data.Map as Map
21import Data.Maybe (Maybe(..))
44import Data.Maybe (Maybe(..))
45+import Data.Number as Number
46+import Data.String as String
22import Data.Symbol (class IsSymbol, reflectSymbol)
47import Data.Symbol (class IsSymbol, reflectSymbol)
48+import Data.Traversable (traverse)
23import Data.URL as URL
49import Data.URL as URL
24−import Effect (Effect)
25import Effect.Aff (Aff)
50import Effect.Aff (Aff)
26import Effect.Aff.Class (liftAff)
51import Effect.Aff.Class (liftAff)
27−import Prim.Row (class Lacks)
52+import Foreign.Object (Object)
53+import Foreign.Object as Object
28import Prim.Row as Row
54import Prim.Row as Row
29import Prim.RowList (class RowToList, RowList)
55import Prim.RowList (class RowToList, RowList)
30import Prim.RowList as RowList
56import Prim.RowList as RowList
57+import PureStack.Cookie (Cookie)
58+import PureStack.Cookie as Cookie
31import Record as Record
59import Record as Record
60+import Record.Builder (Builder)
61+import Record.Builder as Builder
32import Type.Proxy (Proxy(..))
62import Type.Proxy (Proxy(..))
34−type Route = { path :: Array String, verb :: String }
65+ { path :: Array String
67+ , query :: Map String (Array String)
68+ , originalRequest :: Bun.Request
36routeFromRequest :: Request -> Route
71routeFromRequest :: Request -> Route
37routeFromRequest req =
72routeFromRequest req =
74+ url = URL.fromString $ Request.url req
38− { verb: Request.method req
76+ { verb: Request.method req
39− , path: case URL.fromString (Request.url req) of
77+ , path: case url of
40− Nothing -> []
78+ Nothing -> []
41− Just url -> case URL.path url of
79+ Just u -> case URL.path u of
42− URL.PathEmpty -> []
80+ URL.PathEmpty -> []
43− URL.PathAbsolute p -> p
81+ URL.PathAbsolute p -> p
44− URL.PathRelative p -> p
82+ URL.PathRelative p -> p
85+ Just u -> URL.query u
86+ , originalRequest: req
47type Server a = ExceptT Response Aff a
89type Server a = ExceptT Response Aff a
49newtype Nt m = Nt (forall x. m x -> Server x)
91newtype Nt m = Nt (forall x. m x -> Server x)
51−class ServeRoute :: forall k. k -> Type -> (Type -> Type) -> Constraint
93+class ServeRoute :: forall k. k -> Type -> Row Type -> (Type -> Type) -> Constraint
52−class ServeRoute route handler m | route -> handler, handler -> m where
94+class ServeRoute route handler middlewares m | route middlewares -> handler, handler -> m where
53− serveRoute :: Route -> Maybe (Nt m -> handler -> Request -> Server Response)
95+ serveRoute :: Route -> Maybe (Nt m -> handler -> Record middlewares -> Request -> Server Response)
55−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
56− serveRoute { path, verb } = do
98+ serveRoute route@{ path } = do
57 { head: h, tail } <- Array.uncons path
99 { head: h, tail } <- Array.uncons path
58 assert $ h == (reflectSymbol @path Proxy)
100 assert $ h == (reflectSymbol @path Proxy)
59− serveRoute @rest { path: tail, verb: verb }
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
61−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
62− serveRoute { path, verb } = do
127+ serveRoute route@{ path } = do
63 { head: h, tail } <- Array.uncons path
128 { head: h, tail } <- Array.uncons path
64 t <- parsePathPiece @t h
129 t <- parsePathPiece @t h
65− serveRoute @rest { path: tail, verb } <#> (\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)
67−else instance ToResponse resp => ServeRoute (GET resp) (m resp) m where
132+else instance MethodName (meth Unit Unit) => ServeRoute (meth Unit Unit) (m Unit) middlewares m where
68 serveRoute { path, verb } = do
133 serveRoute { path, verb } = do
69− assert $ verb == "GET"
134+ assert $ verb == methodName @(meth Unit Unit)
70 assert $ path == []
135 assert $ path == []
71− pure $ \(Nt nt) handler _req -> do
136+ pure $ \(Nt nt) handler _middlewares _req -> do
72− resp <- nt handler
137+ nt handler
73− pure $ toResponse resp
138+ pure $ ok
75−else instance (ToResponse resp) => ServeRoute (POST Unit resp) (m resp) m where
139+else instance (ToResponse resp, MethodName (meth Unit resp)) => ServeRoute (meth Unit resp) (m resp) middlewares m where
76 serveRoute { path, verb } = do
140 serveRoute { path, verb } = do
141+ assert $ verb == methodName @(meth Unit resp)
77 assert $ path == []
142 assert $ path == []
78− assert $ verb == "POST"
79− pure $ \(Nt nt) handler _req -> do
143+ pure $ \(Nt nt) handler _middlewares _req -> do
80 resp <- nt handler
144 resp <- nt handler
81 pure $ toResponse resp
145 pure $ toResponse resp
82−
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
83−else instance (FromRequest req, ToResponse resp) => ServeRoute (POST 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
84 serveRoute { path, verb } = do
157 serveRoute { path, verb } = do
158+ assert $ verb == methodName @(meth req resp)
85 assert $ path == []
159 assert $ path == []
86− assert $ verb == "POST"
87− pure $ \(Nt nt) handler req -> do
160+ pure $ \(Nt nt) handler _middlewares req -> do
88 liftAff (fromRequest req) >>= case _ of
161 liftAff (fromRequest req) >>= case _ of
89 Nothing -> pure $ badRequest
162 Nothing -> pure $ badRequest
90 Just r -> do
163 Just r -> do
91 resp <- nt $ handler r
164 resp <- nt $ handler r
92 pure $ toResponse resp
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 }
94class ToResponse r where
238class ToResponse r where
95 toResponse :: r -> Response
239 toResponse :: r -> Response
97instance EncodeJson (Record row) => ToResponse (Record row) where
241instance EncodeJson (Record row) => ToResponse (Record row) where
98− toResponse rec = Response.json (encodeJson rec) { status: 200, statusText: "OK", headers: [] }
242+ toResponse rec = Response { body: JsonResponseBody $ encodeJson rec, status: 200, statusText: "OK", headers: Object.empty }
100instance ToResponse Response where
244instance ToResponse Response where
101 toResponse resp = resp
245 toResponse resp = resp
@@ -111,106 +255,156 @@ instance DecodeJson (Record row) => FromRequest (Record row) where
111instance FromRequest Request where
255instance FromRequest Request where
112 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
114class ParsePathPiece t where
336class ParsePathPiece t where
115 parsePathPiece :: String -> Maybe t
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
117assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
356assert :: forall m. Applicative m => Plus m => Boolean -> m Unit
118assert true = pure unit
357assert true = pure unit
119assert false = empty
358assert false = empty
121−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
122−class ServeAPI row list handlers m | row -> handlers, handlers -> m where
361+class ServeAPI row list handlers middlewares m | row -> handlers, handlers -> m where
123− serveAPI :: Nt m -> Record handlers -> Route -> Request -> Server Response
362+ serveAPI :: Nt m -> Record handlers -> Record middlewares -> Route -> Request -> Server Response
125−instance ServeAPI row RowList.Nil () m where
364+instance ServeAPI row RowList.Nil () middlewares m where
126− serveAPI _ _ _ _ = pure notFound
365+ serveAPI _ _ _ _ _ = pure notFound
129− ( ServeRoute t handler m
368+ ( ServeRoute t handler middlewares m
130 , Row.Cons field t tail row
369 , Row.Cons field t tail row
131− , ServeAPI row rest restHandlers m
370+ , ServeAPI row rest restHandlers middlewares m
132 , IsSymbol field
371 , IsSymbol field
133 , Row.Cons field handler restHandlers handlers
372 , Row.Cons field handler restHandlers handlers
134 , Row.Lacks field restHandlers
373 , Row.Lacks field restHandlers
136− ServeAPI row (RowList.Cons field t rest) handlers m where
375+ ServeAPI row (RowList.Cons field t rest) handlers middlewares m where
137− serveAPI nt handlers route@{ path, verb } req =
376+ serveAPI nt handlers middlewares route req =
138− case serveRoute @t { path, verb } of
377+ case serveRoute @t route of
139− 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
140− Just f -> f nt (Record.get (Proxy @field) handlers) req
379+ Just f -> f nt (Record.get (Proxy @field) handlers) middlewares req
142notFound :: Response
381notFound :: Response
143−notFound = Response.string "" { status: 404, statusText: "Not Found", headers: [] }
382+notFound = Response { body: EmptyResponseBody, status: 404, statusText: "Not Found", headers: Object.empty }
145internalServerError :: Response
384internalServerError :: Response
146−internalServerError = Response.string "" { status: 500, statusText: "Internal Server Error", headers: [] }
385+internalServerError = Response { body: EmptyResponseBody, status: 500, statusText: "Internal Server Error", headers: Object.empty }
148badRequest :: Response
387badRequest :: Response
149−badRequest = Response.string "" { status: 400, statusText: "Bad Request", headers: [] }
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 }
151−serve
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.
152− :: forall @row list handlers m
396+ :: forall @row list handlers m middlewares
153 . RowToList row list
397 . RowToList row list
154− => ServeAPI row list handlers m
398+ => ServeAPI row list handlers middlewares m
155 => (forall x. m x -> Server x)
399 => (forall x. m x -> Server x)
156− -> Record handlers
400+ -> { handlers :: Record handlers, middlewares :: Record middlewares }
157 -> Request
401 -> Request
158− -> Aff Response
402+ -> Aff Bun.Response.Response
159−serve nt handlers req = do
403+run nt { handlers, middlewares } req = do
404+ Response { body, headers, status, statusText } <-
160− res <- 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
406+ let opts = { headers, status, statusText }
161− pure case res of
407+ pure $ case body of
162− Left resp -> resp
408+ EmptyResponseBody -> Bun.Response.string "" opts
163− Right resp -> resp
409+ JsonResponseBody json -> Bun.Response.json json opts
164−
410+ StringResponseBody string -> Bun.Response.string string opts
165−-- class RecordNaturalTransformation
174−-- RecordNaturalTransformation from to row list row'
175−-- | list from -> row
177−-- , row' from -> row
178−-- , row to -> row where
179−-- nt' :: (forall a. from a -> to a) -> Record row -> Record row'
181−-- instance RecordNaturalTransformation from to () RowList.Nil () where
186−-- , Row.Cons field t tail row
187−-- , Row.Cons field t' tail' row'
188−-- , FunctionNaturalTransformation from to t t'
189−-- , RecordNaturalTransformation from to tail rest tail'
190−-- , Lacks field tail
191−-- , Lacks field tail'
193−-- RecordNaturalTransformation from to row (RowList.Cons field t rest) row' where
194−-- nt' f rec = Record.insert (Proxy @field) (ntFunction f $ Record.get (Proxy @field) rec)
195−-- $ nt' @from @to @tail @rest @tail' f (Record.delete (Proxy @field) rec)
197−-- class FunctionNaturalTransformation :: forall k. (k -> Type) -> (k -> Type) -> Type -> Type -> Constraint
198−-- class FunctionNaturalTransformation from to func func' | to func -> func' where
199−-- ntFunction :: (forall a. from a -> to a) -> func -> func'
201−-- instance FunctionNaturalTransformation from to (from x) (to x) where
202−-- ntFunction f g = f g
205−-- FunctionNaturalTransformation from to func func' =>
206−-- FunctionNaturalTransformation from to (a -> func) (a -> func') where
207−-- ntFunction f g = \a -> ntFunction f (g a)
210−-- :: forall @from row row' list
211−-- . RowToList row list
212−-- => RecordNaturalTransformation from Aff row list row'
213−-- => (forall a. from a -> Aff a)
216−-- nt f rec = nt' @from @Aff @row @list @row' f rec