From 4ba46b04472d9d5200421688628ad27ed8da41b7 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sat, 14 Mar 2015 03:33:12 +0000 Subject: [PATCH 1/8] Initial implementation --- .gitignore | 6 + README.md | 196 +++++++++++++++++++++++++++++++- bower.json | 31 +++++ gulpfile.js | 27 +++++ package.json | 9 ++ src/Network/Affjax.purs | 8 ++ src/Network/Affjax/DSL.purs | 101 ++++++++++++++++ src/Network/Affjax/Request.purs | 93 +++++++++++++++ 8 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 bower.json create mode 100644 gulpfile.js create mode 100644 package.json create mode 100644 src/Network/Affjax.purs create mode 100644 src/Network/Affjax/DSL.purs create mode 100644 src/Network/Affjax/Request.purs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc070b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.* +!/.gitignore +/bower_components/ +/node_modules/ +/output/ +/tmp/ diff --git a/README.md b/README.md index 374919b..5dc0408 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,194 @@ -# purescript-affjax -An asynchronous AJAX library built using Aff. +# Module Documentation + +## Module Network.Affjax + +#### `runAffjax` + +``` purescript +runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +``` + + + +## Module Network.Affjax.DSL + +#### `AffjaxRequest` + +``` purescript +type AffjaxRequest = FreeC AffjaxRequestF +``` + +A free monad for building AJAX requests + +#### `AffjaxRequestF` + +``` purescript +data AffjaxRequestF a + = SetURL String a + | SetMethod MethodName a + | AddHeader Header a + | SetContent (Maybe Content) a + | SetUsername (Maybe String) a + | SetPassword (Maybe String) a +``` + +The request DSL AST. + +#### `affjaxRequest` + +``` purescript +affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest +``` + +Runs the DSL, producing an `AjaxRequest` object. + +#### `url` + +``` purescript +url :: String -> AffjaxRequest Unit +``` + +Sets the URL for a request. + +#### `method` + +``` purescript +method :: Verb -> AffjaxRequest Unit +``` + +Sets the request method based on an HTTP verb. + +#### `method'` + +``` purescript +method' :: MethodName -> AffjaxRequest Unit +``` + +Sets the request method. + +#### `header` + +``` purescript +header :: HeaderHead -> String -> AffjaxRequest Unit +``` + +Adds a header to the request using a key and value. + +#### `header'` + +``` purescript +header' :: Header -> AffjaxRequest Unit +``` + +Adds a header to the request using a `Header` record. + +#### `content` + +``` purescript +content :: Content -> AffjaxRequest Unit +``` + +Sets the content for the request. + +#### `content'` + +``` purescript +content' :: Maybe Content -> AffjaxRequest Unit +``` + +Sets the content for the request, with the option of setting it to +`Nothing`. + +#### `username` + +``` purescript +username :: String -> AffjaxRequest Unit +``` + +Sets the username for the request. + +#### `username'` + +``` purescript +username' :: Maybe String -> AffjaxRequest Unit +``` + +Sets the username for the request, with the option of setting it to +`Nothing`. + +#### `password` + +``` purescript +password :: String -> AffjaxRequest Unit +``` + +Sets the password for the request. + +#### `password'` + +``` purescript +password' :: Maybe String -> AffjaxRequest Unit +``` + +Sets the password for the request, with the option of setting it to +`Nothing`. + + +## Module Network.Affjax.Request + +#### `Ajax` + +``` purescript +data Ajax :: ! +``` + +The event type for AJAX requests. + +#### `AjaxRequest` + +``` purescript +type AjaxRequest = { password :: Maybe String, username :: Maybe String, content :: Maybe Content, headers :: [Header], method :: MethodName, url :: String } +``` + +The parameters for an AJAX request. + +#### `MethodName` + +``` purescript +newtype MethodName + = MethodName String +``` + +A HTTP method name: `GET`, `POST`, etc. + +#### `Content` + +``` purescript +data Content + = Content String +``` + +#### `AjaxResponse` + +``` purescript +newtype AjaxResponse +``` + +#### `defaultRequest` + +``` purescript +defaultRequest :: AjaxRequest +``` + +A basic request, `GET /` with no particular headers or credentials. + +#### `ajax` + +``` purescript +ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse +``` + +Make an AJAX request. + + + diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..ac36350 --- /dev/null +++ b/bower.json @@ -0,0 +1,31 @@ +{ + "name": "purescript-affjax", + "homepage": "https://github.com/slamdata/purescript-affjax", + "description": "An asynchronous AJAX library built using Aff.", + "keywords": [ + "purescript", + "ajax" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "bower_components", + "node_modules", + "output", + "tests", + "tmp", + "bower.json", + "Gruntfile.js", + "package.json" + ], + "dependencies": { + "purescript-aff": "~0.6.0", + "purescript-exceptions": "~0.2.2", + "purescript-free": "~0.4.0", + "purescript-transformers": "~0.5.1", + "purescript-arrays": "~0.3.3", + "purescript-maybe": "~0.2.1", + "purescript-nullable": "~0.1.1", + "purescript-http": "~0.2.0" + } +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..1466ca9 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,27 @@ +"use strict"; + +var gulp = require("gulp"); +var plumber = require("gulp-plumber"); +var purescript = require("gulp-purescript"); +var jsvalidate = require("gulp-jsvalidate"); + +gulp.task("make", function() { + return gulp.src(["src/**/*.purs", "bower_components/purescript-*/src/**/*.purs"]) + .pipe(plumber()) + .pipe(purescript.pscMake()); +}); + +gulp.task("jsvalidate", ["make"], function () { + return gulp.src("output/**/*.js") + .pipe(plumber()) + .pipe(jsvalidate()); +}); + +gulp.task("docs", function () { + return gulp.src("src/**/*.purs") + .pipe(plumber()) + .pipe(purescript.pscDocs()) + .pipe(gulp.dest("README.md")); +}); + +gulp.task("default", ["jsvalidate", "docs"]); diff --git a/package.json b/package.json new file mode 100644 index 0000000..327f797 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.8.11", + "gulp-jsvalidate": "^1.0.1", + "gulp-plumber": "^1.0.0", + "gulp-purescript": "^0.1.2" + } +} diff --git a/src/Network/Affjax.purs b/src/Network/Affjax.purs new file mode 100644 index 0000000..6bf7ebd --- /dev/null +++ b/src/Network/Affjax.purs @@ -0,0 +1,8 @@ +module Network.Affjax where + +import Control.Monad.Aff +import Network.Affjax.DSL +import Network.Affjax.Request + +runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +runAffjax = ajax <<< affjaxRequest diff --git a/src/Network/Affjax/DSL.purs b/src/Network/Affjax/DSL.purs new file mode 100644 index 0000000..2360edf --- /dev/null +++ b/src/Network/Affjax/DSL.purs @@ -0,0 +1,101 @@ +module Network.Affjax.DSL + ( AffjaxRequest() + , AffjaxRequestF(..) + , affjaxRequest + , url + , method + , method' + , header + , header' + , content + , content' + , username + , username' + , password + , password' + ) where + +import Control.Monad.Free (FreeC(), liftFC, runFreeCM) +import Control.Monad.State (State(), execState) +import Control.Monad.State.Class (modify) +import Data.Coyoneda (Natural()) +import Data.Maybe (Maybe(..)) +import Network.Affjax.Request +import Network.HTTP (Verb(..), Header(..), HeaderHead()) + +-- | A free monad for building AJAX requests +type AffjaxRequest = FreeC AffjaxRequestF + +-- | The request DSL AST. +data AffjaxRequestF a + = SetURL String a + | SetMethod MethodName a + | AddHeader Header a + | SetContent (Maybe Content) a + | SetUsername (Maybe String) a + | SetPassword (Maybe String) a + +-- | The interpreter for the request DSL AST. +affjaxN :: Natural AffjaxRequestF (State AjaxRequest) +affjaxN (SetURL url a) = const a <$> modify (_ { url = url }) +affjaxN (SetMethod method a) = const a <$> modify (_ { method = method }) +affjaxN (AddHeader header a) = const a <$> modify (\req -> req { headers = header : req.headers }) +affjaxN (SetContent content a) = const a <$> modify (_ { content = content }) +affjaxN (SetUsername username a) = const a <$> modify (_ { username = username }) +affjaxN (SetPassword password a) = const a <$> modify (_ { password = password }) + +-- | Runs the DSL, producing an `AjaxRequest` object. +affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest +affjaxRequest = (`execState` defaultRequest) <<< runFreeCM affjaxN + +-- | Take a standard HTTP verb and allow it to be used as an AJAX request +-- | method. +methodNameFromVerb :: Verb -> MethodName +methodNameFromVerb = MethodName <<< show + +-- | Sets the URL for a request. +url :: String -> AffjaxRequest Unit +url url = liftFC (SetURL url unit) + +-- | Sets the request method based on an HTTP verb. +method :: Verb -> AffjaxRequest Unit +method = method' <<< methodNameFromVerb + +-- | Sets the request method. +method' :: MethodName -> AffjaxRequest Unit +method' meth = liftFC (SetMethod meth unit) + +-- | Adds a header to the request using a key and value. +header :: HeaderHead -> String -> AffjaxRequest Unit +header key value = header' (Header key value) + +-- | Adds a header to the request using a `Header` record. +header' :: Header -> AffjaxRequest Unit +header' header = liftFC (AddHeader header unit) + +-- | Sets the content for the request. +content :: Content -> AffjaxRequest Unit +content value = content' (Just value) + +-- | Sets the content for the request, with the option of setting it to +-- | `Nothing`. +content' :: Maybe Content -> AffjaxRequest Unit +content' value = liftFC (SetContent value unit) + +-- | Sets the username for the request. +username :: String -> AffjaxRequest Unit +username value = username' (Just value) + +-- | Sets the username for the request, with the option of setting it to +-- | `Nothing`. +username' :: Maybe String -> AffjaxRequest Unit +username' value = liftFC (SetUsername value unit) + +-- | Sets the password for the request. +password :: String -> AffjaxRequest Unit +password value = password' (Just value) + +-- | Sets the password for the request, with the option of setting it to +-- | `Nothing`. +password' :: Maybe String -> AffjaxRequest Unit +password' value = liftFC (SetPassword value unit) diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs new file mode 100644 index 0000000..ee49f34 --- /dev/null +++ b/src/Network/Affjax/Request.purs @@ -0,0 +1,93 @@ +module Network.Affjax.Request + ( Ajax() + , AjaxRequest() + , MethodName(..) + , Content(..) + , AjaxResponse() + , defaultRequest + , ajax + ) where + +import Control.Monad.Aff (Aff(), EffA(), makeAff) +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Exception(Error()) +import Data.Array () +import Data.Function (Fn8(), runFn8) +import Data.Maybe (Maybe(..), maybe) +import Data.Nullable (Nullable(), toNullable) +import Network.HTTP (Header(..)) + +-- | The event type for AJAX requests. +foreign import data Ajax :: ! + +-- | The parameters for an AJAX request. +type AjaxRequest = + { url :: String + , method :: MethodName + , headers :: [Header] + , content :: Maybe Content + , username :: Maybe String + , password :: Maybe String + } + +-- | A HTTP method name: `GET`, `POST`, etc. +newtype MethodName = MethodName String + +-- | The types of data that can be set in an AJAX request. +-- TODO: how do we want to deal with the various content types? +data Content = Content String + +-- TODO: probably not this? Do we want to deal with other responses, include headers, etc? +newtype AjaxResponse = AjaxResponse String + +-- | A basic request, `GET /` with no particular headers or credentials. +defaultRequest :: AjaxRequest +defaultRequest = + { url: "/" + , method: MethodName "GET" + , headers: [] + , content: Nothing + , username: Nothing + , password: Nothing + } + +-- | Make an AJAX request. +ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse +ajax req = makeAff $ runFn8 + unsafeAjax req.url + (runMethodName req.method) + (runHeader <$> req.headers) + (toNullable $ runContent <$> req.content) + (toNullable req.username) + (toNullable req.password) + where + runMethodName (MethodName name) = name + runHeader (Header header value) = { header: show header, value: value } + runContent (Content content) = content + +foreign import unsafeAjax + """ + function unsafeAjax (url, method, headers, content, username, password, errback, callback) { + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true, username, password); + for (var i = 0, header; header = headers[i]; i++) { + xhr.setRequestHeader(header.header, header.value); + } + xhr.onerror = function (err) { + errback(err); + }; + xhr.onload = function () { + if (xhr.status === 200) callback(xhr.response); + else errback(new Error("Request returned status " + xhr.status)); + } + xhr.send(content); + } + """ :: forall e a. Fn8 String + String + [{ header :: String, value :: String }] + (Nullable a) + (Nullable String) + (Nullable String) + (Error -> Eff (ajax :: Ajax | e) Unit) + (AjaxResponse -> Eff (ajax :: Ajax | e) Unit) + (EffA (ajax :: Ajax | e) Unit) From 323ac8a5515ce4c78efc57ef23fddc686c159925 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sat, 14 Mar 2015 16:16:24 +0000 Subject: [PATCH 2/8] Add content types from the XHRLS --- README.md | 46 +++++++++++++++------------- bower.json | 4 ++- src/Network/Affjax.purs | 3 +- src/Network/Affjax/DSL.purs | 32 +++++++++---------- src/Network/Affjax/Request.purs | 54 +++++++++++++++++++++++---------- 5 files changed, 84 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 5dc0408..73b4a63 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ #### `runAffjax` ``` purescript -runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +runAffjax :: forall e c a. AffjaxRequest c a -> Aff (ajax :: Ajax | e) AjaxResponse ``` @@ -15,7 +15,7 @@ runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse #### `AffjaxRequest` ``` purescript -type AffjaxRequest = FreeC AffjaxRequestF +type AffjaxRequest c = FreeC (AffjaxRequestF c) ``` A free monad for building AJAX requests @@ -23,11 +23,11 @@ A free monad for building AJAX requests #### `AffjaxRequestF` ``` purescript -data AffjaxRequestF a +data AffjaxRequestF c a = SetURL String a | SetMethod MethodName a | AddHeader Header a - | SetContent (Maybe Content) a + | SetContent (Maybe (Content c)) a | SetUsername (Maybe String) a | SetPassword (Maybe String) a ``` @@ -37,7 +37,7 @@ The request DSL AST. #### `affjaxRequest` ``` purescript -affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest +affjaxRequest :: forall c a. AffjaxRequest c a -> AjaxRequest c ``` Runs the DSL, producing an `AjaxRequest` object. @@ -45,7 +45,7 @@ Runs the DSL, producing an `AjaxRequest` object. #### `url` ``` purescript -url :: String -> AffjaxRequest Unit +url :: forall c. String -> AffjaxRequest c Unit ``` Sets the URL for a request. @@ -53,7 +53,7 @@ Sets the URL for a request. #### `method` ``` purescript -method :: Verb -> AffjaxRequest Unit +method :: forall c. Verb -> AffjaxRequest c Unit ``` Sets the request method based on an HTTP verb. @@ -61,7 +61,7 @@ Sets the request method based on an HTTP verb. #### `method'` ``` purescript -method' :: MethodName -> AffjaxRequest Unit +method' :: forall c. MethodName -> AffjaxRequest c Unit ``` Sets the request method. @@ -69,7 +69,7 @@ Sets the request method. #### `header` ``` purescript -header :: HeaderHead -> String -> AffjaxRequest Unit +header :: forall c. HeaderHead -> String -> AffjaxRequest c Unit ``` Adds a header to the request using a key and value. @@ -77,7 +77,7 @@ Adds a header to the request using a key and value. #### `header'` ``` purescript -header' :: Header -> AffjaxRequest Unit +header' :: forall c. Header -> AffjaxRequest c Unit ``` Adds a header to the request using a `Header` record. @@ -85,7 +85,7 @@ Adds a header to the request using a `Header` record. #### `content` ``` purescript -content :: Content -> AffjaxRequest Unit +content :: forall c. Content c -> AffjaxRequest c Unit ``` Sets the content for the request. @@ -93,7 +93,7 @@ Sets the content for the request. #### `content'` ``` purescript -content' :: Maybe Content -> AffjaxRequest Unit +content' :: forall c. Maybe (Content c) -> AffjaxRequest c Unit ``` Sets the content for the request, with the option of setting it to @@ -102,7 +102,7 @@ Sets the content for the request, with the option of setting it to #### `username` ``` purescript -username :: String -> AffjaxRequest Unit +username :: forall c. String -> AffjaxRequest c Unit ``` Sets the username for the request. @@ -110,7 +110,7 @@ Sets the username for the request. #### `username'` ``` purescript -username' :: Maybe String -> AffjaxRequest Unit +username' :: forall c. Maybe String -> AffjaxRequest c Unit ``` Sets the username for the request, with the option of setting it to @@ -119,7 +119,7 @@ Sets the username for the request, with the option of setting it to #### `password` ``` purescript -password :: String -> AffjaxRequest Unit +password :: forall c. String -> AffjaxRequest c Unit ``` Sets the password for the request. @@ -127,7 +127,7 @@ Sets the password for the request. #### `password'` ``` purescript -password' :: Maybe String -> AffjaxRequest Unit +password' :: forall c. Maybe String -> AffjaxRequest c Unit ``` Sets the password for the request, with the option of setting it to @@ -147,7 +147,7 @@ The event type for AJAX requests. #### `AjaxRequest` ``` purescript -type AjaxRequest = { password :: Maybe String, username :: Maybe String, content :: Maybe Content, headers :: [Header], method :: MethodName, url :: String } +type AjaxRequest a = { password :: Maybe String, username :: Maybe String, content :: Maybe (Content a), headers :: [Header], method :: MethodName, url :: String } ``` The parameters for an AJAX request. @@ -164,8 +164,12 @@ A HTTP method name: `GET`, `POST`, etc. #### `Content` ``` purescript -data Content - = Content String +data Content a + = ArrayViewContent (ArrayView a) + | BlobContent Blob + | DocumentContent Document + | TextContent String + | FormDataContent FormData ``` #### `AjaxResponse` @@ -177,7 +181,7 @@ newtype AjaxResponse #### `defaultRequest` ``` purescript -defaultRequest :: AjaxRequest +defaultRequest :: forall c. AjaxRequest c ``` A basic request, `GET /` with no particular headers or credentials. @@ -185,7 +189,7 @@ A basic request, `GET /` with no particular headers or credentials. #### `ajax` ``` purescript -ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse +ajax :: forall e a. AjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse ``` Make an AJAX request. diff --git a/bower.json b/bower.json index ac36350..6ed63fa 100644 --- a/bower.json +++ b/bower.json @@ -26,6 +26,8 @@ "purescript-arrays": "~0.3.3", "purescript-maybe": "~0.2.1", "purescript-nullable": "~0.1.1", - "purescript-http": "~0.2.0" + "purescript-http": "~0.2.0", + "purescript-arraybuffer-types": "~0.1.1", + "purescript-dom": "#additions" } } diff --git a/src/Network/Affjax.purs b/src/Network/Affjax.purs index 6bf7ebd..5e5be87 100644 --- a/src/Network/Affjax.purs +++ b/src/Network/Affjax.purs @@ -3,6 +3,7 @@ module Network.Affjax where import Control.Monad.Aff import Network.Affjax.DSL import Network.Affjax.Request +import Data.ArrayBuffer.Types -runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +runAffjax :: forall e c a. AffjaxRequest c a -> Aff (ajax :: Ajax | e) AjaxResponse runAffjax = ajax <<< affjaxRequest diff --git a/src/Network/Affjax/DSL.purs b/src/Network/Affjax/DSL.purs index 2360edf..1a37add 100644 --- a/src/Network/Affjax/DSL.purs +++ b/src/Network/Affjax/DSL.purs @@ -24,19 +24,19 @@ import Network.Affjax.Request import Network.HTTP (Verb(..), Header(..), HeaderHead()) -- | A free monad for building AJAX requests -type AffjaxRequest = FreeC AffjaxRequestF +type AffjaxRequest c = FreeC (AffjaxRequestF c) -- | The request DSL AST. -data AffjaxRequestF a +data AffjaxRequestF c a = SetURL String a | SetMethod MethodName a | AddHeader Header a - | SetContent (Maybe Content) a + | SetContent (Maybe (Content c)) a | SetUsername (Maybe String) a | SetPassword (Maybe String) a -- | The interpreter for the request DSL AST. -affjaxN :: Natural AffjaxRequestF (State AjaxRequest) +affjaxN :: forall c. Natural (AffjaxRequestF c) (State (AjaxRequest c)) affjaxN (SetURL url a) = const a <$> modify (_ { url = url }) affjaxN (SetMethod method a) = const a <$> modify (_ { method = method }) affjaxN (AddHeader header a) = const a <$> modify (\req -> req { headers = header : req.headers }) @@ -45,7 +45,7 @@ affjaxN (SetUsername username a) = const a <$> modify (_ { username = username } affjaxN (SetPassword password a) = const a <$> modify (_ { password = password }) -- | Runs the DSL, producing an `AjaxRequest` object. -affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest +affjaxRequest :: forall c a. AffjaxRequest c a -> AjaxRequest c affjaxRequest = (`execState` defaultRequest) <<< runFreeCM affjaxN -- | Take a standard HTTP verb and allow it to be used as an AJAX request @@ -54,48 +54,48 @@ methodNameFromVerb :: Verb -> MethodName methodNameFromVerb = MethodName <<< show -- | Sets the URL for a request. -url :: String -> AffjaxRequest Unit +url :: forall c. String -> AffjaxRequest c Unit url url = liftFC (SetURL url unit) -- | Sets the request method based on an HTTP verb. -method :: Verb -> AffjaxRequest Unit +method :: forall c. Verb -> AffjaxRequest c Unit method = method' <<< methodNameFromVerb -- | Sets the request method. -method' :: MethodName -> AffjaxRequest Unit +method' :: forall c. MethodName -> AffjaxRequest c Unit method' meth = liftFC (SetMethod meth unit) -- | Adds a header to the request using a key and value. -header :: HeaderHead -> String -> AffjaxRequest Unit +header :: forall c. HeaderHead -> String -> AffjaxRequest c Unit header key value = header' (Header key value) -- | Adds a header to the request using a `Header` record. -header' :: Header -> AffjaxRequest Unit +header' :: forall c. Header -> AffjaxRequest c Unit header' header = liftFC (AddHeader header unit) -- | Sets the content for the request. -content :: Content -> AffjaxRequest Unit +content :: forall c. Content c -> AffjaxRequest c Unit content value = content' (Just value) -- | Sets the content for the request, with the option of setting it to -- | `Nothing`. -content' :: Maybe Content -> AffjaxRequest Unit +content' :: forall c. Maybe (Content c) -> AffjaxRequest c Unit content' value = liftFC (SetContent value unit) -- | Sets the username for the request. -username :: String -> AffjaxRequest Unit +username :: forall c. String -> AffjaxRequest c Unit username value = username' (Just value) -- | Sets the username for the request, with the option of setting it to -- | `Nothing`. -username' :: Maybe String -> AffjaxRequest Unit +username' :: forall c. Maybe String -> AffjaxRequest c Unit username' value = liftFC (SetUsername value unit) -- | Sets the password for the request. -password :: String -> AffjaxRequest Unit +password :: forall c. String -> AffjaxRequest c Unit password value = password' (Just value) -- | Sets the password for the request, with the option of setting it to -- | `Nothing`. -password' :: Maybe String -> AffjaxRequest Unit +password' :: forall c. Maybe String -> AffjaxRequest c Unit password' value = liftFC (SetPassword value unit) diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index ee49f34..6f18767 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -12,20 +12,24 @@ import Control.Monad.Aff (Aff(), EffA(), makeAff) import Control.Monad.Eff (Eff()) import Control.Monad.Eff.Exception(Error()) import Data.Array () +import Data.ArrayBuffer.Types (ArrayView()) import Data.Function (Fn8(), runFn8) import Data.Maybe (Maybe(..), maybe) import Data.Nullable (Nullable(), toNullable) +import DOM (Document()) +import DOM.File (Blob()) +import DOM.XHR (FormData()) import Network.HTTP (Header(..)) -- | The event type for AJAX requests. foreign import data Ajax :: ! -- | The parameters for an AJAX request. -type AjaxRequest = +type AjaxRequest a = { url :: String , method :: MethodName , headers :: [Header] - , content :: Maybe Content + , content :: Maybe (Content a) , username :: Maybe String , password :: Maybe String } @@ -34,14 +38,18 @@ type AjaxRequest = newtype MethodName = MethodName String -- | The types of data that can be set in an AJAX request. --- TODO: how do we want to deal with the various content types? -data Content = Content String +data Content a + = ArrayViewContent (ArrayView a) + | BlobContent Blob + | DocumentContent Document + | TextContent String + | FormDataContent FormData -- TODO: probably not this? Do we want to deal with other responses, include headers, etc? newtype AjaxResponse = AjaxResponse String -- | A basic request, `GET /` with no particular headers or credentials. -defaultRequest :: AjaxRequest +defaultRequest :: forall c. AjaxRequest c defaultRequest = { url: "/" , method: MethodName "GET" @@ -52,7 +60,7 @@ defaultRequest = } -- | Make an AJAX request. -ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse +ajax :: forall e a. AjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse ajax req = makeAff $ runFn8 unsafeAjax req.url (runMethodName req.method) @@ -63,7 +71,21 @@ ajax req = makeAff $ runFn8 where runMethodName (MethodName name) = name runHeader (Header header value) = { header: show header, value: value } - runContent (Content content) = content + runContent :: forall c. Content c -> XHRContent + runContent (ArrayViewContent av) = unsafeToXHRContent av + runContent (BlobContent b) = unsafeToXHRContent b + runContent (DocumentContent d) = unsafeToXHRContent d + runContent (TextContent s) = unsafeToXHRContent s + runContent (FormDataContent fd) = unsafeToXHRContent fd + +foreign import data XHRContent :: * + +foreign import unsafeToXHRContent + """ + function unsafeToXHRContent (value) { + return value; + } + """ :: forall a. a -> XHRContent foreign import unsafeAjax """ @@ -82,12 +104,12 @@ foreign import unsafeAjax } xhr.send(content); } - """ :: forall e a. Fn8 String - String - [{ header :: String, value :: String }] - (Nullable a) - (Nullable String) - (Nullable String) - (Error -> Eff (ajax :: Ajax | e) Unit) - (AjaxResponse -> Eff (ajax :: Ajax | e) Unit) - (EffA (ajax :: Ajax | e) Unit) + """ :: forall e. Fn8 String + String + [{ header :: String, value :: String }] + (Nullable XHRContent) + (Nullable String) + (Nullable String) + (Error -> Eff (ajax :: Ajax | e) Unit) + (AjaxResponse -> Eff (ajax :: Ajax | e) Unit) + (EffA (ajax :: Ajax | e) Unit) From 684226309f80676920312295213c33ded6624fdb Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sun, 15 Mar 2015 16:44:01 +0000 Subject: [PATCH 3/8] Actually run the Eff callbacks --- src/Network/Affjax/Request.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index 6f18767..bcc3fd2 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -96,11 +96,11 @@ foreign import unsafeAjax xhr.setRequestHeader(header.header, header.value); } xhr.onerror = function (err) { - errback(err); + errback(err)(); }; xhr.onload = function () { - if (xhr.status === 200) callback(xhr.response); - else errback(new Error("Request returned status " + xhr.status)); + if (xhr.status === 200) callback(xhr.response)(); + else errback(new Error("Request returned status " + xhr.status))(); } xhr.send(content); } From a56a0759935d3dedaf084b8ad7effce133007b07 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 16 Mar 2015 23:23:09 +0000 Subject: [PATCH 4/8] Avoid `purescript-http` for now --- bower.json | 3 +- src/Network/Affjax/DSL.purs | 18 +++--------- src/Network/Affjax/HTTP.purs | 50 +++++++++++++++++++++++++++++++++ src/Network/Affjax/Request.purs | 15 ++++------ 4 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 src/Network/Affjax/HTTP.purs diff --git a/bower.json b/bower.json index 6ed63fa..3e05acf 100644 --- a/bower.json +++ b/bower.json @@ -26,8 +26,7 @@ "purescript-arrays": "~0.3.3", "purescript-maybe": "~0.2.1", "purescript-nullable": "~0.1.1", - "purescript-http": "~0.2.0", "purescript-arraybuffer-types": "~0.1.1", - "purescript-dom": "#additions" + "purescript-dom": "~0.1.2" } } diff --git a/src/Network/Affjax/DSL.purs b/src/Network/Affjax/DSL.purs index 1a37add..79040ba 100644 --- a/src/Network/Affjax/DSL.purs +++ b/src/Network/Affjax/DSL.purs @@ -4,7 +4,6 @@ module Network.Affjax.DSL , affjaxRequest , url , method - , method' , header , header' , content @@ -20,8 +19,8 @@ import Control.Monad.State (State(), execState) import Control.Monad.State.Class (modify) import Data.Coyoneda (Natural()) import Data.Maybe (Maybe(..)) +import Network.Affjax.HTTP import Network.Affjax.Request -import Network.HTTP (Verb(..), Header(..), HeaderHead()) -- | A free monad for building AJAX requests type AffjaxRequest c = FreeC (AffjaxRequestF c) @@ -29,7 +28,7 @@ type AffjaxRequest c = FreeC (AffjaxRequestF c) -- | The request DSL AST. data AffjaxRequestF c a = SetURL String a - | SetMethod MethodName a + | SetMethod Method a | AddHeader Header a | SetContent (Maybe (Content c)) a | SetUsername (Maybe String) a @@ -48,22 +47,13 @@ affjaxN (SetPassword password a) = const a <$> modify (_ { password = password } affjaxRequest :: forall c a. AffjaxRequest c a -> AjaxRequest c affjaxRequest = (`execState` defaultRequest) <<< runFreeCM affjaxN --- | Take a standard HTTP verb and allow it to be used as an AJAX request --- | method. -methodNameFromVerb :: Verb -> MethodName -methodNameFromVerb = MethodName <<< show - -- | Sets the URL for a request. url :: forall c. String -> AffjaxRequest c Unit url url = liftFC (SetURL url unit) -- | Sets the request method based on an HTTP verb. -method :: forall c. Verb -> AffjaxRequest c Unit -method = method' <<< methodNameFromVerb - --- | Sets the request method. -method' :: forall c. MethodName -> AffjaxRequest c Unit -method' meth = liftFC (SetMethod meth unit) +method :: forall c. Method -> AffjaxRequest c Unit +method meth = liftFC (SetMethod meth unit) -- | Adds a header to the request using a key and value. header :: forall c. HeaderHead -> String -> AffjaxRequest c Unit diff --git a/src/Network/Affjax/HTTP.purs b/src/Network/Affjax/HTTP.purs new file mode 100644 index 0000000..a878697 --- /dev/null +++ b/src/Network/Affjax/HTTP.purs @@ -0,0 +1,50 @@ +module Network.Affjax.HTTP where + +data Method + = DELETE + | GET + | HEAD + | OPTIONS + | PATCH + | POST + | PUT + | CustomMethod String + +instance eqMethod :: Eq Method where + (==) DELETE DELETE = true + (==) GET GET = true + (==) HEAD HEAD = true + (==) OPTIONS OPTIONS = true + (==) PATCH PATCH = true + (==) POST POST = true + (==) PUT PUT = true + (==) _ _ = false + (/=) x y = not (x == y) + +instance showMethod :: Show Method where + show DELETE = "DELETE" + show GET = "GET" + show HEAD = "HEAD" + show OPTIONS = "OPTIONS" + show PATCH = "PATCH" + show POST = "POST" + show PUT = "PUT" + show (CustomMethod m) = "(CustomMethod " ++ show m ++ ")" + +newtype HeaderHead = HeaderHead String + +instance eqHeaderHead :: Eq HeaderHead where + (==) (HeaderHead x) (HeaderHead y) = x == y + (/=) (HeaderHead x) (HeaderHead y) = x /= y + +instance showHeaderHead :: Show HeaderHead where + show (HeaderHead h) = "(HeaderHead " ++ show h ++ ")" + +data Header = Header HeaderHead String + +instance eqHeader :: Eq Header where + (==) (Header hx x) (Header hy y) = hx == hy && x == y + (/=) (Header hx x) (Header hy y) = hx /= hy || x /= y + +instance showHeader :: Show Header where + show (Header hh h) = "(Header " ++ show hh ++ " " ++ show h ++ ")" diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index bcc3fd2..2aee9d5 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -1,7 +1,6 @@ module Network.Affjax.Request ( Ajax() , AjaxRequest() - , MethodName(..) , Content(..) , AjaxResponse() , defaultRequest @@ -19,7 +18,7 @@ import Data.Nullable (Nullable(), toNullable) import DOM (Document()) import DOM.File (Blob()) import DOM.XHR (FormData()) -import Network.HTTP (Header(..)) +import Network.Affjax.HTTP -- | The event type for AJAX requests. foreign import data Ajax :: ! @@ -27,16 +26,13 @@ foreign import data Ajax :: ! -- | The parameters for an AJAX request. type AjaxRequest a = { url :: String - , method :: MethodName + , method :: Method , headers :: [Header] , content :: Maybe (Content a) , username :: Maybe String , password :: Maybe String } --- | A HTTP method name: `GET`, `POST`, etc. -newtype MethodName = MethodName String - -- | The types of data that can be set in an AJAX request. data Content a = ArrayViewContent (ArrayView a) @@ -52,7 +48,7 @@ newtype AjaxResponse = AjaxResponse String defaultRequest :: forall c. AjaxRequest c defaultRequest = { url: "/" - , method: MethodName "GET" + , method: GET , headers: [] , content: Nothing , username: Nothing @@ -69,7 +65,8 @@ ajax req = makeAff $ runFn8 (toNullable req.username) (toNullable req.password) where - runMethodName (MethodName name) = name + runMethodName (CustomMethod name) = name + runMethodName method = show method runHeader (Header header value) = { header: show header, value: value } runContent :: forall c. Content c -> XHRContent runContent (ArrayViewContent av) = unsafeToXHRContent av @@ -101,7 +98,7 @@ foreign import unsafeAjax xhr.onload = function () { if (xhr.status === 200) callback(xhr.response)(); else errback(new Error("Request returned status " + xhr.status))(); - } + }; xhr.send(content); } """ :: forall e. Fn8 String From 579d3853e0643c75e5a64db3e4391bcd56a15563 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 18 Mar 2015 23:23:29 +0000 Subject: [PATCH 5/8] Reformulate headers, add mime types --- src/Network/Affjax/DSL.purs | 16 +++----- src/Network/Affjax/Request.purs | 16 ++++---- .../{Affjax/HTTP.purs => HTTP/Method.purs} | 22 ++--------- src/Network/HTTP/MimeType.purs | 13 +++++++ src/Network/HTTP/MimeType/Common.purs | 39 +++++++++++++++++++ src/Network/HTTP/RequestHeader.purs | 24 ++++++++++++ src/Network/HTTP/ResponseHeader.purs | 16 ++++++++ 7 files changed, 110 insertions(+), 36 deletions(-) rename src/Network/{Affjax/HTTP.purs => HTTP/Method.purs} (52%) create mode 100644 src/Network/HTTP/MimeType.purs create mode 100644 src/Network/HTTP/MimeType/Common.purs create mode 100644 src/Network/HTTP/RequestHeader.purs create mode 100644 src/Network/HTTP/ResponseHeader.purs diff --git a/src/Network/Affjax/DSL.purs b/src/Network/Affjax/DSL.purs index 79040ba..2f1e391 100644 --- a/src/Network/Affjax/DSL.purs +++ b/src/Network/Affjax/DSL.purs @@ -5,7 +5,6 @@ module Network.Affjax.DSL , url , method , header - , header' , content , content' , username @@ -19,8 +18,9 @@ import Control.Monad.State (State(), execState) import Control.Monad.State.Class (modify) import Data.Coyoneda (Natural()) import Data.Maybe (Maybe(..)) -import Network.Affjax.HTTP import Network.Affjax.Request +import Network.HTTP.Method +import Network.HTTP.RequestHeader -- | A free monad for building AJAX requests type AffjaxRequest c = FreeC (AffjaxRequestF c) @@ -29,7 +29,7 @@ type AffjaxRequest c = FreeC (AffjaxRequestF c) data AffjaxRequestF c a = SetURL String a | SetMethod Method a - | AddHeader Header a + | AddHeader RequestHeader a | SetContent (Maybe (Content c)) a | SetUsername (Maybe String) a | SetPassword (Maybe String) a @@ -55,13 +55,9 @@ url url = liftFC (SetURL url unit) method :: forall c. Method -> AffjaxRequest c Unit method meth = liftFC (SetMethod meth unit) --- | Adds a header to the request using a key and value. -header :: forall c. HeaderHead -> String -> AffjaxRequest c Unit -header key value = header' (Header key value) - --- | Adds a header to the request using a `Header` record. -header' :: forall c. Header -> AffjaxRequest c Unit -header' header = liftFC (AddHeader header unit) +-- | Adds a header to the request. +header :: forall c. RequestHeader -> AffjaxRequest c Unit +header header = liftFC (AddHeader header unit) -- | Sets the content for the request. content :: forall c. Content c -> AffjaxRequest c Unit diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index 2aee9d5..e652dbb 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -18,7 +18,8 @@ import Data.Nullable (Nullable(), toNullable) import DOM (Document()) import DOM.File (Blob()) import DOM.XHR (FormData()) -import Network.Affjax.HTTP +import Network.HTTP.Method +import Network.HTTP.RequestHeader -- | The event type for AJAX requests. foreign import data Ajax :: ! @@ -27,7 +28,7 @@ foreign import data Ajax :: ! type AjaxRequest a = { url :: String , method :: Method - , headers :: [Header] + , headers :: [RequestHeader] , content :: Maybe (Content a) , username :: Maybe String , password :: Maybe String @@ -59,15 +60,14 @@ defaultRequest = ajax :: forall e a. AjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse ajax req = makeAff $ runFn8 unsafeAjax req.url - (runMethodName req.method) + (methodToString req.method) (runHeader <$> req.headers) (toNullable $ runContent <$> req.content) (toNullable req.username) (toNullable req.password) where - runMethodName (CustomMethod name) = name - runMethodName method = show method - runHeader (Header header value) = { header: show header, value: value } + runHeader :: RequestHeader -> { head :: String, value :: String } + runHeader h = { head: requestHeaderName h, value: requestHeaderValue h } runContent :: forall c. Content c -> XHRContent runContent (ArrayViewContent av) = unsafeToXHRContent av runContent (BlobContent b) = unsafeToXHRContent b @@ -90,7 +90,7 @@ foreign import unsafeAjax var xhr = new XMLHttpRequest(); xhr.open(method, url, true, username, password); for (var i = 0, header; header = headers[i]; i++) { - xhr.setRequestHeader(header.header, header.value); + xhr.setRequestHeader(header.head, header.value); } xhr.onerror = function (err) { errback(err)(); @@ -103,7 +103,7 @@ foreign import unsafeAjax } """ :: forall e. Fn8 String String - [{ header :: String, value :: String }] + [{ head :: String, value :: String }] (Nullable XHRContent) (Nullable String) (Nullable String) diff --git a/src/Network/Affjax/HTTP.purs b/src/Network/HTTP/Method.purs similarity index 52% rename from src/Network/Affjax/HTTP.purs rename to src/Network/HTTP/Method.purs index a878697..a9a1490 100644 --- a/src/Network/Affjax/HTTP.purs +++ b/src/Network/HTTP/Method.purs @@ -1,4 +1,4 @@ -module Network.Affjax.HTTP where +module Network.HTTP.Method where data Method = DELETE @@ -31,20 +31,6 @@ instance showMethod :: Show Method where show PUT = "PUT" show (CustomMethod m) = "(CustomMethod " ++ show m ++ ")" -newtype HeaderHead = HeaderHead String - -instance eqHeaderHead :: Eq HeaderHead where - (==) (HeaderHead x) (HeaderHead y) = x == y - (/=) (HeaderHead x) (HeaderHead y) = x /= y - -instance showHeaderHead :: Show HeaderHead where - show (HeaderHead h) = "(HeaderHead " ++ show h ++ ")" - -data Header = Header HeaderHead String - -instance eqHeader :: Eq Header where - (==) (Header hx x) (Header hy y) = hx == hy && x == y - (/=) (Header hx x) (Header hy y) = hx /= hy || x /= y - -instance showHeader :: Show Header where - show (Header hh h) = "(Header " ++ show hh ++ " " ++ show h ++ ")" +methodToString :: Method -> String +methodToString (CustomMethod m) = m +methodToString other = show other diff --git a/src/Network/HTTP/MimeType.purs b/src/Network/HTTP/MimeType.purs new file mode 100644 index 0000000..5e8acc2 --- /dev/null +++ b/src/Network/HTTP/MimeType.purs @@ -0,0 +1,13 @@ +module Network.HTTP.MimeType where + +newtype MimeType = MimeType String + +instance eqMimeType :: Eq MimeType where + (==) (MimeType x) (MimeType y) = x == y + (/=) (MimeType x) (MimeType y) = x /= y + +instance showMimeType :: Show MimeType where + show (MimeType h) = "(MimeType " ++ show h ++ ")" + +mimeTypeToString :: MimeType -> String +mimeTypeToString (MimeType s) = s diff --git a/src/Network/HTTP/MimeType/Common.purs b/src/Network/HTTP/MimeType/Common.purs new file mode 100644 index 0000000..5a7d678 --- /dev/null +++ b/src/Network/HTTP/MimeType/Common.purs @@ -0,0 +1,39 @@ +module Network.HTTP.MimeType.Common where + +import Network.HTTP.MimeType + +applicationFormURLEncoded :: MimeType +applicationFormURLEncoded = MimeType "application/x-www-form-urlencoded" + +applicationJSON :: MimeType +applicationJSON = MimeType "application/json" + +applicationJavascript :: MimeType +applicationJavascript = MimeType "application/javascript" + +applicationOctetStream :: MimeType +applicationOctetStream = MimeType "application/octet-stream" + +applicationXML :: MimeType +applicationXML = MimeType "application/xml" + +imageGIF :: MimeType +imageGIF = MimeType "image/gif" + +imageJPEG :: MimeType +imageJPEG = MimeType "image/jpeg" + +imagePNG :: MimeType +imagePNG = MimeType "image/png" + +multipartFormData :: MimeType +multipartFormData = MimeType "multipart/form-data" + +textCSV :: MimeType +textCSV = MimeType "text/CSV" + +textPlain :: MimeType +textPlain = MimeType "text/plain" + +textXML :: MimeType +textXML = MimeType "text/xml" diff --git a/src/Network/HTTP/RequestHeader.purs b/src/Network/HTTP/RequestHeader.purs new file mode 100644 index 0000000..a03e679 --- /dev/null +++ b/src/Network/HTTP/RequestHeader.purs @@ -0,0 +1,24 @@ +module Network.HTTP.RequestHeader where + +import Network.HTTP.MimeType + +data RequestHeader + = ContentType MimeType + | RequestHeader String String + +instance eqRequestHeader :: Eq RequestHeader where + (==) (ContentType m1) (ContentType m2) = m1 == m2 + (==) (RequestHeader h1 v1) (RequestHeader h2 v2) = h1 == h2 && v1 == v2 + (/=) x y = not (x == y) + +instance showRequestHeader :: Show RequestHeader where + show (ContentType m) = "(ContentType " ++ show m ++ ")" + show (RequestHeader h v) = "(RequestHeader " ++ show h ++ " " ++ show v ++ ")" + +requestHeaderName :: RequestHeader -> String +requestHeaderName (ContentType _) = "ContentType" +requestHeaderName (RequestHeader h _) = h + +requestHeaderValue :: RequestHeader -> String +requestHeaderValue (ContentType m) = mimeTypeToString m +requestHeaderValue (RequestHeader _ v) = v diff --git a/src/Network/HTTP/ResponseHeader.purs b/src/Network/HTTP/ResponseHeader.purs new file mode 100644 index 0000000..a80d8b8 --- /dev/null +++ b/src/Network/HTTP/ResponseHeader.purs @@ -0,0 +1,16 @@ +module Network.HTTP.ResponseHeader where + +data ResponseHeader = ResponseHeader String String + +instance eqResponseHeader :: Eq ResponseHeader where + (==) (ResponseHeader h1 v1) (ResponseHeader h2 v2) = h1 == h2 && v1 == v2 + (/=) x y = not (x == y) + +instance showResponseHeader :: Show ResponseHeader where + show (ResponseHeader h v) = "(ResponseHeader " ++ show h ++ " " ++ show v ++ ")" + +responseHeaderName :: ResponseHeader -> String +responseHeaderName (ResponseHeader h _) = h + +responseHeaderValue :: ResponseHeader -> String +responseHeaderValue (ResponseHeader _ v) = v From ffbbebafa05822e581cde43c9f8d9a2c582894b1 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 18 Mar 2015 23:27:56 +0000 Subject: [PATCH 6/8] Add Accept header, fix Eq --- src/Network/HTTP/RequestHeader.purs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Network/HTTP/RequestHeader.purs b/src/Network/HTTP/RequestHeader.purs index a03e679..9bf790d 100644 --- a/src/Network/HTTP/RequestHeader.purs +++ b/src/Network/HTTP/RequestHeader.purs @@ -3,22 +3,28 @@ module Network.HTTP.RequestHeader where import Network.HTTP.MimeType data RequestHeader - = ContentType MimeType + = Accept MimeType + | ContentType MimeType | RequestHeader String String instance eqRequestHeader :: Eq RequestHeader where + (==) (Accept m1) (Accept m2) = m1 == m2 (==) (ContentType m1) (ContentType m2) = m1 == m2 (==) (RequestHeader h1 v1) (RequestHeader h2 v2) = h1 == h2 && v1 == v2 + (==) _ _ = false (/=) x y = not (x == y) instance showRequestHeader :: Show RequestHeader where + show (Accept m) = "(Accept " ++ show m ++ ")" show (ContentType m) = "(ContentType " ++ show m ++ ")" show (RequestHeader h v) = "(RequestHeader " ++ show h ++ " " ++ show v ++ ")" requestHeaderName :: RequestHeader -> String +requestHeaderName (Accept _) = "Accept" requestHeaderName (ContentType _) = "ContentType" requestHeaderName (RequestHeader h _) = h requestHeaderValue :: RequestHeader -> String +requestHeaderValue (Accept m) = mimeTypeToString m requestHeaderValue (ContentType m) = mimeTypeToString m requestHeaderValue (RequestHeader _ v) = v From 57b1ad98b0f8ff4bcaa22c6ddb2d7ec5f7a48d4d Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sat, 21 Mar 2015 23:24:38 +0000 Subject: [PATCH 7/8] Request content again --- README.md | 334 ++++++++++++++++++++++---- src/Network/Affjax.purs | 3 +- src/Network/Affjax/DSL.purs | 35 +-- src/Network/Affjax/Request.purs | 51 +--- src/Network/Affjax/Requestable.purs | 74 ++++++ src/Network/HTTP/MimeType/Common.purs | 5 +- 6 files changed, 397 insertions(+), 105 deletions(-) create mode 100644 src/Network/Affjax/Requestable.purs diff --git a/README.md b/README.md index 73b4a63..7ed66c0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ #### `runAffjax` ``` purescript -runAffjax :: forall e c a. AffjaxRequest c a -> Aff (ajax :: Ajax | e) AjaxResponse +runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse ``` @@ -15,7 +15,7 @@ runAffjax :: forall e c a. AffjaxRequest c a -> Aff (ajax :: Ajax | e) AjaxRespo #### `AffjaxRequest` ``` purescript -type AffjaxRequest c = FreeC (AffjaxRequestF c) +type AffjaxRequest = FreeC AffjaxRequestF ``` A free monad for building AJAX requests @@ -23,11 +23,11 @@ A free monad for building AJAX requests #### `AffjaxRequestF` ``` purescript -data AffjaxRequestF c a +data AffjaxRequestF a = SetURL String a - | SetMethod MethodName a - | AddHeader Header a - | SetContent (Maybe (Content c)) a + | SetMethod Method a + | AddHeader RequestHeader a + | SetContent (Maybe Content) a | SetUsername (Maybe String) a | SetPassword (Maybe String) a ``` @@ -37,7 +37,7 @@ The request DSL AST. #### `affjaxRequest` ``` purescript -affjaxRequest :: forall c a. AffjaxRequest c a -> AjaxRequest c +affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest ``` Runs the DSL, producing an `AjaxRequest` object. @@ -45,7 +45,7 @@ Runs the DSL, producing an `AjaxRequest` object. #### `url` ``` purescript -url :: forall c. String -> AffjaxRequest c Unit +url :: String -> AffjaxRequest Unit ``` Sets the URL for a request. @@ -53,39 +53,23 @@ Sets the URL for a request. #### `method` ``` purescript -method :: forall c. Verb -> AffjaxRequest c Unit +method :: Method -> AffjaxRequest Unit ``` Sets the request method based on an HTTP verb. -#### `method'` - -``` purescript -method' :: forall c. MethodName -> AffjaxRequest c Unit -``` - -Sets the request method. - #### `header` ``` purescript -header :: forall c. HeaderHead -> String -> AffjaxRequest c Unit +header :: RequestHeader -> AffjaxRequest Unit ``` -Adds a header to the request using a key and value. - -#### `header'` - -``` purescript -header' :: forall c. Header -> AffjaxRequest c Unit -``` - -Adds a header to the request using a `Header` record. +Adds a header to the request. #### `content` ``` purescript -content :: forall c. Content c -> AffjaxRequest c Unit +content :: Content -> AffjaxRequest Unit ``` Sets the content for the request. @@ -93,7 +77,7 @@ Sets the content for the request. #### `content'` ``` purescript -content' :: forall c. Maybe (Content c) -> AffjaxRequest c Unit +content' :: Maybe Content -> AffjaxRequest Unit ``` Sets the content for the request, with the option of setting it to @@ -102,7 +86,7 @@ Sets the content for the request, with the option of setting it to #### `username` ``` purescript -username :: forall c. String -> AffjaxRequest c Unit +username :: String -> AffjaxRequest Unit ``` Sets the username for the request. @@ -110,7 +94,7 @@ Sets the username for the request. #### `username'` ``` purescript -username' :: forall c. Maybe String -> AffjaxRequest c Unit +username' :: Maybe String -> AffjaxRequest Unit ``` Sets the username for the request, with the option of setting it to @@ -119,7 +103,7 @@ Sets the username for the request, with the option of setting it to #### `password` ``` purescript -password :: forall c. String -> AffjaxRequest c Unit +password :: String -> AffjaxRequest Unit ``` Sets the password for the request. @@ -127,7 +111,7 @@ Sets the password for the request. #### `password'` ``` purescript -password' :: forall c. Maybe String -> AffjaxRequest c Unit +password' :: Maybe String -> AffjaxRequest Unit ``` Sets the password for the request, with the option of setting it to @@ -147,31 +131,24 @@ The event type for AJAX requests. #### `AjaxRequest` ``` purescript -type AjaxRequest a = { password :: Maybe String, username :: Maybe String, content :: Maybe (Content a), headers :: [Header], method :: MethodName, url :: String } +type AjaxRequest = { password :: Maybe String, username :: Maybe String, content :: Maybe Content, headers :: [RequestHeader], method :: Method, url :: String } ``` The parameters for an AJAX request. -#### `MethodName` - -``` purescript -newtype MethodName - = MethodName String -``` - -A HTTP method name: `GET`, `POST`, etc. - #### `Content` ``` purescript -data Content a - = ArrayViewContent (ArrayView a) +data Content + = ArrayViewContent (Exists ArrayView) | BlobContent Blob | DocumentContent Document | TextContent String | FormDataContent FormData ``` +The types of data that can be set in an AJAX request. + #### `AjaxResponse` ``` purescript @@ -181,7 +158,7 @@ newtype AjaxResponse #### `defaultRequest` ``` purescript -defaultRequest :: forall c. AjaxRequest c +defaultRequest :: AjaxRequest ``` A basic request, `GET /` with no particular headers or credentials. @@ -189,10 +166,273 @@ A basic request, `GET /` with no particular headers or credentials. #### `ajax` ``` purescript -ajax :: forall e a. AjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse ``` Make an AJAX request. +## Module Network.HTTP.Method + +#### `Method` + +``` purescript +data Method + = DELETE + | GET + | HEAD + | OPTIONS + | PATCH + | POST + | PUT + | CustomMethod String +``` + + +#### `eqMethod` + +``` purescript +instance eqMethod :: Eq Method +``` + + +#### `showMethod` + +``` purescript +instance showMethod :: Show Method +``` + + +#### `methodToString` + +``` purescript +methodToString :: Method -> String +``` + + + +## Module Network.HTTP.MimeType + +#### `MimeType` + +``` purescript +newtype MimeType + = MimeType String +``` + + +#### `eqMimeType` + +``` purescript +instance eqMimeType :: Eq MimeType +``` + + +#### `showMimeType` + +``` purescript +instance showMimeType :: Show MimeType +``` + + +#### `mimeTypeToString` + +``` purescript +mimeTypeToString :: MimeType -> String +``` + + + +## Module Network.HTTP.RequestHeader + +#### `RequestHeader` + +``` purescript +data RequestHeader + = Accept MimeType + | ContentType MimeType + | RequestHeader String String +``` + + +#### `eqRequestHeader` + +``` purescript +instance eqRequestHeader :: Eq RequestHeader +``` + + +#### `showRequestHeader` + +``` purescript +instance showRequestHeader :: Show RequestHeader +``` + + +#### `requestHeaderName` + +``` purescript +requestHeaderName :: RequestHeader -> String +``` + + +#### `requestHeaderValue` + +``` purescript +requestHeaderValue :: RequestHeader -> String +``` + + + +## Module Network.HTTP.ResponseHeader + +#### `ResponseHeader` + +``` purescript +data ResponseHeader + = ResponseHeader String String +``` + + +#### `eqResponseHeader` + +``` purescript +instance eqResponseHeader :: Eq ResponseHeader +``` + + +#### `showResponseHeader` + +``` purescript +instance showResponseHeader :: Show ResponseHeader +``` + + +#### `responseHeaderName` + +``` purescript +responseHeaderName :: ResponseHeader -> String +``` + + +#### `responseHeaderValue` + +``` purescript +responseHeaderValue :: ResponseHeader -> String +``` + + + +## Module Network.HTTP.StatusCode + +#### `StatusCode` + +``` purescript +newtype StatusCode + = StatusCode Int +``` + + +#### `eqStatusCode` + +``` purescript +instance eqStatusCode :: Eq StatusCode +``` + + +#### `showStatusCode` + +``` purescript +instance showStatusCode :: Show StatusCode +``` + + + +## Module Network.HTTP.MimeType.Common + +#### `applicationFormURLEncoded` + +``` purescript +applicationFormURLEncoded :: MimeType +``` + + +#### `applicationJSON` + +``` purescript +applicationJSON :: MimeType +``` + + +#### `applicationJavascript` + +``` purescript +applicationJavascript :: MimeType +``` + + +#### `applicationOctetStream` + +``` purescript +applicationOctetStream :: MimeType +``` + + +#### `applicationXML` + +``` purescript +applicationXML :: MimeType +``` + + +#### `imageGIF` + +``` purescript +imageGIF :: MimeType +``` + + +#### `imageJPEG` + +``` purescript +imageJPEG :: MimeType +``` + + +#### `imagePNG` + +``` purescript +imagePNG :: MimeType +``` + + +#### `multipartFormData` + +``` purescript +multipartFormData :: MimeType +``` + + +#### `textCSV` + +``` purescript +textCSV :: MimeType +``` + + +#### `textPlain` + +``` purescript +textPlain :: MimeType +``` + + +#### `textXML` + +``` purescript +textXML :: MimeType +``` + + + diff --git a/src/Network/Affjax.purs b/src/Network/Affjax.purs index 5e5be87..6bf7ebd 100644 --- a/src/Network/Affjax.purs +++ b/src/Network/Affjax.purs @@ -3,7 +3,6 @@ module Network.Affjax where import Control.Monad.Aff import Network.Affjax.DSL import Network.Affjax.Request -import Data.ArrayBuffer.Types -runAffjax :: forall e c a. AffjaxRequest c a -> Aff (ajax :: Ajax | e) AjaxResponse +runAffjax :: forall e a. AffjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse runAffjax = ajax <<< affjaxRequest diff --git a/src/Network/Affjax/DSL.purs b/src/Network/Affjax/DSL.purs index 2f1e391..5345a7b 100644 --- a/src/Network/Affjax/DSL.purs +++ b/src/Network/Affjax/DSL.purs @@ -19,23 +19,24 @@ import Control.Monad.State.Class (modify) import Data.Coyoneda (Natural()) import Data.Maybe (Maybe(..)) import Network.Affjax.Request -import Network.HTTP.Method -import Network.HTTP.RequestHeader +import Network.Affjax.Requestable +import Network.HTTP.Method (Method()) +import Network.HTTP.RequestHeader (RequestHeader()) -- | A free monad for building AJAX requests -type AffjaxRequest c = FreeC (AffjaxRequestF c) +type AffjaxRequest = FreeC AffjaxRequestF -- | The request DSL AST. -data AffjaxRequestF c a +data AffjaxRequestF a = SetURL String a | SetMethod Method a | AddHeader RequestHeader a - | SetContent (Maybe (Content c)) a + | SetContent (Maybe AjaxContent) a | SetUsername (Maybe String) a | SetPassword (Maybe String) a -- | The interpreter for the request DSL AST. -affjaxN :: forall c. Natural (AffjaxRequestF c) (State (AjaxRequest c)) +affjaxN :: Natural AffjaxRequestF (State AjaxRequest) affjaxN (SetURL url a) = const a <$> modify (_ { url = url }) affjaxN (SetMethod method a) = const a <$> modify (_ { method = method }) affjaxN (AddHeader header a) = const a <$> modify (\req -> req { headers = header : req.headers }) @@ -44,44 +45,44 @@ affjaxN (SetUsername username a) = const a <$> modify (_ { username = username } affjaxN (SetPassword password a) = const a <$> modify (_ { password = password }) -- | Runs the DSL, producing an `AjaxRequest` object. -affjaxRequest :: forall c a. AffjaxRequest c a -> AjaxRequest c +affjaxRequest :: forall a. AffjaxRequest a -> AjaxRequest affjaxRequest = (`execState` defaultRequest) <<< runFreeCM affjaxN -- | Sets the URL for a request. -url :: forall c. String -> AffjaxRequest c Unit +url :: String -> AffjaxRequest Unit url url = liftFC (SetURL url unit) -- | Sets the request method based on an HTTP verb. -method :: forall c. Method -> AffjaxRequest c Unit +method :: Method -> AffjaxRequest Unit method meth = liftFC (SetMethod meth unit) -- | Adds a header to the request. -header :: forall c. RequestHeader -> AffjaxRequest c Unit +header :: RequestHeader -> AffjaxRequest Unit header header = liftFC (AddHeader header unit) -- | Sets the content for the request. -content :: forall c. Content c -> AffjaxRequest c Unit +content :: forall c. (AjaxRequestable c) => c -> AffjaxRequest Unit content value = content' (Just value) -- | Sets the content for the request, with the option of setting it to -- | `Nothing`. -content' :: forall c. Maybe (Content c) -> AffjaxRequest c Unit -content' value = liftFC (SetContent value unit) +content' :: forall c. (AjaxRequestable c) => Maybe c -> AffjaxRequest Unit +content' value = liftFC (SetContent (toContent <$> value) unit) -- | Sets the username for the request. -username :: forall c. String -> AffjaxRequest c Unit +username :: String -> AffjaxRequest Unit username value = username' (Just value) -- | Sets the username for the request, with the option of setting it to -- | `Nothing`. -username' :: forall c. Maybe String -> AffjaxRequest c Unit +username' :: Maybe String -> AffjaxRequest Unit username' value = liftFC (SetUsername value unit) -- | Sets the password for the request. -password :: forall c. String -> AffjaxRequest c Unit +password :: String -> AffjaxRequest Unit password value = password' (Just value) -- | Sets the password for the request, with the option of setting it to -- | `Nothing`. -password' :: forall c. Maybe String -> AffjaxRequest c Unit +password' :: Maybe String -> AffjaxRequest Unit password' value = liftFC (SetPassword value unit) diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index e652dbb..25218d0 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -1,7 +1,7 @@ module Network.Affjax.Request ( Ajax() + , AjaxContent() , AjaxRequest() - , Content(..) , AjaxResponse() , defaultRequest , ajax @@ -9,44 +9,34 @@ module Network.Affjax.Request import Control.Monad.Aff (Aff(), EffA(), makeAff) import Control.Monad.Eff (Eff()) -import Control.Monad.Eff.Exception(Error()) -import Data.Array () -import Data.ArrayBuffer.Types (ArrayView()) +import Control.Monad.Eff.Exception (Error()) import Data.Function (Fn8(), runFn8) import Data.Maybe (Maybe(..), maybe) import Data.Nullable (Nullable(), toNullable) -import DOM (Document()) -import DOM.File (Blob()) -import DOM.XHR (FormData()) -import Network.HTTP.Method -import Network.HTTP.RequestHeader +import Network.HTTP.Method (Method(..), methodToString) +import Network.HTTP.RequestHeader (RequestHeader(), requestHeaderName, requestHeaderValue) -- | The event type for AJAX requests. foreign import data Ajax :: ! +-- | Type subsuming all content types that can be sent in a request. +foreign import data AjaxContent :: * + -- | The parameters for an AJAX request. -type AjaxRequest a = +type AjaxRequest = { url :: String , method :: Method , headers :: [RequestHeader] - , content :: Maybe (Content a) + , content :: Maybe AjaxContent , username :: Maybe String , password :: Maybe String } --- | The types of data that can be set in an AJAX request. -data Content a - = ArrayViewContent (ArrayView a) - | BlobContent Blob - | DocumentContent Document - | TextContent String - | FormDataContent FormData - -- TODO: probably not this? Do we want to deal with other responses, include headers, etc? newtype AjaxResponse = AjaxResponse String -- | A basic request, `GET /` with no particular headers or credentials. -defaultRequest :: forall c. AjaxRequest c +defaultRequest :: AjaxRequest defaultRequest = { url: "/" , method: GET @@ -57,32 +47,17 @@ defaultRequest = } -- | Make an AJAX request. -ajax :: forall e a. AjaxRequest a -> Aff (ajax :: Ajax | e) AjaxResponse +ajax :: forall e. AjaxRequest -> Aff (ajax :: Ajax | e) AjaxResponse ajax req = makeAff $ runFn8 unsafeAjax req.url (methodToString req.method) (runHeader <$> req.headers) - (toNullable $ runContent <$> req.content) + (toNullable req.content) (toNullable req.username) (toNullable req.password) where runHeader :: RequestHeader -> { head :: String, value :: String } runHeader h = { head: requestHeaderName h, value: requestHeaderValue h } - runContent :: forall c. Content c -> XHRContent - runContent (ArrayViewContent av) = unsafeToXHRContent av - runContent (BlobContent b) = unsafeToXHRContent b - runContent (DocumentContent d) = unsafeToXHRContent d - runContent (TextContent s) = unsafeToXHRContent s - runContent (FormDataContent fd) = unsafeToXHRContent fd - -foreign import data XHRContent :: * - -foreign import unsafeToXHRContent - """ - function unsafeToXHRContent (value) { - return value; - } - """ :: forall a. a -> XHRContent foreign import unsafeAjax """ @@ -104,7 +79,7 @@ foreign import unsafeAjax """ :: forall e. Fn8 String String [{ head :: String, value :: String }] - (Nullable XHRContent) + (Nullable AjaxContent) (Nullable String) (Nullable String) (Error -> Eff (ajax :: Ajax | e) Unit) diff --git a/src/Network/Affjax/Requestable.purs b/src/Network/Affjax/Requestable.purs new file mode 100644 index 0000000..e9dc01c --- /dev/null +++ b/src/Network/Affjax/Requestable.purs @@ -0,0 +1,74 @@ +module Network.Affjax.Requestable + ( AjaxRequestable, requestMimeType, toContent + ) where + +import DOM (Document()) +import DOM.File (Blob()) +import DOM.XHR (FormData()) +import Network.Affjax.Request +import Network.HTTP.MimeType (MimeType()) +import Network.HTTP.MimeType.Common (applicationOctetStream, multipartFormData, textHTML, textPlain) +import qualified Data.ArrayBuffer.Types as A + +class AjaxRequestable a where + requestMimeType :: a -> MimeType + toContent :: a -> AjaxContent + +instance requestableInt8Array :: AjaxRequestable (A.ArrayView A.Int8) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableInt16Array :: AjaxRequestable (A.ArrayView A.Int16) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableInt32Array :: AjaxRequestable (A.ArrayView A.Int32) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableUint8Array :: AjaxRequestable (A.ArrayView A.Uint8) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableUint16Array :: AjaxRequestable (A.ArrayView A.Uint16) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableUint32Array :: AjaxRequestable (A.ArrayView A.Uint32) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableUint8ClampedArray :: AjaxRequestable (A.ArrayView A.Uint8Clamped) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableFloat32Array :: AjaxRequestable (A.ArrayView A.Float32) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableFloat64Array :: AjaxRequestable (A.ArrayView A.Float64) where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableBlob :: AjaxRequestable Blob where + requestMimeType = const applicationOctetStream + toContent = unsafeToContent + +instance requestableDocument :: AjaxRequestable Document where + requestMimeType = const textHTML + toContent = unsafeToContent + +instance requestableString :: AjaxRequestable String where + requestMimeType = const textPlain + toContent = unsafeToContent + +instance requestableFormData :: AjaxRequestable FormData where + requestMimeType = const multipartFormData + toContent = unsafeToContent + +foreign import unsafeToContent + """ + function unsafeToContent (x) { + return x; + } + """ :: forall a. a -> AjaxContent diff --git a/src/Network/HTTP/MimeType/Common.purs b/src/Network/HTTP/MimeType/Common.purs index 5a7d678..976f328 100644 --- a/src/Network/HTTP/MimeType/Common.purs +++ b/src/Network/HTTP/MimeType/Common.purs @@ -30,7 +30,10 @@ multipartFormData :: MimeType multipartFormData = MimeType "multipart/form-data" textCSV :: MimeType -textCSV = MimeType "text/CSV" +textCSV = MimeType "text/csv" + +textHTML :: MimeType +textHTML = MimeType "text/html" textPlain :: MimeType textPlain = MimeType "text/plain" From 3f55ade3bdf3e3abd11612607497eff08f04335a Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sun, 22 Mar 2015 12:57:03 +0000 Subject: [PATCH 8/8] Prevent unsafeAjax from evaluating too early --- src/Network/Affjax/Request.purs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Network/Affjax/Request.purs b/src/Network/Affjax/Request.purs index 25218d0..2438d4d 100644 --- a/src/Network/Affjax/Request.purs +++ b/src/Network/Affjax/Request.purs @@ -62,19 +62,21 @@ ajax req = makeAff $ runFn8 foreign import unsafeAjax """ function unsafeAjax (url, method, headers, content, username, password, errback, callback) { - var xhr = new XMLHttpRequest(); - xhr.open(method, url, true, username, password); - for (var i = 0, header; header = headers[i]; i++) { - xhr.setRequestHeader(header.head, header.value); - } - xhr.onerror = function (err) { - errback(err)(); + return function () { + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true, username, password); + for (var i = 0, header; header = headers[i]; i++) { + xhr.setRequestHeader(header.head, header.value); + } + xhr.onerror = function (err) { + errback(err)(); + }; + xhr.onload = function () { + if (xhr.status === 200) callback(xhr.response)(); + else errback(new Error("Request returned status " + xhr.status))(); + }; + xhr.send(content); }; - xhr.onload = function () { - if (xhr.status === 200) callback(xhr.response)(); - else errback(new Error("Request returned status " + xhr.status))(); - }; - xhr.send(content); } """ :: forall e. Fn8 String String