Skip to content

Commit

Permalink
feat: Improve Http module
Browse files Browse the repository at this point in the history
Make Http work for both node and browser and make an all purpose request function.

Fix Scope issue that was sometimes throwing false recursion error.

Fix record spread operator issue where sometimes inference was not working properly.
  • Loading branch information
aboeglin committed Apr 27, 2021
1 parent 300770a commit 9e785aa
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ Right
(TApp
(TApp
(TCon (TC "(->)" (Kfun Star (Kfun Star Star))) "prelude")
(TRecord (fromList [ ( "y" , TVar (TV "e4" Star) ) ]) True))
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
, ( "y" , TVar (TV "e4" Star) )
])
True))
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
Expand All @@ -20,7 +25,12 @@ Right
(TApp
(TApp
(TCon (TC "(->)" (Kfun Star (Kfun Star Star))) "prelude")
(TRecord (fromList [ ( "y" , TVar (TV "e4" Star) ) ]) True))
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
, ( "y" , TVar (TV "e4" Star) )
])
True))
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
Expand All @@ -30,16 +40,16 @@ Right
(Area (Loc 10 1 11) (Loc 55 1 56))
(Abs
(Solved
(TRecord (fromList [ ( "y" , TVar (TV "e4" Star) ) ]) True)
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
, ( "y" , TVar (TV "e4" Star) )
])
True)
(Area (Loc 11 1 12) (Loc 16 1 17))
"state")
[ Solved
(TRecord
(fromList
[ ( "x" , TCon (TC "String" Star) "prelude" )
, ( "y" , TVar (TV "e4" Star) )
])
True)
(TVar (TV "c2" Star))
(Area (Loc 22 1 23) (Loc 54 1 55))
(Record
[ Solved
Expand Down
4 changes: 2 additions & 2 deletions madlib.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 5be60a949ac61cd7123883a272d5f98e69a0e23fe51a5fd489c258c3349b68fe
-- hash: 9c789cb27eb9c056b15149629457a6482af363d0983c56799b029e8bfd600320

name: madlib
version: 0.8.2
version: 0.8.3
description: Please see the README on GitHub at <https://github.com/githubuser/madlib#readme>
homepage: https://github.com/open-sorcerers/madlib#readme
bug-reports: https://github.com/open-sorcerers/madlib/issues
Expand Down
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: madlib
version: 0.8.2
version: 0.8.3
github: "open-sorcerers/madlib"
license: BSD3
author: "Arnaud Boeglin, Brekk Bockrath"
Expand Down
2 changes: 1 addition & 1 deletion pkg/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@madlib-lang/madlib",
"version": "0.8.2",
"version": "0.8.3",
"main": "./src/run.js",
"bin": {
"madlib": "src/run.js"
Expand Down
9 changes: 4 additions & 5 deletions prelude/__internal__/Binary.mad
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export type ByteWord
= Int8Bit a
| Int16Bit a
| Int32Bit a
// export type ByteArray = ByteArray (List ByteWord)


export type ByteArray = ByteArray (List ByteWord)
export type ByteArray = ByteArray

len :: ByteArray -> Number
export len = (byteArray) => #- byteArray.length -#
5 changes: 5 additions & 0 deletions prelude/__internal__/Data.mad
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ByteArray } from "Binary"

export type Data
= TextData String
| BinaryData ByteArray
10 changes: 3 additions & 7 deletions prelude/__internal__/FileSystem.mad
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import W from "Wish"
import B from "Binary"
import IO from "IO"

export type Data
= TextData String
| BinaryData B.ByteArray

import { Data, BinaryData, TextData } from "Data"

#- import fs from "fs" -#


writeFile :: String -> Data -> W.Wish e String
export writeFile = (path, d) => where(d) {
is BinaryData (B.ByteArray arr): W.Wish((bad, good) => #- {
fs.writeFile(path, arr, (err) => {
is BinaryData bytes: W.Wish((bad, good) => #- {
fs.writeFile(path, bytes, (err) => {
if (err) {
bad(err);
}
Expand Down
202 changes: 179 additions & 23 deletions prelude/__internal__/Http.mad
Original file line number Diff line number Diff line change
@@ -1,48 +1,204 @@
import W from "Wish"
import { Wish } from "Wish"
import B from "Binary"
import { Maybe, Just, Nothing, isJust } from "Maybe"
import S from "String"
import L from "List"
import { Data, BinaryData, TextData } from "Data"

export type Body = TextBody String
| BinaryBody B.ByteArray
#-
{Node}
import https from "https"
import http from "http"
{/Node}
-#

export type Response = Response { body :: Body }
alias Status = Number
BadRequest = 400
Unauthorized = 401
PaymentRequired = 402

isBinary = (contentType) => contentType == "application/zip"

#- import https from "https" -#
export type Response = Response { data :: Data, status :: Status }

get :: String -> W.Wish e Response
export get = (url) => W.Wish((bad, good) => (#- {
const req = https.request(url, (response) => {
export type Header = Header String String

export type Method
= GET
| POST
| PUT
| DELETE
| PATCH
| HEAD
| OPTIONS
| CONNECT
| TRACE

alias Request = {
method :: Method,
url :: String,
headers :: List Header,
body :: Maybe Data
}

methodStr :: Method -> String
methodStr = where
is GET : "GET"
is POST : "POST"
is PUT : "PUT"
is DELETE : "DELETE"
is PATCH : "PATCH"
is HEAD : "HEAD"
is OPTIONS: "OPTIONS"
is CONNECT: "CONNECT"
is TRACE : "TRACE"

headerKey :: Header -> String
headerKey = where is Header key _: key

headerValue :: Header -> String
headerValue = where is Header _ val: val

bodyLength :: Data -> Number
bodyLength = where
is TextData s : S.len(s)
is BinaryData bytes: B.len(bytes)

#-
const buildHeaderObj = (headerItems) => {
return headerItems.reduce((acc, item) => {
const k = headerKey(item)
const v = headerValue(item)

return { ...acc, [k]: v }
}, {});
}
-#

isBinary = (mimeType) => where(S.split("/", mimeType))
is ["text", _]: false

is ["application", subType]:
!L.includes(
subType,
["json", "ld+json", "x-httpd-php", "x-sh", "x-csh", "xhtml+xml", "xml"]
)

is _: true


request :: Request -> Wish Response Response
export request = (config) => Wish((bad, good) => #-{
{Browser}
const headers = config.headers
const xhr = new XMLHttpRequest();
xhr.open(methodStr(config.method), config.url)
xhr.responseType = "arraybuffer"

headers.forEach((header) => {
xhr.setRequestHeader(headerKey(header), headerValue(header))
})

if (isJust(config.body)) {
xhr.send(config.body.__args[0].__args[0])
} else {
xhr.send()
}

xhr.onerror = (err) => {
return bad(Response({ data: TextData("Unknown error"), status: xhr.status }))
}

xhr.onload = () => {
const contentType = xhr.getResponseHeader('Content-Type')
const isBinaryData = isBinary(contentType.split(";")[0])

const cb = xhr.status >= 400 ? bad : good
const buffer = xhr.response
const ui8 = new Uint8Array(buffer)

if (isBinaryData) {
cb(Response({ data: BinaryData(ui8), status: xhr.status }))
}
else {
cb(Response({ data: TextData(new TextDecoder().decode(ui8)), status: xhr.status }))
}
}
{/Browser}
{Node}
const headers = buildHeaderObj(config.headers)
let reqFn = http.request
const protocol = config.url.split("://")[0]

if (protocol === "https") {
reqFn = https.request
} else if (protocol === "http") {
} else {
return bad(Response({ data: TextData(`Invalid protocol '${protocol}'`), status: 400 }))
}

if (isJust(config.body)) {
headers["Content-Length"] = bodyLength(config.body.__args[0])
}

const req = reqFn(config.url, { method: methodStr(config.method), headers }, (response) => {
if (response.statusCode === 302) {
W.fulfill(bad, good, get(response.headers.location))
W.fulfill(bad, good, request({ ...config, url: response.headers.location }))
}
else {
const contentType = response.headers["content-type"];
let chunks = isBinary ? [] : "";
const contentType = response.headers["content-type"].split(";")[0]
const isBinaryData = isBinary(contentType)

let chunks = isBinaryData ? [] : ""

response.on('data', (chunk) => {
if (isBinary) {
chunks.push(chunk);
if (isBinaryData) {
chunks.push(chunk)
}
else {
chunks = chunks + chunk;
chunks = chunks + chunk
}
});

response.on('end', () => {
if (isBinary) {
const buffer = Buffer.concat(chunks);
const ui8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT);
good(Response({ body: BinaryBody(B.ByteArray(ui8)) }));
const cb = response.statusCode >= 400 ? bad : good
if (isBinaryData) {
const buffer = Buffer.concat(chunks)
const ui8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint8Array.BYTES_PER_ELEMENT)

cb(Response({ data: BinaryData(ui8), status: response.statusCode }))
}
else {
good(Response({ body: TextBody(chunks) }));
cb(Response({ data: TextData(chunks), status: response.statusCode }))
}
});
}

});
req.on('error', bad)

if (isJust(config.body)) {
req.write(config.body.__args[0].__args[0])
}

req.on('error', (err) => {
bad({ data: TextData(err.message), status: 0 })
})
req.end();
}
-#))
{/Node}
}-#)










get :: String -> Wish Response Response
export get = (url) => request({
method: GET,
url,
headers: [],
body: Nothing
})
16 changes: 14 additions & 2 deletions prelude/__internal__/IO.mad
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#- import util from "util" -#
#-
{Node}
import util from "util"
{/Node}
-#

log :: a -> a
export log = (a) => (#- { console.log(a); return a; } -#)
Expand All @@ -13,7 +17,15 @@ warn :: a -> a
export warn = (w) => (#- { console.warn(w); return w; } -#)

inspect :: a -> a
export inspect = (a) => (#- { console.log(util.inspect(a, {showHidden: false, depth: null})); return a; } -#)
export inspect = (a) => (#-{
{Browser}
console.log(a)
{/Browser}
{Node}
console.log(util.inspect(a, {showHidden: false, depth: null}))
{/Node}
return a
}-#)


#-
Expand Down
3 changes: 2 additions & 1 deletion prelude/__internal__/TestTools.mad
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Maybe from "Maybe"
import T from "Tuple"
import Lcov from "LcovDotInfo"
import { andDo } from "Monad"
import { TextData } from "Data"


colorString :: String -> String -> String
Expand Down Expand Up @@ -137,7 +138,7 @@ export collectCoverage = (hitPoints) =>
|> chain((lcov) =>
of(lcov)
|> map(Lcov.stringify)
|> map(FS.TextData)
|> map(TextData)
|> chain(FS.writeFile(".coverage/lcov.info"))
|> andDo(of(stdoutReport(lcov)))
)
Expand Down
Loading

0 comments on commit 9e785aa

Please sign in to comment.