Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create separate types for request and response headers #190

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/Examples/BinaryResponse/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ module Examples.BinaryResponse.Main where
import Prelude
import Effect.Console (log)
import Node.FS.Aff (readFile)
import HTTPure (ServerM, Request, ResponseM, Headers, serve, ok', header)
import HTTPure (ServerM, Request, ResponseM, ResponseHeaders, serve, ok', header)

-- | The path to the file containing the response to send
filePath :: String
filePath = "./docs/Examples/BinaryResponse/circle.png"

responseHeaders :: Headers
responseHeaders :: ResponseHeaders
responseHeaders = header "Content-Type" "image/png"

-- | Respond with image data when run
Expand Down
4 changes: 2 additions & 2 deletions docs/Examples/Headers/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module Examples.Headers.Main where

import Prelude
import Effect.Console (log)
import HTTPure (ServerM, Headers, Request, ResponseM, (!@), header, serve, ok')
import HTTPure (ServerM, ResponseHeaders, Request, ResponseM, (!@), header, serve, ok')

-- | The headers that will be included in every response.
responseHeaders :: Headers
responseHeaders :: ResponseHeaders
responseHeaders = header "X-Example" "hello world!"

-- | Route to the correct handler
Expand Down
6 changes: 4 additions & 2 deletions src/HTTPure.purs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module HTTPure
( module HTTPure.Body
, module HTTPure.Headers
, module HTTPure.RequestHeaders
, module HTTPure.ResponseHeaders
, module HTTPure.Lookup
, module HTTPure.Method
, module HTTPure.Path
Expand All @@ -12,12 +13,13 @@ module HTTPure
) where

import HTTPure.Body (toBuffer, toStream, toString)
import HTTPure.Headers (Headers, empty, header, headers)
import HTTPure.ResponseHeaders (ResponseHeaders, empty, header, headers)
import HTTPure.Lookup (at, (!@), has, (!?), lookup, (!!))
import HTTPure.Method (Method(..))
import HTTPure.Path (Path)
import HTTPure.Query (Query)
import HTTPure.Request (Request, fullPath)
import HTTPure.RequestHeaders (RequestHeaders, read)
import HTTPure.Response
( Response
, ResponseM
Expand Down
4 changes: 2 additions & 2 deletions src/HTTPure/Body.purs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Effect.Aff (Aff, makeAff, nonCanceler)
import Effect.Class (liftEffect)
import Effect.Ref (Ref)
import Effect.Ref (read, modify, new, write) as Ref
import HTTPure.Headers (Headers, header)
import HTTPure.ResponseHeaders (ResponseHeaders, header)
import Node.Buffer (Buffer, concat, fromString, size)
import Node.Buffer (toString) as Buffer
import Node.Encoding (Encoding(UTF8))
Expand Down Expand Up @@ -101,7 +101,7 @@ class Body b where
-- | things like `Content-Type`, `Content-Length`, and `Transfer-Encoding`.
-- | Note that any headers passed in a response helper such as `ok'` will take
-- | precedence over these.
defaultHeaders :: b -> Effect Headers
defaultHeaders :: b -> Effect ResponseHeaders
-- | Given a body value and a Node HTTP `Response` value, write the body value
-- | to the Node response.
write :: b -> Response -> Aff Unit
Expand Down
75 changes: 0 additions & 75 deletions src/HTTPure/Headers.purs

This file was deleted.

6 changes: 3 additions & 3 deletions src/HTTPure/Request.purs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import Effect.Class (liftEffect)
import Foreign.Object (isEmpty, toArrayWithKey)
import HTTPure.Body (RequestBody)
import HTTPure.Body (read) as Body
import HTTPure.Headers (Headers)
import HTTPure.Headers (read) as Headers
import HTTPure.RequestHeaders (RequestHeaders)
import HTTPure.RequestHeaders (read) as Headers
import HTTPure.Method (Method)
import HTTPure.Method (read) as Method
import HTTPure.Path (Path)
Expand All @@ -31,7 +31,7 @@ type Request =
{ method :: Method
, path :: Path
, query :: Query
, headers :: Headers
, headers :: RequestHeaders
, body :: RequestBody
, httpVersion :: Version
, url :: String
Expand Down
48 changes: 48 additions & 0 deletions src/HTTPure/RequestHeaders.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module HTTPure.RequestHeaders
( RequestHeaders(..)
, empty
, read
) where

import Prelude

import Data.FoldableWithIndex (foldMapWithIndex)
import Data.Newtype (class Newtype)
import Data.String as String
import Foreign.Object (Object, union)
import Foreign.Object as Object
import HTTPure.Lookup (class Lookup, (!!))
import Node.HTTP (Request, requestHeaders)

-- | The `RequestHeaders` type is just sugar for a `Object` of `Strings`
-- | that represents the set of headers in an HTTP request.
newtype RequestHeaders = RequestHeaders (Object String)

derive instance newtypeRequestHeaders :: Newtype RequestHeaders _

-- | Given a string. return a `Maybe` containing the value of the matching
-- | request header, if there is any.
instance lookupRequestHeaders :: Lookup RequestHeaders String String where
lookup (RequestHeaders headers') key = headers' !! (String.toLower key)

-- | Allow a `RequestHeaders` to be represented as a string. This string is
-- | formatted in HTTP headers format.
instance showRequestHeaders :: Show RequestHeaders where
show (RequestHeaders headers') = foldMapWithIndex showField headers' <> "\n"
where
showField key value = key <> ": " <> value <> "\n"
arthurxavierx marked this conversation as resolved.
Show resolved Hide resolved

-- | Compare two `RequestHeaders` objects by comparing the underlying `Objects`.
instance eqRequestHeaders :: Eq RequestHeaders where
eq (RequestHeaders a) (RequestHeaders b) = eq a b
arthurxavierx marked this conversation as resolved.
Show resolved Hide resolved

-- | Allow one `RequestHeaders` objects to be appended to another.
instance semigroupRequestHeaders :: Semigroup RequestHeaders where
append (RequestHeaders a) (RequestHeaders b) = RequestHeaders $ union b a

-- | Get the request headers out of a HTTP `Request` object.
read :: Request -> RequestHeaders
read = requestHeaders >>> RequestHeaders
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we're missing a way to access array-like headers like set-cookie here. Should we also have a read' that returns maybe a RequestHeaders' (Object (Array String))?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that we just can't know which headers are array-like.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can with FFI. That of course requires us to assume a JS backend, but we're already using Node bindings e.g. for setHeaders, so I think we've already made that assumption.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have a function that returns Either String (Array String) for the headers, I guess, is that what you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we have read :: Request -> Object String, we could also have read' :: Request -> Object (Array String). Or we could have a more descriptive name like readArray :: Request -> Object (Array String).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, either change is breaking, it's just breaking in different directions (

Basically, instead of splitting Headers into RequestHeaders and ResponseHeaders, we'd split it into StringHeaders and ArrayHeaders

).

If we go with the direction of this PR, it probably wouldn't be worth it to adopt my suggestion later. It's more a one or the other choice IMO.

But like I said, I don't feel strongly here. It seems a more flexible API, but maybe there's no point in supporting array headers for requests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it though, my suggestion might not require a breaking change. We just add a new ArrayHeaders type to HTTPure.Headers, and the supporting functions. Or even add a new module for HTTPure.ArrayHeaders (again, not set on the name, maybe MultipleHeaders or HeadersCollection, etc.), and then the current headers continue to work as-is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One major problem with this approach is that we'd also have to create HTTPure.Request.RequestArray and HTTPure.Response.ResponseArray variants, as currently HTTPure.Request.Request and HTTPure.Request.Response both have a headers :: HTTPure.Headers.Headers field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would just extend them to contain both (I'm now thinking the name could be MultiHeaders instead of ArrayHeaders):

  type Request =
    { method :: Method
    , path :: Path
    , query :: Query
    , headers :: Headers
+   , multiHeaders :: MultiHeaders
    , httpVersion :: Version
    , url :: String
    }
  type Response =
    { status :: Status
    , headers :: Headers
+   , multiHeaders :: MultiHeaders
    , writeBody :: HTTP.Response -> Aff Unit
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I hadn't considered that option. Looks like it should work. Thanks!


empty :: RequestHeaders
empty = RequestHeaders Object.empty
Loading