diff --git a/Makefile b/Makefile index 0ccc741..5e0bd5f 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ BOWER := bower NODE := node NPM := npm +# Options to pass to pulp when building +BUILD_OPTIONS := -- --stash --censor-lib --strict + # Package manifest files BOWERJSON := bower.json @@ -47,7 +50,7 @@ $(BUILD): $(COMPONENTS) $(SOURCES) $(PULP) build \ --src-path $(SRCPATH) \ --build-path $(BUILD) \ - -- --stash --censor-lib --strict + $(BUILD_OPTIONS) touch $(BUILD) build: $(BUILD) @@ -89,7 +92,7 @@ test: $(BUILD) $(TESTSOURCES) $(EXAMPLESOURCES) --include $(EXAMPLESPATH) \ --build-path $(BUILD) \ --main $(TESTMAIN) \ - -- --stash --censor-lib --strict + $(BUILD_OPTIONS) # Launch a repl with all modules loaded repl: $(COMPONENTS) $(SOURCES) $(TESTSOURCES) $(EXAMPLESOURCES) diff --git a/Readme.md b/Readme.md index 2a63efe..9ffd5b8 100644 --- a/Readme.md +++ b/Readme.md @@ -7,14 +7,24 @@ A purescript HTTP server framework. +HTTPure is: + +- Well-tested (see our [tests](./test)) +- Well-documented (see our [documentation](./docs)) +- Built to take advantage of PureScript language features for flexible and + extensible routing +- Pure (no `set`, `get`, `use`, etc) + ## Status -This project is currently an early-stage work in progress. It is not -production-ready yet. You can track what's left before it gets production-ready -by looking at -our [roadmap](https://github.com/cprussin/purescript-httpure/projects). If you'd -like to help us get there quicker, please contribute! To get started, check -our [contributing guide](Contributing.md). +This project is currently fairly stable, but has not reached it's 1.0 release +yet. You can track what's left before it gets there by looking at our +[roadmap](https://github.com/cprussin/purescript-httpure/projects). The API +signatures are _mostly_ stable, but are subject to change before the 1.0 release +if there's a good reason to change them. + +If you'd like to help us get to 1.0 quicker, please contribute! To get started, +check our [contributing guide](Contributing.md). ## Installation @@ -27,10 +37,9 @@ bower install --save purescript-httpure ```purescript module Main where -import Prelude (pure, ($)) +import Prelude (($)) import Control.Monad.Eff.Console as Console -import Data.StrMap as StrMap import HTTPure as HTTPure main :: HTTPure.ServerM (console :: Console.CONSOLE) diff --git a/src/HTTPure/Body.purs b/src/HTTPure/Body.purs index 1cf1334..fb3d9c9 100644 --- a/src/HTTPure/Body.purs +++ b/src/HTTPure/Body.purs @@ -15,11 +15,11 @@ import Node.Stream as Stream import HTTPure.HTTPureM as HTTPureM --- | The Body type is just sugar for a String, that will be sent or received in --- | the HTTP body. +-- | The `Body` type is just sugar for a `String`, that will be sent or received +-- | in the HTTP body. type Body = String --- | Extract the contents of the body of the HTTP Request. +-- | Extract the contents of the body of the HTTP `Request`. read :: forall e. HTTP.Request -> Aff.Aff (HTTPureM.HTTPureEffects e) Body read request = Aff.makeAff \_ success -> do let stream = HTTP.requestAsStream request @@ -28,7 +28,7 @@ read request = Aff.makeAff \_ success -> do void $ ST.modifySTRef buf ((<>) str) Stream.onEnd stream $ ST.readSTRef buf >>= success --- | Write a body to the given HTTP Response and close it. +-- | Write a `Body` to the given HTTP `Response` and close it. write :: forall e. HTTP.Response -> Body -> Eff.Eff (http :: HTTP.HTTP | e) Unit write response body = void do _ <- Stream.writeString stream Encoding.UTF8 body $ pure unit diff --git a/src/HTTPure/Headers.purs b/src/HTTPure/Headers.purs index b001f10..5ab8a64 100644 --- a/src/HTTPure/Headers.purs +++ b/src/HTTPure/Headers.purs @@ -17,33 +17,34 @@ import Node.HTTP as HTTP import HTTPure.Lookup as Lookup --- | The Headers type is just sugar for a StrMap of Strings that represents the --- | set of headers sent or received in an HTTP request or response. +-- | The `Headers` type is just sugar for a `StrMap` of `Strings` that +-- | represents the set of headers in an HTTP request or response. newtype Headers = Headers (StrMap.StrMap String) --- | Given a string, return the matching headers. This search is --- | case-insensitive. +-- | Given a string, return the value of the matching header, or an empty string +-- | if no match exists. This search is case-insensitive. instance lookupHeaders :: Lookup.Lookup Headers String String where lookup (Headers headers') = Maybe.fromMaybe "" <<< flip StrMap.lookup headers' <<< StringUtil.toLower --- | Allow a headers set to be represented as a string. +-- | Allow a `Headers` to be represented as a string. This string is formatted +-- | in HTTP headers format. instance showHeaders :: Show Headers where show (Headers headers') = StrMap.foldMap showField headers' <> "\n" where showField key value = key <> ": " <> value <> "\n" --- | Compare two Headers objects by comparing the underlying StrMaps. +-- | Compare two `Headers` objects by comparing the underlying `StrMaps`. instance eqHeaders :: Eq Headers where eq (Headers a) (Headers b) = eq a b --- | Get the headers out of a HTTP Request object. +-- | Get the headers out of a HTTP `Request` object. read :: HTTP.Request -> Headers read = HTTP.requestHeaders >>> Headers --- | Given an HTTP Response and a Headers object, return an effect that will --- | write the Headers to the Response. +-- | Given an HTTP `Response` and a `Headers` object, return an effect that will +-- | write the `Headers` to the `Response`. write :: forall e. HTTP.Response -> Headers -> @@ -51,6 +52,6 @@ write :: forall e. write response (Headers headers') = void $ TraversableWithIndex.traverseWithIndex (HTTP.setHeader response) headers' --- | Convert an Array of Tuples of 2 Strings to a Headers object. +-- | Convert an `Array` of `Tuples` of 2 `Strings` to a `Headers` object. headers :: Array (Tuple.Tuple String String) -> Headers headers = StrMap.fromFoldable >>> Headers diff --git a/src/HTTPure/Lookup.purs b/src/HTTPure/Lookup.purs index 17a3f1b..d969750 100644 --- a/src/HTTPure/Lookup.purs +++ b/src/HTTPure/Lookup.purs @@ -10,24 +10,33 @@ import Data.Maybe as Maybe import Data.Monoid as Monoid import Data.StrMap as StrMap --- | Types that implement the Lookup class can be looked up by some key to +-- | Types that implement the `Lookup` class can be looked up by some key to -- | retrieve some value. For instance, you could have an implementation for --- | String (Maybe String) Int where `lookup string index` returns `Just` the --- | character in `string` at `index`, or `Nothing` if `index` is out of bounds. +-- | `String (Maybe String) Int` where `lookup s i` returns `Just` the +-- | character in `s` at `i`, or `Nothing` if `i` is out of bounds. class Lookup a v k where + + -- | Given some type and a key on that type, extract some value that + -- | corresponds to that key. lookup :: a -> k -> v --- | !! can be used as an infix operator instead of using the `lookup` function. +-- | `!!` is inspired by `!!` in `Data.Array`, but note that it differs from +-- | `!!` in `Data.Array` in that the default instance for `Arrays` with `Int` +-- | key types is defined on `Arrays` of some members of `Monoids`, and will +-- | always return a value and will not return `Maybes`. If the requested index +-- | is out of bounds, then this implementation will return `mempty` instead of +-- | `Nothing`. infixl 8 lookup as !! --- | A default instance of Lookup for an Array of some type of Monoid. Note that --- | this is different from `!!` defined in `Data.Array` in that it does not --- | return a Maybe. If the index is out of bounds, the return value is mempty. +-- | A default instance of `Lookup` for an `Array` of some type of `Monoid`. +-- | Note that this is different from `!!` defined in `Data.Array` in that it +-- | does not return a `Maybe`. If the index is out of bounds, the return value +-- | is `mempty`. instance lookupArray :: Monoid.Monoid m => Lookup (Array m) m Int where lookup arr = Maybe.fromMaybe Monoid.mempty <<< Array.index arr --- | A default instance of Lookup for a StrMap of some type of Monoid. If the --- | key does not exist in the StrMap, then the return value is mempty. +-- | A default instance of `Lookup` for a `StrMap` of some type of `Monoid`. If +-- | the key does not exist in the `StrMap`, then the return value is `mempty`. instance lookupStrMap :: Monoid.Monoid m => Lookup (StrMap.StrMap m) m String where lookup strMap = Maybe.fromMaybe Monoid.mempty <<< flip StrMap.lookup strMap diff --git a/src/HTTPure/Method.purs b/src/HTTPure/Method.purs index aab0bdc..f2cd362 100644 --- a/src/HTTPure/Method.purs +++ b/src/HTTPure/Method.purs @@ -20,12 +20,12 @@ data Method | Trace | Patch --- | If two Methods are the same constructor, they are equal. +-- | If two `Methods` are the same constructor, they are equal. derive instance generic :: Generic.Generic Method instance eq :: Eq.Eq Method where eq = Generic.gEq --- | Convert a constructor to a string. +-- | Convert a constructor to a `String`. instance show :: Show.Show Method where show Get = "Get" show Post = "Post" @@ -37,7 +37,7 @@ instance show :: Show.Show Method where show Trace = "Trace" show Patch = "Patch" --- | Take an HTTP Request and return the Method for that request. +-- | Take an HTTP `Request` and extract the `Method` for that request. read :: HTTP.Request -> Method read request = case HTTP.requestMethod request of "POST" -> Post diff --git a/src/HTTPure/Path.purs b/src/HTTPure/Path.purs index b0da258..338b482 100644 --- a/src/HTTPure/Path.purs +++ b/src/HTTPure/Path.purs @@ -10,15 +10,15 @@ import Data.Maybe as Maybe import Data.String as String import Node.HTTP as HTTP --- | 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. Note --- | that this type has an implementation of Lookup for `Int` keys defined by --- | `lookpArray` in `Lookup.purs` because `lookupArray` is defined for any --- | `Array` of `Monoids`. So you can do something like `path !! 2` to get the --- | path segment at index 2. +-- | 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. +-- | Note that this type has an implementation of `Lookup` for `Int` keys +-- | defined by `lookupArray` in [Lookup.purs](./Lookup.purs) because +-- | `lookupArray` is defined for any `Array` of `Monoids`. So you can do +-- | something like `path !! 2` to get the path segment at index 2. type Path = Array String --- | Given an HTTP Request object, extract the Path. +-- | Given an HTTP `Request` object, extract the `Path`. read :: HTTP.Request -> Path read = HTTP.requestURL >>> split "?" >>> first >>> split "/" >>> nonempty where diff --git a/src/HTTPure/Query.purs b/src/HTTPure/Query.purs index 0ffc274..a7f53ab 100644 --- a/src/HTTPure/Query.purs +++ b/src/HTTPure/Query.purs @@ -12,17 +12,17 @@ import Data.StrMap as StrMap import Data.Tuple as Tuple import Node.HTTP as HTTP --- | The Query type is a StrMap of Strings, with one entry per query parameter --- | in the request. For any query parameters that don't have values --- | (`/some/path?query`), the value in the StrMap for that parameter will be --- | the string "true". Note that this type has an implementation of Lookup for --- | `String` keys defined by `lookpStrMap` in `Lookup.purs` because +-- | The `Query` type is a `StrMap` of `Strings`, with one entry per query +-- | parameter in the request. For any query parameters that don't have values +-- | (`/some/path?query`), the value in the `StrMap` for that parameter will be +-- | the string `"true"`. Note that this type has an implementation of `Lookup` +-- | for `String` keys defined by `lookpStrMap` in `Lookup.purs` because -- | `lookupStrMap` is defined for any `StrMap` of `Monoids`. So you can do -- | something like `query !! "foo"` to get the value of the query parameter -- | "foo". type Query = StrMap.StrMap String --- | The StrMap of query segments in the given HTTP Request. +-- | The `StrMap` of query segments in the given HTTP `Request`. read :: HTTP.Request -> Query read = HTTP.requestURL >>> split "?" >>> last >>> split "&" >>> nonempty >>> toStrMap diff --git a/src/HTTPure/Request.purs b/src/HTTPure/Request.purs index 636a03e..5fa7812 100644 --- a/src/HTTPure/Request.purs +++ b/src/HTTPure/Request.purs @@ -15,8 +15,8 @@ import HTTPure.Method as Method import HTTPure.Path as Path import HTTPure.Query as Query --- | A Route is a function that takes a Method, a Path, a Query, a Header, and a --- | Body, and returns a Response monad. +-- | The `Request` type is a `Record` type that includes fields for accessing +-- | the different parts of the HTTP request. type Request = { method :: Method.Method , path :: Path.Path @@ -25,8 +25,8 @@ type Request = , body :: Body.Body } --- | Given an HTTP Request object, this method will convert it to an HTTPure --- | Request object. +-- | Given an HTTP `Request` object, this method will convert it to an HTTPure +-- | `Request` object. fromHTTPRequest :: forall e. HTTP.Request -> Aff.Aff (HTTPureM.HTTPureEffects e) Request diff --git a/src/HTTPure/Response.purs b/src/HTTPure/Response.purs index 2f754a9..9805df5 100644 --- a/src/HTTPure/Response.purs +++ b/src/HTTPure/Response.purs @@ -84,17 +84,17 @@ import HTTPure.Headers as Headers import HTTPure.HTTPureM as HTTPureM import HTTPure.Status as Status --- | The ResponseM type simply conveniently wraps up an HTTPure monad that +-- | The `ResponseM` type simply conveniently wraps up an HTTPure monad that -- | returns a response. This type is the return type of all router/route -- | methods. type ResponseM e = HTTPureM.HTTPureM e Response --- | A response is a status code, headers, and a body. +-- | A `Response` is a status code, headers, and a body. data Response = Response Status.Status Headers.Headers Body.Body --- | Given an HTTP response and a HTTPure response, this method will return a --- | monad encapsulating writing the HTTPure response to the HTTP response and --- | closing the HTTP response. +-- | Given an HTTP `Response` and a HTTPure `Response`, this method will return +-- | a monad encapsulating writing the HTTPure `Response` to the HTTP `Response` +-- | and closing the HTTP `Response`. send :: forall e. HTTP.Response -> Response -> HTTPureM.HTTPureM e Unit send httpresponse (Response status headers body) = do Status.write httpresponse $ status diff --git a/src/HTTPure/Server.purs b/src/HTTPure/Server.purs index 6be96b4..8b376d6 100644 --- a/src/HTTPure/Server.purs +++ b/src/HTTPure/Server.purs @@ -21,18 +21,19 @@ import HTTPure.HTTPureM as HTTPureM import HTTPure.Request as Request import HTTPure.Response as Response --- | The ServerM type simply conveniently wraps up an HTTPure monad that --- | returns a Unit. This type is the return type of the HTTPure serve and +-- | The `ServerM` type simply conveniently wraps up an HTTPure monad that +-- | returns a `Unit`. This type is the return type of the HTTPure serve and -- | related methods. type ServerM e = HTTPureM.HTTPureM e Unit --- | The SecureServerM type is the same as the ServerM type, but it includes +-- | The `SecureServerM` type is the same as the `ServerM` type, but it includes -- | effects for working with the filesystem (to load the key and certificate). type SecureServerM e = ServerM (fs :: FS.FS | e) --- | This function takes a method which takes a request and returns a ResponseM, --- | an HTTP request, and an HTTP response. It runs the request, extracts the --- | Response from the ResponseM, and sends the Response to the HTTP Response. +-- | This function takes a method which takes a `Request` and returns a +-- | `ResponseM`, an HTTP `Request`, and an HTTP `Response`. It runs the +-- | request, extracts the `Response` from the `ResponseM`, and sends the +-- | `Response` to the HTTP `Response`. handleRequest :: forall e. (Request.Request -> Response.ResponseM e) -> HTTP.Request -> @@ -43,9 +44,9 @@ handleRequest router request response = req <- Request.fromHTTPRequest request EffClass.liftEff $ router req >>= Response.send response --- | Given a ListenOptions Record, a function mapping Request to ResponseM, and --- | an HTTPureM containing effects to run on boot, creates and runs a HTTPure --- | server without SSL. +-- | Given a `ListenOptions` object, a function mapping `Request` to +-- | `ResponseM`, and an `HTTPureM` containing effects to run on boot, creates +-- | and runs a HTTPure server without SSL. bootHTTP :: forall e. HTTP.ListenOptions -> (Request.Request -> Response.ResponseM e) -> @@ -55,8 +56,8 @@ bootHTTP options router onStarted = HTTP.createServer (handleRequest router) >>= \server -> HTTP.listen server options onStarted --- | Given a ListenOptions Record, a path to a cert file, a path to a private --- | key file, a function mapping Request to ResponseM, and an HTTPureM +-- | Given a `ListenOptions` object, a path to a cert file, a path to a private +-- | key file, a function mapping `Request` to `ResponseM`, and an `HTTPureM` -- | containing effects to run on boot, creates and runs a HTTPure server with -- | SSL. bootHTTPS :: forall e. @@ -76,7 +77,7 @@ bootHTTPS options cert key router onStarted = do HTTPS.key := HTTPS.keyString key' <> HTTPS.cert := HTTPS.certString cert' --- | Given a port number, return a HTTP.ListenOptions Record. +-- | Given a port number, return a `HTTP.ListenOptions` `Record`. listenOptions :: Int -> HTTP.ListenOptions listenOptions port = { hostname: "localhost" @@ -85,9 +86,10 @@ listenOptions port = } -- | Create and start a server. This is the main entry point for HTTPure. Takes --- | a port number on which to listen, a function mapping Request to ResponseM, --- | and an HTTPureM containing effects to run after the server has booted --- | (usually logging). Returns an HTTPureM containing the server's effects. +-- | a port number on which to listen, a function mapping `Request` to +-- | `ResponseM`, and an `HTTPureM` containing effects to run after the server +-- | has booted (usually logging). Returns an `HTTPureM` containing the server's +-- | effects. serve :: forall e. Int -> (Request.Request -> Response.ResponseM e) -> @@ -100,7 +102,7 @@ serve = bootHTTP <<< listenOptions -- | 1. A port number -- | 2. A path to a cert file -- | 3. A path to a private key file --- | 4. A handler method which maps Request to ResponseM +-- | 4. A handler method which maps `Request` to `ResponseM` -- | 5. A callback to call when the server is up serve' :: forall e. Int -> diff --git a/src/HTTPure/Status.purs b/src/HTTPure/Status.purs index 6f260a0..40dac73 100644 --- a/src/HTTPure/Status.purs +++ b/src/HTTPure/Status.purs @@ -78,10 +78,10 @@ import Prelude import Control.Monad.Eff as Eff import Node.HTTP as HTTP --- | The Status type enumerates all valid HTTP response status codes. +-- | The `Status` type enumerates all valid HTTP response status codes. type Status = Int --- | Write a status to a given HTTP Response. +-- | Write a status to a given HTTP `Response`. write :: forall e. HTTP.Response -> Status ->