wip2 typed headers
This commit is contained in:
parent
96eacc3ba0
commit
7f5c022356
@ -2,70 +2,70 @@
|
||||
[
|
||||
"Axon Request Parts Body extracts a JSON body",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Body extracts a string body from a buffer",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Body extracts a string body from a cached string",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Body extracts a string body from a readable stream",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path ... but does if ends in IgnoreRest",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path does not partially match a route ...",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path extracts an int",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path extracts an int and a string",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path matches a route matching literal",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts Path matches a route matching multiple literals",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
@ -97,6 +97,13 @@
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts extracts header, method, path, JSON body",
|
||||
{
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"Axon Request Parts extracts method, path, JSON body",
|
||||
{
|
||||
@ -107,7 +114,7 @@
|
||||
[
|
||||
"Axon Request Parts extracts the whole request",
|
||||
{
|
||||
"timestamp": "1733095274452.0",
|
||||
"timestamp": "1733176427760.0",
|
||||
"success": true
|
||||
}
|
||||
],
|
||||
|
@ -1,211 +0,0 @@
|
||||
module Axon.Request (Request, Body(..), BodyReadableError(..), BodyStringError(..), BodyJSONError(..), BodyBufferError(..), bodyReadable, bodyString, bodyJSON, bodyBuffer, headers, method, address, url, contentType, accept, contentLength, lookupHeader, make) where
|
||||
|
||||
import Prelude
|
||||
|
||||
import Axon.Request.Method (Method)
|
||||
import Control.Monad.Error.Class (throwError, try)
|
||||
import Control.Monad.Except (ExceptT(..), runExceptT)
|
||||
import Control.Monad.Trans.Class (lift)
|
||||
import Data.Argonaut.Core (Json)
|
||||
import Data.Argonaut.Core (stringify) as JSON
|
||||
import Data.Argonaut.Parser (jsonParser) as JSON
|
||||
import Data.Bifunctor (lmap)
|
||||
import Data.Either (Either)
|
||||
import Data.FoldableWithIndex (foldlWithIndex)
|
||||
import Data.Generic.Rep (class Generic)
|
||||
import Data.Int as Int
|
||||
import Data.MIME (MIME)
|
||||
import Data.MIME as MIME
|
||||
import Data.Map (Map)
|
||||
import Data.Map as Map
|
||||
import Data.Maybe (Maybe)
|
||||
import Data.Show.Generic (genericShow)
|
||||
import Data.String.Lower (StringLower)
|
||||
import Data.String.Lower as String.Lower
|
||||
import Data.URL (URL)
|
||||
import Effect (Effect)
|
||||
import Effect.Aff (Aff)
|
||||
import Effect.Aff.Class (liftAff)
|
||||
import Effect.Class (liftEffect)
|
||||
import Effect.Exception (Error)
|
||||
import Effect.Exception as Error
|
||||
import Effect.Ref (Ref) as Effect
|
||||
import Effect.Ref as Ref
|
||||
import Node.Buffer (Buffer)
|
||||
import Node.Buffer as Buffer
|
||||
import Node.Encoding (Encoding(..))
|
||||
import Node.Net.Types (IPv4, IPv6, SocketAddress)
|
||||
import Node.Stream as Stream
|
||||
import Node.Stream.Aff as Stream.Aff
|
||||
|
||||
data BodyReadableError
|
||||
= BodyReadableErrorHasBeenConsumed
|
||||
| BodyReadableErrorEmpty
|
||||
|
||||
derive instance Generic BodyReadableError _
|
||||
derive instance Eq BodyReadableError
|
||||
instance Show BodyReadableError where
|
||||
show = genericShow
|
||||
|
||||
data BodyBufferError
|
||||
= BodyBufferErrorReadable BodyReadableError
|
||||
| BodyBufferErrorReading Error
|
||||
|
||||
derive instance Generic BodyBufferError _
|
||||
instance Eq BodyBufferError where
|
||||
eq (BodyBufferErrorReadable a) (BodyBufferErrorReadable b) = a == b
|
||||
eq (BodyBufferErrorReading a) (BodyBufferErrorReading b) = Error.message a == Error.message b
|
||||
eq _ _ = false
|
||||
instance Show BodyBufferError where
|
||||
show = genericShow
|
||||
|
||||
data BodyStringError
|
||||
= BodyStringErrorBuffer BodyBufferError
|
||||
| BodyStringErrorNotUTF8
|
||||
|
||||
derive instance Generic BodyStringError _
|
||||
derive instance Eq BodyStringError
|
||||
instance Show BodyStringError where
|
||||
show = genericShow
|
||||
|
||||
data BodyJSONError
|
||||
= BodyJSONErrorString BodyStringError
|
||||
| BodyJSONErrorParsing String
|
||||
|
||||
derive instance Generic BodyJSONError _
|
||||
derive instance Eq BodyJSONError
|
||||
instance Show BodyJSONError where
|
||||
show = genericShow
|
||||
|
||||
data Body
|
||||
= BodyEmpty
|
||||
| BodyReadable (Stream.Readable ())
|
||||
| BodyReadableConsumed
|
||||
| BodyCached Buffer
|
||||
| BodyCachedString String
|
||||
| BodyCachedJSON Json
|
||||
|
||||
data Request =
|
||||
Request
|
||||
{ headers :: Map StringLower String
|
||||
, address :: Either (SocketAddress IPv4) (SocketAddress IPv6)
|
||||
, url :: URL
|
||||
, method :: Method
|
||||
, bodyRef :: Effect.Ref Body
|
||||
}
|
||||
|
||||
make :: { headers :: Map String String
|
||||
, address :: Either (SocketAddress IPv4) (SocketAddress IPv6)
|
||||
, url :: URL
|
||||
, method :: Method
|
||||
, body :: Body
|
||||
} -> Effect Request
|
||||
make a = do
|
||||
bodyRef <- Ref.new a.body
|
||||
pure $ Request {bodyRef: bodyRef, headers: foldlWithIndex (\k m v -> Map.insert (String.Lower.fromString k) v m) Map.empty a.headers, address: a.address, url: a.url, method: a.method}
|
||||
|
||||
headers :: Request -> Map StringLower String
|
||||
headers (Request a) = a.headers
|
||||
|
||||
lookupHeader :: String -> Request -> Maybe String
|
||||
lookupHeader k (Request a) = Map.lookup (String.Lower.fromString k) a.headers
|
||||
|
||||
contentType :: Request -> Maybe MIME
|
||||
contentType = lookupHeader "content-type" >>> map MIME.fromString
|
||||
|
||||
accept :: Request -> Maybe MIME
|
||||
accept = lookupHeader "accept" >>> map MIME.fromString
|
||||
|
||||
contentLength :: Request -> Maybe Int
|
||||
contentLength = lookupHeader "content-length" >=> Int.fromString
|
||||
|
||||
method :: Request -> Method
|
||||
method (Request a) = a.method
|
||||
|
||||
address :: Request -> Either (SocketAddress IPv4) (SocketAddress IPv6)
|
||||
address (Request a) = a.address
|
||||
|
||||
url :: Request -> URL
|
||||
url (Request a) = a.url
|
||||
|
||||
bodyReadable :: Request -> Effect (Either BodyReadableError (Stream.Readable ()))
|
||||
bodyReadable (Request {bodyRef}) = runExceptT do
|
||||
body <- liftEffect $ Ref.read bodyRef
|
||||
case body of
|
||||
BodyEmpty -> throwError BodyReadableErrorEmpty
|
||||
BodyReadableConsumed -> throwError BodyReadableErrorHasBeenConsumed
|
||||
BodyReadable r ->
|
||||
Ref.write BodyReadableConsumed bodyRef $> r # lift
|
||||
BodyCached buf -> Stream.readableFromBuffer buf # lift
|
||||
BodyCachedString str -> Stream.readableFromString str UTF8 # lift
|
||||
BodyCachedJSON json -> json # JSON.stringify # flip Buffer.fromString UTF8 >>= Stream.readableFromBuffer # lift
|
||||
|
||||
bodyBuffer :: Request -> Aff (Either BodyBufferError Buffer)
|
||||
bodyBuffer r@(Request {bodyRef}) =
|
||||
let
|
||||
stream =
|
||||
bodyReadable r
|
||||
# liftEffect
|
||||
<#> lmap BodyBufferErrorReadable
|
||||
# ExceptT
|
||||
readAll s =
|
||||
Stream.Aff.readAll s
|
||||
# liftAff
|
||||
# try
|
||||
<#> lmap BodyBufferErrorReading
|
||||
# ExceptT
|
||||
>>= (liftEffect <<< Buffer.concat)
|
||||
in
|
||||
runExceptT do
|
||||
body <- Ref.read bodyRef # liftEffect
|
||||
case body of
|
||||
BodyCached buf -> pure buf
|
||||
BodyCachedString str -> Buffer.fromString str UTF8 # liftEffect
|
||||
BodyCachedJSON json -> Buffer.fromString (JSON.stringify json) UTF8 # liftEffect
|
||||
_ -> do
|
||||
buf <- stream >>= readAll
|
||||
Ref.write (BodyCached buf) bodyRef $> buf # liftEffect
|
||||
|
||||
bodyString :: Request -> Aff (Either BodyStringError String)
|
||||
bodyString r@(Request {bodyRef}) =
|
||||
let
|
||||
buf =
|
||||
bodyBuffer r
|
||||
<#> lmap BodyStringErrorBuffer
|
||||
# ExceptT
|
||||
bufString b =
|
||||
Buffer.toString UTF8 b
|
||||
# liftEffect
|
||||
# try
|
||||
<#> lmap (const BodyStringErrorNotUTF8)
|
||||
# ExceptT
|
||||
in
|
||||
runExceptT do
|
||||
body <- Ref.read bodyRef # liftEffect
|
||||
case body of
|
||||
BodyCachedString str -> pure str
|
||||
BodyCachedJSON json -> JSON.stringify json # pure
|
||||
_ -> do
|
||||
str <- buf >>= bufString
|
||||
Ref.write (BodyCachedString str) bodyRef $> str # liftEffect
|
||||
|
||||
bodyJSON :: Request -> Aff (Either BodyJSONError Json)
|
||||
bodyJSON r@(Request {bodyRef}) =
|
||||
let
|
||||
str =
|
||||
bodyString r
|
||||
<#> lmap BodyJSONErrorString
|
||||
# ExceptT
|
||||
parse s =
|
||||
JSON.jsonParser s
|
||||
# lmap BodyJSONErrorParsing
|
||||
# pure
|
||||
# ExceptT
|
||||
in
|
||||
runExceptT do
|
||||
body <- Ref.read bodyRef # liftEffect
|
||||
case body of
|
||||
BodyCachedJSON j -> pure j
|
||||
_ -> do
|
||||
j <- str >>= parse
|
||||
Ref.write (BodyCachedJSON j) bodyRef $> j # liftEffect
|
@ -9,21 +9,28 @@ import Data.Array as Array
|
||||
import Data.Array.NonEmpty (NonEmptyArray)
|
||||
import Data.Array.NonEmpty as Array.NonEmpty
|
||||
import Data.Bifunctor (lmap)
|
||||
import Data.DateTime (DateTime)
|
||||
import Data.Date as Date
|
||||
import Data.Date.Component (Month(..), Weekday(..))
|
||||
import Data.DateTime (DateTime(..))
|
||||
import Data.DateTime as DateTime
|
||||
import Data.Either (Either(..))
|
||||
import Data.Either.Nested (type (\/))
|
||||
import Data.Either.Nested as Either.Nested
|
||||
import Data.Enum (fromEnum, toEnum)
|
||||
import Data.Generic.Rep (class Generic)
|
||||
import Data.Int as Int
|
||||
import Data.MIME as MIME
|
||||
import Data.Map as Map
|
||||
import Data.Maybe (Maybe(..), fromMaybe, isJust)
|
||||
import Data.Maybe (Maybe(..), isJust)
|
||||
import Data.Newtype (class Newtype, unwrap, wrap)
|
||||
import Data.Show.Generic (genericShow)
|
||||
import Data.String as String
|
||||
import Data.String.Base64 as String.Base64
|
||||
import Data.String.CodeUnits as String.CodeUnit
|
||||
import Data.String.Lower (StringLower)
|
||||
import Data.String.Lower as String.Lower
|
||||
import Data.String.Regex.Flags as Regex.Flags
|
||||
import Data.Time as Time
|
||||
import Data.Tuple (fst)
|
||||
import Data.Tuple.Nested (type (/\), (/\))
|
||||
import Effect.Exception as Error
|
||||
@ -38,6 +45,76 @@ import Record as Record
|
||||
import Type.MIME as Type.MIME
|
||||
|
||||
data Wildcard = Wildcard
|
||||
derive instance Eq Wildcard
|
||||
|
||||
datetimeParser :: Parser String DateTime
|
||||
datetimeParser =
|
||||
let
|
||||
as :: forall a. String -> a -> Parser String a
|
||||
as s a = Parse.String.string s $> a
|
||||
|
||||
weekday = Parse.Combine.choice [as "Mon" Monday, as "Tue" Tuesday, as "Wed" Wednesday, as "Thu" Thursday, as "Fri" Friday, as "Sat" Saturday, as "Sun" Sunday]
|
||||
month = Parse.Combine.choice [as "Jan" January, as "Feb" February, as "Mar" March, as "Apr" April, as "May" May, as "Jun" June, as "Jul" July, as "Aug" August, as "Sep" September, as "Oct" October, as "Nov" November, as "Dec" December]
|
||||
day = Parse.String.Basic.intDecimal <#> toEnum >>= Parse.liftMaybe (const "invalid day")
|
||||
year = Parse.String.Basic.intDecimal <#> toEnum >>= Parse.liftMaybe (const "invalid year")
|
||||
date =
|
||||
( pure (\d m y -> Date.exactDate y m d)
|
||||
<*> (weekday *> Parse.String.Basic.whiteSpace *> day)
|
||||
<*> (Parse.String.Basic.whiteSpace *> month) <*> (Parse.String.Basic.whiteSpace *> year)
|
||||
)
|
||||
>>= Parse.liftMaybe (const "invalid date")
|
||||
|
||||
time =
|
||||
( pure (\h m s ms -> Time.Time h m s ms)
|
||||
<*> ((Parse.String.Basic.intDecimal <#> toEnum >>= Parse.liftMaybe (const "invalid hour")))
|
||||
<*> (Parse.String.string ":" *> (Parse.String.Basic.intDecimal <#> toEnum >>= Parse.liftMaybe (const "invalid minutes")))
|
||||
<*> (Parse.String.string ":" *> (Parse.String.Basic.intDecimal <#> toEnum >>= Parse.liftMaybe (const "invalid seconds")))
|
||||
<*> (toEnum 0 # Parse.liftMaybe (const "invalid milliseconds"))
|
||||
)
|
||||
in
|
||||
pure DateTime <*> (date <* Parse.String.Basic.whiteSpace) <*> time
|
||||
|
||||
|
||||
printDateTime :: DateTime -> String
|
||||
printDateTime dt =
|
||||
let
|
||||
weekday = case _ of
|
||||
Monday -> "Mon"
|
||||
Tuesday -> "Tue"
|
||||
Wednesday -> "Wed"
|
||||
Thursday -> "Thu"
|
||||
Friday -> "Fri"
|
||||
Saturday -> "Sat"
|
||||
Sunday -> "Sun"
|
||||
month =
|
||||
case _ of
|
||||
January -> "Jan"
|
||||
February -> "Feb"
|
||||
March -> "Mar"
|
||||
April -> "Apr"
|
||||
May -> "May"
|
||||
June -> "Jun"
|
||||
July -> "Jul"
|
||||
August -> "Aug"
|
||||
September -> "Sep"
|
||||
October -> "Oct"
|
||||
November -> "Nov"
|
||||
December -> "Dec"
|
||||
|
||||
time =
|
||||
[ dt # DateTime.time # DateTime.hour # fromEnum # Int.toStringAs Int.decimal
|
||||
, dt # DateTime.time # DateTime.minute # fromEnum # Int.toStringAs Int.decimal
|
||||
, dt # DateTime.time # DateTime.second # fromEnum # Int.toStringAs Int.decimal
|
||||
]
|
||||
# Array.intercalate ":"
|
||||
in
|
||||
[ weekday (DateTime.weekday $ DateTime.date dt) <> ","
|
||||
, dt # DateTime.date # DateTime.day # fromEnum # Int.toStringAs Int.decimal
|
||||
, dt # DateTime.date # DateTime.month # month
|
||||
, dt # DateTime.date # DateTime.year # fromEnum # Int.toStringAs Int.decimal
|
||||
, time
|
||||
]
|
||||
# Array.intercalate " "
|
||||
|
||||
commas :: forall a. Parser String a -> Parser String (Array a)
|
||||
commas p = Parse.Combine.sepBy p (Parse.String.Basic.whiteSpace <* Parse.String.string "," <* Parse.String.Basic.whiteSpace) <#> Array.fromFoldable
|
||||
@ -49,18 +126,21 @@ wildcardParser :: Parser String Wildcard
|
||||
wildcardParser = Parse.String.string "*" $> Wildcard
|
||||
|
||||
mimeParser :: Parser String MIME.MIME
|
||||
mimeParser = Parse.String.anyTill Parse.String.Basic.space <#> fst <#> MIME.fromString
|
||||
mimeParser = Parse.String.anyTill (void Parse.String.Basic.space <|> Parse.String.eof) <#> fst <#> MIME.fromString
|
||||
|
||||
headerNameRegexParser :: Parser String String
|
||||
headerNameRegexParser = unsafePartial $ (\(Right a) -> a) $ Parse.String.regex "[\\w-]+" Regex.Flags.noFlags
|
||||
|
||||
closeRegexParser :: Parser String String
|
||||
closeRegexParser = unsafePartial $ (\(Right a) -> a) $ Parse.String.regex "close" Regex.Flags.ignoreCase
|
||||
|
||||
headerNameParser :: Parser String StringLower
|
||||
headerNameParser = headerNameRegexParser <#> String.Lower.fromString
|
||||
|
||||
methodParser :: Parser String Method
|
||||
methodParser = Parse.Combine.many Parse.String.Basic.alphaNum <#> Array.fromFoldable <#> String.CodeUnit.fromCharArray >>= (\a -> Parse.liftMaybe (const $ "invalid method " <> a) $ Method.fromString a)
|
||||
|
||||
directiveParser :: Parser String (String /\ Maybe String)
|
||||
directiveParser :: Parser String (StringLower /\ Maybe String)
|
||||
directiveParser =
|
||||
let
|
||||
boundary = Parse.String.string ";" <|> Parse.String.string "," <|> (Parse.String.eof *> pure "")
|
||||
@ -68,8 +148,8 @@ directiveParser =
|
||||
k /\ stop <- Parse.String.anyTill (Parse.String.string "=" <|> boundary)
|
||||
when (stop /= "=") $ Parse.fail ""
|
||||
v <- Parse.String.anyTill boundary <#> fst
|
||||
pure $ String.trim k /\ Just (String.trim v)
|
||||
kParser = Parse.String.anyTill boundary <#> fst <#> String.trim <#> (\k -> k /\ Nothing)
|
||||
pure $ String.Lower.fromString (String.trim k) /\ Just (String.trim v)
|
||||
kParser = Parse.String.anyTill boundary <#> fst <#> String.trim <#> String.Lower.fromString <#> (\k -> k /\ Nothing)
|
||||
in
|
||||
kvParser <|> kParser
|
||||
|
||||
@ -79,56 +159,74 @@ class TypedHeader a where
|
||||
headerValueEncode :: a -> String
|
||||
|
||||
newtype Accept a = Accept a
|
||||
derive instance Generic (Accept a) _
|
||||
derive instance Newtype (Accept a) _
|
||||
derive instance Eq a => Eq (Accept a)
|
||||
instance Show a => Show (Accept a) where show = genericShow
|
||||
|
||||
data AccessControlAllowCredentials = AccessControlAllowCredentials
|
||||
|
||||
newtype AccessControlAllowHeaders = AccessControlAllowHeaders (Wildcard \/ NonEmptyArray StringLower)
|
||||
derive instance Newtype (AccessControlAllowHeaders) _
|
||||
derive instance Eq (AccessControlAllowHeaders)
|
||||
|
||||
newtype AccessControlAllowMethods = AccessControlAllowMethods (Wildcard \/ NonEmptyArray Method)
|
||||
derive instance Newtype (AccessControlAllowMethods) _
|
||||
derive instance Eq (AccessControlAllowMethods)
|
||||
|
||||
newtype AccessControlAllowOrigin = AccessControlAllowOrigin (Wildcard \/ String)
|
||||
derive instance Newtype (AccessControlAllowOrigin) _
|
||||
derive instance Eq (AccessControlAllowOrigin)
|
||||
|
||||
newtype AccessControlExposeHeaders = AccessControlExposeHeaders (Wildcard \/ Array StringLower)
|
||||
derive instance Newtype (AccessControlExposeHeaders) _
|
||||
derive instance Eq (AccessControlExposeHeaders)
|
||||
|
||||
newtype AccessControlMaxAge = AccessControlMaxAge Int
|
||||
derive instance Newtype (AccessControlMaxAge) _
|
||||
derive instance Eq (AccessControlMaxAge)
|
||||
|
||||
newtype AccessControlRequestHeaders = AccessControlRequestHeaders (NonEmptyArray StringLower)
|
||||
derive instance Newtype (AccessControlRequestHeaders) _
|
||||
derive instance Eq (AccessControlRequestHeaders)
|
||||
|
||||
newtype AccessControlRequestMethod = AccessControlRequestMethod Method
|
||||
derive instance Newtype (AccessControlRequestMethod) _
|
||||
derive instance Eq (AccessControlRequestMethod)
|
||||
|
||||
newtype Age = Age Int
|
||||
derive instance Newtype (Age) _
|
||||
derive instance Eq (Age)
|
||||
|
||||
newtype Allow = Allow (NonEmptyArray Method)
|
||||
derive instance Newtype (Allow) _
|
||||
derive instance Eq (Allow)
|
||||
|
||||
newtype AuthScheme = AuthScheme String
|
||||
derive instance Newtype (AuthScheme) _
|
||||
derive instance Eq (AuthScheme)
|
||||
|
||||
data Authorization = Authorization AuthScheme String
|
||||
|
||||
newtype BearerAuth = BearerAuth String
|
||||
derive instance Newtype (BearerAuth) _
|
||||
derive instance Eq (BearerAuth)
|
||||
|
||||
newtype BasicAuth = BasicAuth {username :: String, password :: String}
|
||||
derive instance Newtype (BasicAuth) _
|
||||
derive instance Eq (BasicAuth)
|
||||
|
||||
newtype ByteRangeStart = ByteRangeStart Int
|
||||
derive instance Newtype (ByteRangeStart) _
|
||||
derive instance Eq (ByteRangeStart)
|
||||
|
||||
newtype ByteRangeEnd = ByteRangeEnd Int
|
||||
derive instance Newtype (ByteRangeEnd) _
|
||||
derive instance Eq (ByteRangeEnd)
|
||||
|
||||
newtype ByteRangeLength = ByteRangeLength Int
|
||||
derive instance Newtype (ByteRangeLength) _
|
||||
derive instance Eq (ByteRangeLength)
|
||||
|
||||
type CacheControl' =
|
||||
( maxAge :: Maybe Int
|
||||
@ -151,76 +249,104 @@ type CacheControl' =
|
||||
|
||||
newtype CacheControl = CacheControl (Record CacheControl')
|
||||
derive instance Newtype (CacheControl) _
|
||||
derive instance Eq (CacheControl)
|
||||
|
||||
data CloseConnection = CloseConnection
|
||||
data ConnectionClose = ConnectionClose
|
||||
derive instance Eq (ConnectionClose)
|
||||
|
||||
newtype Connection = Connection (CloseConnection \/ NonEmptyArray StringLower)
|
||||
newtype Connection = Connection (ConnectionClose \/ NonEmptyArray StringLower)
|
||||
derive instance Newtype (Connection) _
|
||||
derive instance Eq (Connection)
|
||||
|
||||
newtype ContentDisposition = ContentDisposition (ContentDispositionInline \/ ContentDispositionAttachment \/ ContentDispositionFormData)
|
||||
newtype ContentDisposition = ContentDisposition (ContentDispositionInline \/ ContentDispositionAttachment \/ ContentDispositionFormData \/ Void)
|
||||
derive instance Newtype (ContentDisposition) _
|
||||
derive instance Eq (ContentDisposition)
|
||||
|
||||
data ContentDispositionInline = ContentDispositionInline
|
||||
derive instance Eq (ContentDispositionInline)
|
||||
|
||||
newtype ContentDispositionAttachment = ContentDispositionAttachment {filename :: Maybe {language :: Maybe String, encoding :: Maybe String, value :: String}}
|
||||
newtype ContentDispositionAttachment = ContentDispositionAttachment {filename :: Maybe String}
|
||||
derive instance Newtype (ContentDispositionAttachment) _
|
||||
derive instance Eq (ContentDispositionAttachment)
|
||||
|
||||
newtype ContentDispositionFormData = ContentDispositionFormData {filename :: Maybe String, name :: Maybe String}
|
||||
derive instance Newtype (ContentDispositionFormData) _
|
||||
derive instance Eq (ContentDispositionFormData)
|
||||
|
||||
newtype ContentEncoding = ContentEncoding (NonEmptyArray String)
|
||||
derive instance Newtype (ContentEncoding) _
|
||||
derive instance Eq (ContentEncoding)
|
||||
|
||||
newtype ContentLength = ContentLength Int
|
||||
derive instance Newtype (ContentLength) _
|
||||
derive instance Eq (ContentLength)
|
||||
|
||||
newtype ContentLocation = ContentLocation String
|
||||
derive instance Newtype (ContentLocation) _
|
||||
derive instance Eq (ContentLocation)
|
||||
|
||||
newtype ContentRange = ContentRange ((ByteRangeStart /\ ByteRangeEnd /\ ByteRangeLength) \/ (ByteRangeStart /\ ByteRangeEnd) \/ ByteRangeLength)
|
||||
newtype ContentRange = ContentRange ((ByteRangeStart /\ ByteRangeEnd /\ ByteRangeLength) \/ (ByteRangeStart /\ ByteRangeEnd) \/ ByteRangeLength \/ Void)
|
||||
derive instance Newtype (ContentRange) _
|
||||
derive instance Eq (ContentRange)
|
||||
|
||||
newtype ContentType a = ContentType a
|
||||
derive instance Generic (ContentType a) _
|
||||
derive instance Newtype (ContentType a) _
|
||||
derive instance Eq a => Eq (ContentType a)
|
||||
instance Show a => Show (ContentType a) where show = genericShow
|
||||
|
||||
newtype Cookie = Cookie String
|
||||
derive instance Newtype (Cookie) _
|
||||
derive instance Eq (Cookie)
|
||||
|
||||
newtype Date = Date DateTime
|
||||
derive instance Newtype (Date) _
|
||||
derive instance Eq (Date)
|
||||
|
||||
newtype ETag = ETag String
|
||||
derive instance Newtype (ETag) _
|
||||
derive instance Eq (ETag)
|
||||
|
||||
data ExpectContinue = ExpectContinue
|
||||
|
||||
newtype Expires = Expires DateTime
|
||||
derive instance Newtype (Expires) _
|
||||
derive instance Eq (Expires)
|
||||
|
||||
newtype Host = Host String
|
||||
derive instance Newtype (Host) _
|
||||
derive instance Eq (Host)
|
||||
|
||||
newtype IfMatch = IfMatch (Wildcard \/ NonEmptyArray MatchETag)
|
||||
newtype IfMatch = IfMatch (Wildcard \/ NonEmptyArray String)
|
||||
derive instance Newtype (IfMatch) _
|
||||
derive instance Eq (IfMatch)
|
||||
|
||||
newtype IfNoneMatch = IfNoneMatch (Wildcard \/ NonEmptyArray MatchETag)
|
||||
derive instance Newtype (IfNoneMatch) _
|
||||
derive instance Eq (IfNoneMatch)
|
||||
|
||||
newtype IfModifiedSince = IfModifiedSince DateTime
|
||||
derive instance Newtype (IfModifiedSince) _
|
||||
derive instance Eq (IfModifiedSince)
|
||||
|
||||
newtype IfRange = IfRange (DateTime \/ String)
|
||||
derive instance Newtype (IfRange) _
|
||||
derive instance Eq (IfRange)
|
||||
|
||||
newtype IfUnmodifiedSince = IfUnmodifiedSince DateTime
|
||||
derive instance Newtype (IfUnmodifiedSince) _
|
||||
derive instance Eq (IfUnmodifiedSince)
|
||||
|
||||
newtype LastModified = LastModified DateTime
|
||||
derive instance Newtype (LastModified) _
|
||||
derive instance Eq (LastModified)
|
||||
|
||||
data MatchETag = MatchETag String | MatchETagWeak String
|
||||
derive instance Eq MatchETag
|
||||
|
||||
newtype Origin = Origin String
|
||||
derive instance Newtype (Origin) _
|
||||
derive instance Eq (Origin)
|
||||
|
||||
data ProxyAuthorization = ProxyAuthorization AuthScheme String
|
||||
|
||||
@ -228,9 +354,11 @@ type RangeSpecifier = ByteRangeStart \/ (ByteRangeStart /\ ByteRangeEnd) \/ Byte
|
||||
|
||||
newtype Range = Range (RangeSpecifier \/ Array RangeSpecifier)
|
||||
derive instance Newtype (Range) _
|
||||
derive instance Eq (Range)
|
||||
|
||||
newtype Referer = Referer String
|
||||
derive instance Newtype (Referer) _
|
||||
derive instance Eq (Referer)
|
||||
|
||||
data ReferrerPolicy
|
||||
= ReferrerPolicyNoReferrer
|
||||
@ -244,39 +372,51 @@ data ReferrerPolicy
|
||||
|
||||
newtype RetryAfter = RetryAfter (DateTime \/ Int)
|
||||
derive instance Newtype (RetryAfter) _
|
||||
derive instance Eq (RetryAfter)
|
||||
|
||||
newtype SecWebsocketKey = SecWebsocketKey String
|
||||
derive instance Newtype (SecWebsocketKey) _
|
||||
derive instance Eq (SecWebsocketKey)
|
||||
|
||||
newtype SecWebsocketAccept = SecWebsocketAccept SecWebsocketKey
|
||||
derive instance Newtype (SecWebsocketAccept) _
|
||||
derive instance Eq (SecWebsocketAccept)
|
||||
|
||||
newtype SecWebsocketVersion = SecWebsocketVersion (String \/ NonEmptyArray String)
|
||||
derive instance Newtype (SecWebsocketVersion) _
|
||||
derive instance Eq (SecWebsocketVersion)
|
||||
|
||||
newtype Server = Server String
|
||||
derive instance Newtype (Server) _
|
||||
derive instance Eq (Server)
|
||||
|
||||
newtype SetCookie = SetCookie String
|
||||
derive instance Newtype (SetCookie) _
|
||||
derive instance Eq (SetCookie)
|
||||
|
||||
newtype StrictTransportSecurity = StrictTransportSecurity {maxAge :: Int, includeSubdomains :: Boolean, preload :: Boolean}
|
||||
derive instance Newtype (StrictTransportSecurity) _
|
||||
derive instance Eq (StrictTransportSecurity)
|
||||
|
||||
newtype TE = TE String
|
||||
derive instance Newtype (TE) _
|
||||
derive instance Eq (TE)
|
||||
|
||||
newtype TransferEncoding = TransferEncoding String
|
||||
derive instance Newtype (TransferEncoding) _
|
||||
derive instance Eq (TransferEncoding)
|
||||
|
||||
newtype Upgrade = Upgrade (NonEmptyArray String)
|
||||
derive instance Newtype (Upgrade) _
|
||||
derive instance Eq (Upgrade)
|
||||
|
||||
newtype UserAgent = UserAgent String
|
||||
derive instance Newtype (UserAgent) _
|
||||
derive instance Eq (UserAgent)
|
||||
|
||||
newtype Vary = Vary (Wildcard \/ NonEmptyArray StringLower)
|
||||
derive instance Newtype (Vary) _
|
||||
derive instance Eq (Vary)
|
||||
|
||||
cacheControlDefaults :: Record CacheControl'
|
||||
cacheControlDefaults =
|
||||
@ -969,7 +1109,7 @@ instance TypedHeader Authorization where
|
||||
headerName = "Authorization"
|
||||
headerValueParser =
|
||||
let
|
||||
scheme = (Parse.String.anyTill (Parse.String.Basic.space) <#> fst <#> AuthScheme)
|
||||
scheme = (Parse.String.anyTill (void Parse.String.Basic.space <|> Parse.String.eof) <#> fst <#> AuthScheme)
|
||||
in
|
||||
pure Authorization <*> scheme <*> (Parse.String.rest <#> String.trim)
|
||||
headerValueEncode (Authorization (AuthScheme s) v) = s <> " " <> v
|
||||
@ -998,7 +1138,7 @@ instance TypedHeader BearerAuth where
|
||||
instance TypedHeader CacheControl where
|
||||
headerName = "Cache-Control"
|
||||
headerValueParser = do
|
||||
directives <- commas1 directiveParser <#> Map.fromFoldable
|
||||
directives <- commas1 directiveParser <#> map (\(k /\ v) -> String.Lower.toString k /\ v) <#> Map.fromFoldable
|
||||
pure $ CacheControl
|
||||
{ maxAge: Map.lookup "max-age" directives # join >>= Int.fromString
|
||||
, maxStale: Map.lookup "max-stale" directives # join >>= Int.fromString
|
||||
@ -1043,3 +1183,126 @@ instance TypedHeader CacheControl where
|
||||
, flag a.staleWhileRevalidate "stale-while-revalidate"
|
||||
, flag a.staleIfError "stale-if-error"
|
||||
]
|
||||
|
||||
instance TypedHeader Connection where
|
||||
headerName = "Connection"
|
||||
headerValueParser =
|
||||
let
|
||||
close = closeRegexParser $> Connection (Left ConnectionClose)
|
||||
in
|
||||
close <|> (commas1 headerNameParser <#> Right <#> Connection)
|
||||
headerValueEncode (Connection (Left ConnectionClose)) = "close"
|
||||
headerValueEncode (Connection (Right as)) = as <#> String.Lower.toString # Array.NonEmpty.intercalate ", "
|
||||
|
||||
instance TypedHeader ContentDisposition where
|
||||
headerName = "Content-Disposition"
|
||||
headerValueParser =
|
||||
let
|
||||
boundary = Parse.String.string ";" <|> (Parse.String.eof *> pure "")
|
||||
inline = Parse.String.string "inline" *> boundary $> ContentDisposition (Either.Nested.in1 ContentDispositionInline)
|
||||
attachment = do
|
||||
void $ Parse.String.string "attachment" *> boundary *> Parse.String.Basic.whiteSpace
|
||||
directives <- commas1 directiveParser <#> map (\(k /\ v) -> String.Lower.toString k /\ v) <#> Map.fromFoldable
|
||||
let
|
||||
filename =
|
||||
join (Map.lookup "filename" directives <|> Map.lookup "filename*" directives)
|
||||
pure $ ContentDisposition $ Either.Nested.in2 $ ContentDispositionAttachment {filename}
|
||||
formData = do
|
||||
void $ Parse.String.string "form-data" *> boundary *> Parse.String.Basic.whiteSpace
|
||||
directives <- commas1 directiveParser <#> map (\(k /\ v) -> String.Lower.toString k /\ v) <#> Map.fromFoldable
|
||||
let
|
||||
filename = join (Map.lookup "filename" directives)
|
||||
name = join (Map.lookup "name" directives)
|
||||
pure $ ContentDisposition $ Either.Nested.in3 $ ContentDispositionFormData {filename, name}
|
||||
in
|
||||
inline <|> attachment <|> formData
|
||||
headerValueEncode (ContentDisposition a) =
|
||||
Either.Nested.either3
|
||||
(const $ [Just "inline"])
|
||||
(\(ContentDispositionAttachment {filename}) -> [Just "attachment", (\s -> "filename=\"" <> s <> "\"") <$> filename])
|
||||
(\(ContentDispositionFormData {filename, name}) -> [Just "attachment", (\s -> "filename=\"" <> s <> "\"") <$> filename, (\s -> "name=\"" <> s <> "\"") <$> name])
|
||||
a
|
||||
# Array.catMaybes
|
||||
# Array.intercalate "; "
|
||||
|
||||
instance TypedHeader ContentEncoding where
|
||||
headerName = "Content-Encoding"
|
||||
headerValueParser =
|
||||
commas1 (Parse.Combine.many Parse.String.Basic.alphaNum <#> Array.fromFoldable <#> String.CodeUnit.fromCharArray)
|
||||
<#> ContentEncoding
|
||||
headerValueEncode (ContentEncoding as) = Array.NonEmpty.intercalate ", " as
|
||||
|
||||
instance TypedHeader ContentLength where
|
||||
headerName = "Content-Length"
|
||||
headerValueParser = Parse.String.Basic.intDecimal <#> ContentLength
|
||||
headerValueEncode (ContentLength a) = Int.toStringAs Int.decimal a
|
||||
|
||||
instance TypedHeader ContentLocation where
|
||||
headerName = "Content-Location"
|
||||
headerValueParser = Parse.String.rest <#> ContentLocation
|
||||
headerValueEncode (ContentLocation a) = a
|
||||
|
||||
instance TypedHeader ContentRange where
|
||||
headerName = "Content-Range"
|
||||
headerValueParser =
|
||||
let
|
||||
startEndSize =
|
||||
pure (\a b c -> a /\ b /\ c)
|
||||
<*> ((Parse.String.Basic.intDecimal <#> ByteRangeStart) <* Parse.String.string "-")
|
||||
<*> ((Parse.String.Basic.intDecimal <#> ByteRangeEnd) <* Parse.String.string "/")
|
||||
<*> (Parse.String.Basic.intDecimal <#> ByteRangeLength)
|
||||
startEnd =
|
||||
pure (\a b -> a /\ b)
|
||||
<*> ((Parse.String.Basic.intDecimal <#> ByteRangeStart) <* Parse.String.string "-")
|
||||
<*> ((Parse.String.Basic.intDecimal <#> ByteRangeEnd) <* Parse.String.string "/" <* wildcardParser)
|
||||
size =
|
||||
wildcardParser
|
||||
*> Parse.String.string "/"
|
||||
*> Parse.String.Basic.intDecimal <#> ByteRangeLength
|
||||
in
|
||||
Parse.String.string "bytes"
|
||||
*> Parse.String.Basic.whiteSpace
|
||||
*> (startEndSize <#> Either.Nested.in1) <|> (startEnd <#> Either.Nested.in2) <|> (size <#> Either.Nested.in3)
|
||||
<#> ContentRange
|
||||
headerValueEncode (ContentRange a) =
|
||||
Either.Nested.either3
|
||||
(\(ByteRangeStart start /\ ByteRangeEnd end /\ ByteRangeLength len) -> ["bytes ", Int.toStringAs Int.decimal start, "-", Int.toStringAs Int.decimal end, "/", Int.toStringAs Int.decimal len])
|
||||
(\(ByteRangeStart start /\ ByteRangeEnd end) -> ["bytes ", Int.toStringAs Int.decimal start, "-", Int.toStringAs Int.decimal end, "/*"])
|
||||
(\(ByteRangeLength len) -> ["bytes ", "*/", Int.toStringAs Int.decimal len])
|
||||
a
|
||||
# Array.fold
|
||||
|
||||
instance TypedHeader Cookie where
|
||||
headerName = "Cookie"
|
||||
headerValueParser = Parse.String.rest <#> Cookie
|
||||
headerValueEncode (Cookie a) = a
|
||||
|
||||
instance TypedHeader Date where
|
||||
headerName = "Date"
|
||||
headerValueParser = datetimeParser <#> Date
|
||||
headerValueEncode (Date a) = printDateTime a
|
||||
|
||||
instance TypedHeader ETag where
|
||||
headerName = "ETag"
|
||||
headerValueParser = Parse.String.rest <#> ETag
|
||||
headerValueEncode (ETag a) = a
|
||||
|
||||
instance TypedHeader ExpectContinue where
|
||||
headerName = "Expect"
|
||||
headerValueParser = Parse.String.string "100-continue" $> ExpectContinue
|
||||
headerValueEncode ExpectContinue = "100-continue"
|
||||
|
||||
instance TypedHeader Expires where
|
||||
headerName = "Expires"
|
||||
headerValueParser = datetimeParser <#> Expires
|
||||
headerValueEncode (Expires a) = printDateTime a
|
||||
|
||||
instance TypedHeader Host where
|
||||
headerName = "Host"
|
||||
headerValueParser = Parse.String.rest <#> Host
|
||||
headerValueEncode (Host a) = a
|
||||
|
||||
instance TypedHeader Origin where
|
||||
headerName = "Origin"
|
||||
headerValueParser = Parse.String.rest <#> Origin
|
||||
headerValueEncode (Origin a) = a
|
||||
|
@ -1,6 +1,7 @@
|
||||
module Axon.Request.Parts.Class
|
||||
( class RequestParts
|
||||
, extractRequestParts
|
||||
, Header(..)
|
||||
, module Parts.Method
|
||||
, module Parts.Body
|
||||
, module Path.Parts
|
||||
@ -8,40 +9,17 @@ module Axon.Request.Parts.Class
|
||||
|
||||
import Prelude
|
||||
|
||||
import Axon.Header.Typed (class TypedHeader, headerName, headerValueParser)
|
||||
import Axon.Request (Request)
|
||||
import Axon.Request as Request
|
||||
import Axon.Request.Method (Method)
|
||||
import Axon.Request.Method as Method
|
||||
import Axon.Request.Parts.Body (Json(..), Stream(..))
|
||||
import Axon.Request.Parts.Body (Json(..), Stream(..)) as Parts.Body
|
||||
import Axon.Request.Parts.Method
|
||||
( Connect(..)
|
||||
, Delete(..)
|
||||
, Get(..)
|
||||
, Options(..)
|
||||
, Patch(..)
|
||||
, Post(..)
|
||||
, Put(..)
|
||||
, Trace(..)
|
||||
)
|
||||
import Axon.Request.Parts.Method
|
||||
( Get(..)
|
||||
, Post(..)
|
||||
, Put(..)
|
||||
, Patch(..)
|
||||
, Delete(..)
|
||||
, Trace(..)
|
||||
, Options(..)
|
||||
, Connect(..)
|
||||
) as Parts.Method
|
||||
import Axon.Request.Parts.Method (Connect(..), Delete(..), Get(..), Options(..), Patch(..), Post(..), Put(..), Trace(..))
|
||||
import Axon.Request.Parts.Method (Get(..), Post(..), Put(..), Patch(..), Delete(..), Trace(..), Options(..), Connect(..)) as Parts.Method
|
||||
import Axon.Request.Parts.Path (Path(..)) as Path.Parts
|
||||
import Axon.Request.Parts.Path
|
||||
( class DiscardTupledUnits
|
||||
, class PathParts
|
||||
, Path(..)
|
||||
, discardTupledUnits
|
||||
, extractPathParts
|
||||
)
|
||||
import Axon.Request.Parts.Path (class DiscardTupledUnits, class PathParts, Path(..), discardTupledUnits, extractPathParts)
|
||||
import Axon.Response (Response)
|
||||
import Axon.Response as Response
|
||||
import Control.Alternative (guard)
|
||||
@ -51,14 +29,25 @@ import Control.Monad.Trans.Class (lift)
|
||||
import Data.Argonaut.Decode (class DecodeJson, decodeJson)
|
||||
import Data.Array as Array
|
||||
import Data.Bifunctor (lmap)
|
||||
import Data.Either (Either(..))
|
||||
import Data.Either (Either(..), hush)
|
||||
import Data.Generic.Rep (class Generic)
|
||||
import Data.Map as Map
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Data.Newtype (class Newtype, wrap)
|
||||
import Data.String.Lower as String.Lower
|
||||
import Data.Tuple.Nested (type (/\), (/\))
|
||||
import Data.URL as URL
|
||||
import Effect.Aff (Aff)
|
||||
import Effect.Class (liftEffect)
|
||||
import Node.Buffer (Buffer)
|
||||
import Parsing (runParser)
|
||||
|
||||
newtype Header a = Header a
|
||||
derive instance Generic (Header a) _
|
||||
derive instance Newtype (Header a) _
|
||||
derive newtype instance (Eq a) => Eq (Header a)
|
||||
derive newtype instance (Ord a) => Ord (Header a)
|
||||
derive newtype instance (Show a) => Show (Header a)
|
||||
|
||||
extractMethod ::
|
||||
forall a.
|
||||
@ -96,6 +85,11 @@ instance RequestParts (Either Request.BodyStringError String) where
|
||||
<#> Just
|
||||
<#> Right
|
||||
|
||||
instance TypedHeader a => RequestParts (Header a) where
|
||||
extractRequestParts r = runExceptT $ runMaybeT do
|
||||
value <- Request.headers r # Map.lookup (String.Lower.fromString $ headerName @a) # pure # MaybeT
|
||||
runParser value (headerValueParser @a) # hush # pure # MaybeT <#> Header
|
||||
|
||||
instance (PathParts a b, DiscardTupledUnits b c) => RequestParts (Path a c) where
|
||||
extractRequestParts r =
|
||||
let
|
||||
|
@ -2,16 +2,11 @@ module Test.Axon.Request.Parts where
|
||||
|
||||
import Prelude
|
||||
|
||||
import Axon.Header.Typed (ContentType(..))
|
||||
import Axon.Request (Request)
|
||||
import Axon.Request as Request
|
||||
import Axon.Request.Method (Method(..))
|
||||
import Axon.Request.Parts.Class
|
||||
( Json(..)
|
||||
, Patch(..)
|
||||
, Path(..)
|
||||
, Post(..)
|
||||
, extractRequestParts
|
||||
)
|
||||
import Axon.Request.Parts.Class (Header(..), Json(..), Patch(..), Path(..), Post(..), extractRequestParts)
|
||||
import Axon.Request.Parts.Path (type (/), IgnoreRest)
|
||||
import Control.Monad.Error.Class (liftEither, liftMaybe)
|
||||
import Data.Bifunctor (lmap)
|
||||
@ -30,6 +25,7 @@ import Node.Stream as Stream
|
||||
import Partial.Unsafe (unsafePartial)
|
||||
import Test.Spec (Spec, describe, it)
|
||||
import Test.Spec.Assertions (shouldEqual)
|
||||
import Type.MIME as MIME
|
||||
|
||||
spec :: Spec Unit
|
||||
spec = describe "Parts" do
|
||||
@ -37,7 +33,7 @@ spec = describe "Parts" do
|
||||
req <- liftEffect $ Request.make
|
||||
{ body: Request.BodyEmpty
|
||||
, url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust
|
||||
, headers: Map.empty
|
||||
, headers: Map.singleton "content-type" "application/json"
|
||||
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4
|
||||
{ address: "127.0.0.1", port: 81 }
|
||||
, method: GET
|
||||
@ -46,7 +42,7 @@ spec = describe "Parts" do
|
||||
>>= liftEither
|
||||
>>= liftMaybe (error "was nothing")
|
||||
|
||||
it "extracts method, path, JSON body" do
|
||||
it "extracts header, method, path, JSON body" do
|
||||
stream <- Buffer.fromString """{"firstName": "henry"}""" UTF8
|
||||
>>= Stream.readableFromBuffer
|
||||
# liftEffect
|
||||
@ -54,17 +50,17 @@ spec = describe "Parts" do
|
||||
{ body: Request.BodyReadable stream
|
||||
, url: URL.fromString "http://localhost:80/users/12" # unsafePartial
|
||||
fromJust
|
||||
, headers: Map.empty
|
||||
, headers: Map.singleton "content-type" "application/json"
|
||||
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4
|
||||
{ address: "127.0.0.1", port: 81 }
|
||||
, method: PATCH
|
||||
}
|
||||
a <-
|
||||
extractRequestParts
|
||||
@(Patch /\ (Path ("users" / Int) _) /\ Json { firstName :: String })
|
||||
@(Patch /\ Header (ContentType MIME.Json) /\ (Path ("users" / Int) _) /\ Json { firstName :: String })
|
||||
req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe
|
||||
(error "was nothing")
|
||||
a `shouldEqual` (Patch /\ Path 12 /\ Json { firstName: "henry" })
|
||||
a `shouldEqual` (Patch /\ Header (ContentType MIME.Json) /\ Path 12 /\ Json { firstName: "henry" })
|
||||
|
||||
describe "Path" do
|
||||
it "matches a route matching literal" do
|
||||
|
Loading…
Reference in New Issue
Block a user