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