Skip to content

Commit

Permalink
Update node libs (tests do not pass)
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanMartinez authored and sigma-andex committed Sep 18, 2023
1 parent 791a18c commit 4667fee
Show file tree
Hide file tree
Showing 19 changed files with 176 additions and 182 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"purescript.buildCommand": "spago -x test.dhall build --purs-args --json-errors"
}
3 changes: 3 additions & 0 deletions spago.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
, "either"
, "exceptions"
, "foldable-traversable"
, "foreign"
, "foreign-object"
, "functions"
, "js-uri"
Expand All @@ -19,7 +20,9 @@
, "maybe"
, "newtype"
, "node-buffer"
, "node-event-emitter"
, "node-fs"
, "node-net"
, "node-http"
, "node-process"
, "node-streams"
Expand Down
55 changes: 25 additions & 30 deletions src/HTTPurple/Body.purs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ import Effect.Aff (Aff, makeAff, nonCanceler)
import Effect.Aff.Class (class MonadAff, liftAff)
import Effect.Class (liftEffect)
import Effect.Ref (Ref)
import Effect.Ref (modify, new, read, write) as Ref
import Effect.Ref (new, read, write) as Ref
import HTTPurple.Headers (RequestHeaders, mkRequestHeader)
import Node.Buffer (Buffer, concat, fromString, size)
import Node.Buffer (toString) as Buffer
import Node.Buffer (Buffer, fromString, size)
import Node.Buffer (concat, toString) as Buffer
import Node.Encoding (Encoding(UTF8))
import Node.HTTP (Request, Response, requestAsStream, responseAsStream)
import Node.Stream (Readable, Stream, end, onData, onEnd, pipe, writeString)
import Node.Stream (write) as Stream
import Node.EventEmitter (once_)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.OutgoingMessage as OM
import Node.HTTP.ServerResponse as SR
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Node.Stream (Readable, Stream, end', pipe, writeString')
import Node.Stream (endH, write') as Stream
import Node.Stream.Aff (readableToBuffers)
import Type.Equality (class TypeEquals, to)

type RequestBody =
Expand All @@ -35,13 +40,13 @@ type RequestBody =
}

-- | Read the body `Readable` stream out of the incoming request
read :: Request -> Effect RequestBody
read :: IncomingMessage IMServer -> Effect RequestBody
read request = do
buffer <- Ref.new Nothing
string <- Ref.new Nothing
pure
{ buffer
, stream: requestAsStream request
, stream: IM.toReadable request
, string
}

Expand Down Expand Up @@ -75,22 +80,12 @@ toBuffer requestBody = do
$ Ref.read requestBody.buffer
case maybeBuffer of
Nothing -> do
buffer <- streamToBuffer requestBody.stream
liftEffect
$ Ref.write (Just buffer) requestBody.buffer
pure buffer
buffers <- liftAff $ readableToBuffers requestBody.stream
liftEffect do
buffer <- Buffer.concat buffers
Ref.write (Just buffer) requestBody.buffer
pure buffer
Just buffer -> pure buffer
where
-- | Slurp the entire `Readable` stream into a `Buffer`
streamToBuffer :: MonadAff m => Readable () -> m Buffer
streamToBuffer stream =
liftAff $ makeAff \done -> do
bufs <- Ref.new []
onData stream \buf -> void $ Ref.modify (_ <> [ buf ]) bufs
onEnd stream do
body <- Ref.read bufs >>= concat
done $ Right body
pure nonCanceler

-- | Return the `Readable` stream directly from `RequestBody`
toStream :: RequestBody -> Readable ()
Expand All @@ -106,7 +101,7 @@ class Body b where
defaultHeaders :: b -> Effect RequestHeaders
-- | Given a body value and a Node HTTP `Response` value, write the body value
-- | to the Node response.
write :: b -> Response -> Aff Unit
write :: b -> ServerResponse -> Aff Unit

-- | The instance for `String` will convert the string to a buffer first in
-- | order to determine it's additional headers. This is to ensure that the
Expand All @@ -118,8 +113,8 @@ instance Body String where
buf :: Buffer <- fromString body UTF8
defaultHeaders buf
write body response = makeAff \done -> do
let stream = responseAsStream response
void $ writeString stream UTF8 body $ const $ end stream $ const $ done $ Right unit
let stream = OM.toWriteable $ SR.toOutgoingMessage response
void $ writeString' stream UTF8 body $ const $ end' stream $ const $ done $ Right unit
pure nonCanceler

-- | The instance for `Buffer` is trivial--we add a `Content-Length` header
Expand All @@ -128,8 +123,8 @@ instance Body String where
instance Body Buffer where
defaultHeaders buf = mkRequestHeader "Content-Length" <$> show <$> size buf
write body response = makeAff \done -> do
let stream = responseAsStream response
void $ Stream.write stream body $ const $ end stream $ const $ done $ Right unit
let stream = OM.toWriteable $ SR.toOutgoingMessage response
void $ Stream.write' stream body $ const $ end' stream $ const $ done $ Right unit
pure nonCanceler

-- | This instance can be used to send chunked data. Here, we add a
Expand All @@ -141,6 +136,6 @@ instance
defaultHeaders _ = pure $ mkRequestHeader "Transfer-Encoding" "chunked"
write body response = makeAff \done -> do
let stream = to body
void $ pipe stream $ responseAsStream response
onEnd stream $ done $ Right unit
void $ pipe stream $ OM.toWriteable $ SR.toOutgoingMessage response
stream # once_ Stream.endH (done $ Right unit)
pure nonCanceler
14 changes: 9 additions & 5 deletions src/HTTPurple/Headers.purs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import Data.Tuple (Tuple(Tuple))
import Effect (Effect)
import Foreign.Object (fold)
import HTTPurple.Lookup (class Lookup, (!!))
import Node.HTTP (Request, Response, requestHeaders, setHeaders)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.OutgoingMessage (setHeader')
import Node.HTTP.ServerResponse (toOutgoingMessage)
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Prim.Row as Row
import Prim.RowList (class RowToList, Cons, Nil)
import Record as Record
Expand Down Expand Up @@ -84,17 +87,18 @@ instance Eq ResponseHeaders where
eq (ResponseHeaders a) (ResponseHeaders b) = eq a b

-- | Get the headers out of a HTTP `RequestHeaders` object.
read :: Request -> RequestHeaders
read = requestHeaders >>> fold insertField Map.empty >>> RequestHeaders
read :: IncomingMessage IMServer -> RequestHeaders
read = IM.headers >>> fold insertField Map.empty >>> RequestHeaders
where
insertField x key value = insert (CaseInsensitiveString key) value x

-- | Given an HTTP `Response` and a `ResponseHeaders` object, return an effect that will
-- | write the `ResponseHeaders` to the `Response`.
write :: Response -> ResponseHeaders -> Effect Unit
write :: ServerResponse -> ResponseHeaders -> Effect Unit
write response (ResponseHeaders headers') = void $ traverseWithIndex writeField headers'
where
writeField key values = setHeaders response (unwrap key) values
om = toOutgoingMessage response
writeField key values = om # setHeader' (unwrap key) values

-- | Return a `ResponseHeaders` containing no headers.
empty :: ResponseHeaders
Expand Down
7 changes: 4 additions & 3 deletions src/HTTPurple/Method.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module HTTPurple.Method

import Prelude

import Node.HTTP (Request, requestMethod)
import Node.HTTP.IncomingMessage (method)
import Node.HTTP.Types (IMServer, IncomingMessage)

-- | These are the HTTP methods that HTTPurple understands.
data Method
Expand Down Expand Up @@ -35,8 +36,8 @@ instance showMethod :: Show Method where
show Patch = "Patch"

-- | Take an HTTP `Request` and extract the `Method` for that request.
read :: Request -> Method
read = requestMethod >>> case _ of
read :: IncomingMessage IMServer -> Method
read = method >>> case _ of
"POST" -> Post
"PUT" -> Put
"DELETE" -> Delete
Expand Down
6 changes: 3 additions & 3 deletions src/HTTPurple/NodeMiddleware.purs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ import Effect.Exception (Error)
import Effect.Ref as Ref
import Effect.Uncurried (EffectFn1, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn3)
import Literals.Undefined (Undefined, undefined)
import Node.HTTP as HTTP
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Prim.Row (class Union)
import Untagged.Union (type (|+|), UndefinedOr, asOneOf, uorToMaybe)

newtype NodeMiddleware :: forall k. k -> Type
newtype NodeMiddleware extended =
NodeMiddleware (EffectFn3 HTTP.Request HTTP.Response (EffectFn1 (UndefinedOr Error) Unit) (Effect Unit))
NodeMiddleware (EffectFn3 (IncomingMessage IMServer) ServerResponse (EffectFn1 (UndefinedOr Error) Unit) (Effect Unit))

derive instance Newtype (NodeMiddleware extended) _

data NextInvocation = NotCalled | ProcessingFailed Error | ProcessingSucceeded

type MiddlewareResultR =
(request :: HTTP.Request, response :: HTTP.Response, middlewareResult :: NextInvocation)
(request :: IncomingMessage IMServer, response :: ServerResponse, middlewareResult :: NextInvocation)

newtype MiddlewareResult :: forall k. k -> Type
newtype MiddlewareResult input = MiddlewareResult { | MiddlewareResultR }
Expand Down
7 changes: 4 additions & 3 deletions src/HTTPurple/Path.purs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Data.Array (filter, head)
import Data.Maybe (fromMaybe)
import Data.String (Pattern(Pattern), split)
import HTTPurple.Utils (urlDecode)
import Node.HTTP (Request, requestURL)
import Node.HTTP.IncomingMessage (url)
import Node.HTTP.Types (IMServer, IncomingMessage)

-- | The `Path` type is just sugar for an `Array` of `String` segments that are
-- | sent in a request and indicates the path of the resource being requested.
Expand All @@ -20,8 +21,8 @@ import Node.HTTP (Request, requestURL)
type Path = Array String

-- | Given an HTTP `Request` object, extract the `Path`.
read :: Request -> Path
read = requestURL >>> split' "?" >>> first >>> split' "/" >>> nonempty >>> map urlDecode
read :: IncomingMessage IMServer -> Path
read = url >>> split' "?" >>> first >>> split' "/" >>> nonempty >>> map urlDecode
where
nonempty = filter ((/=) "")
split' = Pattern >>> split
Expand Down
7 changes: 4 additions & 3 deletions src/HTTPurple/Query.purs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import Data.String (Pattern(Pattern), joinWith, split)
import Data.Tuple (Tuple(Tuple))
import Foreign.Object (Object, fromFoldable)
import HTTPurple.Utils (replacePlus, urlDecode)
import Node.HTTP (Request, requestURL)
import Node.HTTP.IncomingMessage (url)
import Node.HTTP.Types (IMServer, IncomingMessage)

-- | The `Query` type is a `Object` of `Strings`, with one entry per query
-- | parameter in the request. For any query parameters that don't have values
Expand All @@ -25,8 +26,8 @@ import Node.HTTP (Request, requestURL)
type Query = Object String

-- | The `Map` of query segments in the given HTTP `Request`.
read :: Request -> Query
read = requestURL >>> split' "?" >>> last >>> split' "&" >>> nonempty >>> toObject
read :: IncomingMessage IMServer -> Query
read = url >>> split' "?" >>> last >>> split' "&" >>> nonempty >>> toObject
where
toObject = map toTuple >>> fromFoldable
nonempty = filter ((/=) "")
Expand Down
16 changes: 8 additions & 8 deletions src/HTTPurple/Request.purs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import HTTPurple.Query (read) as Query
import HTTPurple.Utils (encodeURIComponent)
import HTTPurple.Version (Version)
import HTTPurple.Version (read) as Version
import Node.HTTP (Request) as HTTP
import Node.HTTP (requestURL)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.Types (IMServer, IncomingMessage)
import Prim.Row (class Nub, class Union)
import Prim.RowList (class RowToList)
import Record (merge)
Expand Down Expand Up @@ -81,7 +81,7 @@ fullPath { path: p, query } = "/" <> path <> questionMark <> queryParams
queryParamsArr = toArrayWithKey stringifyQueryParam query
stringifyQueryParam key value = encodeURIComponent key <> "=" <> encodeURIComponent value

mkRequest :: forall route m. MonadEffect m => HTTP.Request -> route -> m (Request route)
mkRequest :: forall route m. MonadEffect m => IncomingMessage IMServer -> route -> m (Request route)
mkRequest request route = do
body <- liftEffect $ Body.read request
pure
Expand All @@ -92,17 +92,17 @@ mkRequest request route = do
, headers: Headers.read request
, body
, httpVersion: Version.read request
, url: requestURL request
, url: IM.url request
}

-- | Given an HTTP `Request` object, this method will convert it to an HTTPurple
-- | `Request` object.
fromHTTPRequest :: forall route. RD.RouteDuplex' route -> HTTP.Request -> Aff (Either (Request Unit) (Request route))
fromHTTPRequest :: forall route. RD.RouteDuplex' route -> IncomingMessage IMServer -> Aff (Either (Request Unit) (Request route))
fromHTTPRequest route request = do
RD.parse route (requestURL request) #
RD.parse route (IM.url request) #
bitraverse (const $ mkRequest request unit) (mkRequest request)

fromHTTPRequestUnit :: HTTP.Request -> Aff (Request Unit)
fromHTTPRequestUnit :: IncomingMessage IMServer -> Aff (Request Unit)
fromHTTPRequestUnit = flip mkRequest unit

fromHTTPRequestExt ::
Expand All @@ -113,7 +113,7 @@ fromHTTPRequestExt ::
Keys ctx =>
RD.RouteDuplex' route ->
Proxy ctx ->
HTTP.Request ->
IncomingMessage IMServer ->
Aff (Either (Request Unit) (ExtRequestNT route ctx))
fromHTTPRequestExt route _ nodeRequest = do
let
Expand Down
6 changes: 3 additions & 3 deletions src/HTTPurple/Response.purs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ import HTTPurple.Headers (ResponseHeaders, empty, toResponseHeaders)
import HTTPurple.Headers (write) as Headers
import HTTPurple.Status (Status)
import HTTPurple.Status (accepted, alreadyReported, badGateway, badRequest, conflict, continue, created, expectationFailed, failedDependency, forbidden, found, gatewayTimeout, gone, hTTPVersionNotSupported, iMUsed, imATeapot, insufficientStorage, internalServerError, lengthRequired, locked, loopDetected, methodNotAllowed, misdirectedRequest, movedPermanently, multiStatus, multipleChoices, networkAuthenticationRequired, noContent, nonAuthoritativeInformation, notAcceptable, notExtended, notFound, notImplemented, notModified, ok, partialContent, payloadTooLarge, paymentRequired, permanentRedirect, preconditionFailed, preconditionRequired, processing, proxyAuthenticationRequired, rangeNotSatisfiable, requestHeaderFieldsTooLarge, requestTimeout, resetContent, seeOther, serviceUnavailable, switchingProtocols, temporaryRedirect, tooManyRequests, uRITooLong, unauthorized, unavailableForLegalReasons, unprocessableEntity, unsupportedMediaType, upgradeRequired, useProxy, variantAlsoNegotiates, write) as Status
import Node.HTTP (Response) as HTTP
import Node.HTTP.Types (ServerResponse)

-- | The `ResponseM` type simply conveniently wraps up an HTTPurple monad that
-- | returns a response. This type is the return type of all router/route
Expand All @@ -154,13 +154,13 @@ type ResponseM = Aff Response
type Response =
{ status :: Status
, headers :: ResponseHeaders
, writeBody :: HTTP.Response -> Aff Unit
, writeBody :: ServerResponse -> Aff Unit
}

-- | Given an HTTP `Response` and a HTTPurple `Response`, this method will return
-- | a monad encapsulating writing the HTTPurple `Response` to the HTTP `Response`
-- | and closing the HTTP `Response`.
send :: forall m. MonadEffect m => MonadAff m => HTTP.Response -> Response -> m Unit
send :: forall m. MonadEffect m => MonadAff m => ServerResponse -> Response -> m Unit
send httpresponse { status, headers, writeBody } = do
liftEffect $ Status.write httpresponse status
liftEffect $ Headers.write httpresponse headers
Expand Down
Loading

0 comments on commit 4667fee

Please sign in to comment.