mirror of
https://github.com/enso-org/enso.git
synced 2024-10-05 00:57:27 +03:00
parent
c0de753d95
commit
11e4241921
8
.github/workflows/scala.yml
vendored
8
.github/workflows/scala.yml
vendored
@ -40,6 +40,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Enable Developer Command Prompt (Windows)
|
||||
uses: ilammy/msvc-dev-cmd@v1.4.1
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
- name: Disable TCP/UDP Offloading (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
@ -248,6 +250,12 @@ jobs:
|
||||
cp manifest.yaml $ENGINE_DIST_DIR
|
||||
|
||||
# Test Distribution
|
||||
- name: Prepare Engine Test Environment
|
||||
shell: bash
|
||||
run: |
|
||||
go get -v github.com/ahmetb/go-httpbin/cmd/httpbin
|
||||
$(go env GOPATH)/bin/httpbin -host :8080 &
|
||||
|
||||
- name: Test Engine Distribution (Unix)
|
||||
shell: bash
|
||||
if: runner.os != 'Windows'
|
||||
|
@ -75,6 +75,11 @@ type Math
|
||||
Math.pi : Decimal
|
||||
Math.pi = 3.141592653589793
|
||||
|
||||
## A pair of elements.
|
||||
type Pair
|
||||
|
||||
type Pair first second
|
||||
|
||||
## Generic equality of arbitrary values.
|
||||
Any.== : Any -> Boolean
|
||||
Any.== that = if Meta.is_same_object this that then True else
|
||||
|
358
distribution/std-lib/Base/src/Net/Http.enso
Normal file
358
distribution/std-lib/Base/src/Net/Http.enso
Normal file
@ -0,0 +1,358 @@
|
||||
from Base import all
|
||||
import Base.Data.Json
|
||||
import Base.Net.Proxy
|
||||
import Base.Net.Uri
|
||||
import Base.Net.Http.Form
|
||||
import Base.Net.Http.Header
|
||||
import Base.Net.Http.Method
|
||||
import Base.Net.Http.Request
|
||||
import Base.Net.Http.Request.Body as Request_Body
|
||||
import Base.Net.Http.Response
|
||||
import Base.Net.Http.Version
|
||||
import Base.System.File
|
||||
import Base.Time.Duration
|
||||
import Base.Time.Time
|
||||
|
||||
polyglot java import java.time.Duration as Java_Duration
|
||||
polyglot java import java.net.InetSocketAddress
|
||||
polyglot java import java.net.ProxySelector
|
||||
polyglot java import java.net.URI
|
||||
polyglot java import java.net.http.HttpClient
|
||||
polyglot java import java.net.http.HttpRequest
|
||||
polyglot java import java.net.http.HttpResponse
|
||||
polyglot java import org.enso.base.Http_Utils
|
||||
|
||||
type Http
|
||||
|
||||
type Http timeout follow_redirects proxy version
|
||||
|
||||
## Send an Options request.
|
||||
|
||||
> Example
|
||||
Send an Options request.
|
||||
Http.new.options "http://httpbin.org"
|
||||
options : To_Uri -> Vector -> Response
|
||||
options uri (headers = []) =
|
||||
req = Request.options uri headers
|
||||
this.request req
|
||||
|
||||
## Send a Get request.
|
||||
|
||||
> Example
|
||||
Send a Get request.
|
||||
Http.new.get "http://httpbin.org/get"
|
||||
|
||||
> Example
|
||||
Send authenticated Get request (note the use of TLS).
|
||||
Http.new.get "https://httpbin.org/basic-auth/user/pass" [Header.authorization_basic "user" "pass"]
|
||||
|
||||
> Example
|
||||
Download a file.
|
||||
out_file = File.new "/tmp/out.bin"
|
||||
res = Http.new.get "http://httpbin.org/bytes/1024"
|
||||
res.body.to_file out_file
|
||||
get : To_Uri -> Vector -> Response
|
||||
get uri (headers = []) =
|
||||
req = Request.get uri headers
|
||||
this.request req
|
||||
|
||||
## Send a Head request.
|
||||
|
||||
> Example
|
||||
Send a Head request.
|
||||
res = Http.new.head "http://httpbin.org"
|
||||
IO.println res.headers
|
||||
head : To_Uri -> Vector -> Response
|
||||
head uri (headers = []) =
|
||||
req = Request.head uri headers
|
||||
this.request req
|
||||
|
||||
## Send a Post request.
|
||||
|
||||
> Example
|
||||
Send a Post request with binary data.
|
||||
body = Body.Bytes "Hello".utf_8
|
||||
header_binary = Header.content_type "application/octet-stream"
|
||||
Http.new.post "http://httpbin.org/post" body [header_binary]
|
||||
post : To_Uri -> Request_Body -> Vector -> Respoonse
|
||||
post uri body (headers = []) =
|
||||
req = Request.post uri body headers
|
||||
this.request req
|
||||
|
||||
## Send a Post request with the form. By default it will be encoded as
|
||||
"application/x-www-form-urlencoded". To encode the form as
|
||||
"multipart/form-data" add the appropriate header.
|
||||
|
||||
> Example
|
||||
Send a Post request with form.
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
Http.new.post_form "http://httpbin.org/post" form
|
||||
|
||||
> Example
|
||||
Send a Post request with form encoded as "multipart/form-data".
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
Http.new.post_form "http://httpbin.org/post" form [Header.multipart_form_data]
|
||||
|
||||
> Example
|
||||
Configure HTTP client and send a Post request.
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
http = Http.new (timeout = 30.seconds)
|
||||
http.post_form "http://httpbin.org/post" form
|
||||
post_form : To_Uri -> To_Form -> Vector -> Response
|
||||
post_form uri parts (headers = []) =
|
||||
new_headers = [Header.application_x_www_form_urlencoded]
|
||||
req = Request.post uri (Request_Body.Form parts.to_form) new_headers . with_headers headers
|
||||
this.request req
|
||||
|
||||
## Send a Post request with body with content-type "application/json".
|
||||
|
||||
> Example
|
||||
Send a Post request with json data.
|
||||
json = Json.parse <| '''
|
||||
{"key":"val"}
|
||||
Http.new.post_json "http://httpbin.org/post" json
|
||||
post_json : To_Uri -> Json -> Vector -> Response
|
||||
post_json uri body_json (headers = []) =
|
||||
new_headers = [Header.application_json]
|
||||
req = Request.post uri (Request_Body.Json body_json) headers . with_headers new_headers
|
||||
this.request req
|
||||
|
||||
## Send a Put request.
|
||||
|
||||
> Example
|
||||
Send a Put request with binary data.
|
||||
body = Body.Bytes "contents".utf_8
|
||||
header_binary = Header.content_type "application/octet-stream"
|
||||
Http.new.put "http://httpbin.org/post" body [header_binary]
|
||||
put : To_Uri -> Request_Body -> Vector -> Respoonse
|
||||
put uri body (headers = []) =
|
||||
req = Request.put uri body headers
|
||||
this.request req
|
||||
|
||||
## Send a Put request with body with content-type "application/json".
|
||||
|
||||
> Example
|
||||
Send a Put request with json data.
|
||||
json = Json.parse <| '''
|
||||
{"key":"val"}
|
||||
Http.new.put_json "http://httpbin.org/put" json
|
||||
put_json : To_Uri -> Json -> Vector -> Response
|
||||
put_json uri body_json (headers = []) =
|
||||
new_headers = [Header.application_json]
|
||||
req = Request.put uri (Request_Body.Json body_json) headers . with_headers new_headers
|
||||
this.request req
|
||||
|
||||
## Create a Delete request.
|
||||
|
||||
> Example
|
||||
Send a Delete request.
|
||||
Http.new.delete "http://httpbin.org/delete"
|
||||
delete : To_Uri -> Vector -> Response
|
||||
delete uri (headers = []) =
|
||||
req = Request.delete uri headers
|
||||
this.request req
|
||||
|
||||
## Create a request
|
||||
|
||||
> Example
|
||||
Send a Get request with headers.
|
||||
req = Request.new Method.Get "http://httpbin.org/get" . with_header "X-Trace-Id" "00000"
|
||||
res = Http.new.request req
|
||||
res.body
|
||||
|
||||
> Example
|
||||
Open a connection and send a Post request with form.
|
||||
req = Request.post "http://httpbin.org/post" . with_form [Form.text_field "key" "value"] . with_header "X-Trace-Id" "123456789"
|
||||
res = http.new.request req
|
||||
res.code
|
||||
|
||||
> Example
|
||||
Send a Post request with urlencoded form data.
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
req = Request.post "http://httpbin.org/post" . with_form form
|
||||
Http.new.request req
|
||||
|
||||
> Example
|
||||
Send a Post request with form encoded as "multipart/form-data".
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
req = Request.post "http://httpbin.org/post" . with_form form . with_headers [Header.multipart_form_data]
|
||||
Http.new.post req
|
||||
|
||||
> Example
|
||||
Configure HTTP client and send a Post request with form.
|
||||
form = [Form.text_field "name" "John Doe"]
|
||||
req = Request.new Method.Post "http://httpbin.org/post" . with_form form
|
||||
http = Http.new (timeout = 30.seconds) (proxy = Proxy.new "proxy.example.com:80")
|
||||
http.request req
|
||||
request : Request -> Response
|
||||
request req =
|
||||
body_publishers = Polyglot.get_member HttpRequest "BodyPublishers"
|
||||
builder = HttpRequest.newBuilder []
|
||||
# set uri
|
||||
builder.uri [req.uri.internal_uri]
|
||||
# prepare headers and body
|
||||
req_with_body = case req.body of
|
||||
Request_Body.Empty ->
|
||||
Pair req (body_publishers.noBody [])
|
||||
Request_Body.Text text ->
|
||||
builder.header [Header.text_plain.name, Header.text_plain.value]
|
||||
Pair req (body_publishers.ofString [text])
|
||||
Request_Body.Json json ->
|
||||
builder.header [Header.application_json.name, Header.application_json.value]
|
||||
Pair req (body_publishers.ofString [json.to_text])
|
||||
Request_Body.Form form ->
|
||||
add_multipart form =
|
||||
body_builder = Http_Utils.multipart_body_builder []
|
||||
form.parts.map part-> case part.value of
|
||||
Form.Part_Text text -> body_builder.add_part_text [part.key, text]
|
||||
Form.Part_File file -> body_builder.add_part_file [part.key, file.path]
|
||||
boundary = body_builder.get_boundary []
|
||||
Pair (req.with_headers [Header.multipart_form_data boundary]) (body_builder.build [])
|
||||
add_urlencoded form =
|
||||
body_builder = Http_Utils.urlencoded_body_builder []
|
||||
form.parts.map part-> case part.value of
|
||||
Form.Part_Text text -> body_builder.add_part_text [part.key, text]
|
||||
Form.Part_File file -> body_builder.add_part_file [part.key, file.path]
|
||||
Pair req (body_builder.build [])
|
||||
if req.headers.contains Header.multipart_form_data then add_multipart form else
|
||||
add_urlencoded form
|
||||
# method
|
||||
req_http_method = case req.method of
|
||||
Method.Options -> "OPTIONS"
|
||||
Method.Get -> "GET"
|
||||
Method.Post -> "POST"
|
||||
Method.Put -> "PUT"
|
||||
Method.Delete -> "DELETE"
|
||||
Method.Trace -> "TRACE"
|
||||
Method.Connect -> "CONNECT"
|
||||
case req_with_body of
|
||||
Pair req body ->
|
||||
# set method and body
|
||||
builder.method [req_http_method, body]
|
||||
# set headers
|
||||
req.headers.map h-> builder.header [h.name, h.value]
|
||||
http_request = builder.build []
|
||||
body_handler = Polyglot.get_member HttpResponse "BodyHandlers" . ofByteArray []
|
||||
Response.response (this.internal_http_client.send [http_request, body_handler])
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Build an HTTP client.
|
||||
internal_http_client : HttpClient
|
||||
internal_http_client =
|
||||
builder = HttpClient.newBuilder []
|
||||
# timeout
|
||||
if this.timeout.is_date then Panic.throw (Time.time_error "Connection timeout does not support date intervals") else builder.connectTimeout [this.timeout.internal_duration]
|
||||
# redirect
|
||||
redirect = Polyglot.get_member HttpClient "Redirect"
|
||||
redirect_policy = case this.follow_redirects of
|
||||
True -> Polyglot.get_member redirect "ALWAYS"
|
||||
False -> Polyglot.get_member redirect "NEVER"
|
||||
builder.followRedirects [redirect_policy]
|
||||
# proxy
|
||||
case this.proxy of
|
||||
Proxy.Proxy_Addr proxy_host proxy_port ->
|
||||
proxy_selector = ProxySelector.of [InetSocketAddress.new [proxy_host, proxy_port].to_array]
|
||||
Polyglot.invoke builder "proxy" [proxy_selector].to_array
|
||||
Proxy.System ->
|
||||
proxy_selector = ProxySelector.getDefault []
|
||||
Polyglot.invoke builder "proxy" [proxy_selector].to_array
|
||||
Proxy.None ->
|
||||
Unit
|
||||
# version
|
||||
http_client_version = Polyglot.get_member HttpClient "Version"
|
||||
case this.version of
|
||||
Version.Http_1_1 ->
|
||||
Polyglot.invoke builder "version" [http_client_version.valueOf ["HTTP_1_1"]].to_array
|
||||
Version.Http_2 ->
|
||||
Polyglot.invoke builder "version" [http_client_version.valueOf ["HTTP_2"]].to_array
|
||||
# build http client
|
||||
builder.build []
|
||||
|
||||
## Create a new instance of HTTP client.
|
||||
|
||||
> Example
|
||||
Create an HTTP client with default settings.
|
||||
Http.new
|
||||
|
||||
> Example
|
||||
Create an HTTP client with extended timeout.
|
||||
Http.new timeout=30.seconds
|
||||
|
||||
> Example
|
||||
Create an HTTP client with extended timeout and proxy settings.
|
||||
Http.new (timeout = 30.seconds) (proxy = Proxy.new "example.com" 8080)
|
||||
new : Duration -> Boolean -> Proxy -> Http
|
||||
new (timeout = 10.seconds) (follow_redirects = True) (proxy = Proxy.System) (version = Version.Http_1_1) =
|
||||
Http timeout follow_redirects proxy version
|
||||
|
||||
## Send an Options request.
|
||||
|
||||
> Example
|
||||
Send an Options request.
|
||||
Http.options "http://httpbin.org"
|
||||
options : To_Uri -> Vector -> Response
|
||||
options uri (headers = []) = here.new.options uri headers
|
||||
|
||||
## Send a Get request.
|
||||
|
||||
> Example
|
||||
Send a Get request.
|
||||
Http.get "http://httpbin.org/get"
|
||||
|
||||
> Example
|
||||
Send authenticated Get request (note the use of TLS).
|
||||
Http.get "https://httpbin.org/basic-auth/user/pass" [Header.authorization_basic "user" "pass"]
|
||||
|
||||
> Example
|
||||
Download a file.
|
||||
out_file = File.new "/tmp/out.bin"
|
||||
res = Http.get "http://httpbin.org/bytes/1024"
|
||||
res.body.to_file out_file
|
||||
get : To_Uri -> Vector -> Response
|
||||
get uri (headers = []) = here.new.get uri headers
|
||||
|
||||
## Send a Head request.
|
||||
|
||||
> Example
|
||||
Send a Head request.
|
||||
res = Http.head "http://httpbin.org"
|
||||
IO.println res.headers
|
||||
head : To_Uri -> Vector -> Response
|
||||
head uri (headers = []) = here.new.options uri headers
|
||||
|
||||
## Send a Post request.
|
||||
|
||||
> Example
|
||||
Send a Post request with binary data.
|
||||
body = Body.Bytes "Hello".utf_8
|
||||
header_binary = Header.content_type "application/octet-stream"
|
||||
Http.post "http://httpbin.org/post" body [header_binary]
|
||||
post : To_Uri -> Request_Body -> Vector -> Respoonse
|
||||
post uri body (headers = []) = here.new.post uri body headers
|
||||
|
||||
## Send a Post request with the form. By default it will be encoded as
|
||||
"application/x-www-form-urlencoded". To encode the form as
|
||||
"multipart/form-data" add the appropriate header.
|
||||
|
||||
> Example
|
||||
Send a Post request with form.
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
Http.post_form "http://httpbin.org/post" form
|
||||
|
||||
> Example
|
||||
Send a Post request with form encoded as "multipart/form-data".
|
||||
form = [Form.text_field "name" "John Doe", Form.file_field "license.txt" (Enso_Project.root / "LICENSE")]
|
||||
Http.post_form "http://httpbin.org/post" form [Header.multipart_form_data]
|
||||
post_form : To_Uri -> To_Form -> Vector -> Response
|
||||
post_form uri parts (headers = []) = here.new.post_form uri parts headers
|
||||
|
||||
## Send a Post request with body with content-type "application/json".
|
||||
|
||||
> Example
|
||||
Send a Post request with json data.
|
||||
json = Json.parse <| '''
|
||||
{"key":"val"}
|
||||
Http.post_json "http://httpbin.org/post" json
|
||||
post_json : To_Uri -> Json -> Vector -> Response
|
||||
post_json uri body_json (headers = []) = here.new.post_json uri body_json headers
|
47
distribution/std-lib/Base/src/Net/Http/Form.enso
Normal file
47
distribution/std-lib/Base/src/Net/Http/Form.enso
Normal file
@ -0,0 +1,47 @@
|
||||
from Base import all
|
||||
import Base.Vector
|
||||
|
||||
type To_Form
|
||||
|
||||
type To_Form internal_to_form
|
||||
|
||||
to_form : Form
|
||||
to_form = this.internal_to_form
|
||||
|
||||
## Implement To_Form for vector
|
||||
Vector.Vector.to_form = Form this
|
||||
|
||||
## The HTTP form containing a vector of parts.
|
||||
type Form
|
||||
|
||||
type Form parts
|
||||
|
||||
## Implement To_Form.to_form
|
||||
to_form : Form
|
||||
to_form = this
|
||||
|
||||
## The key-value element of the form.
|
||||
type Part
|
||||
|
||||
type Part key value
|
||||
|
||||
## The value of the form element.
|
||||
type Part_Value
|
||||
|
||||
type Part_Text part_text
|
||||
|
||||
type Part_File part_file
|
||||
|
||||
## Create Form data from Parts
|
||||
new : Vector -> Form
|
||||
new parts = Form parts
|
||||
|
||||
# Helpers for creating different parts of the form.
|
||||
|
||||
## Create a text field of a Form.
|
||||
text_field : Text -> Text -> Part
|
||||
text_field key val = Part key (Part_Text val)
|
||||
|
||||
## Create a file field of a Form.
|
||||
file_field : Text -> Text -> Part
|
||||
file_field key file = Part key (Part_File file)
|
68
distribution/std-lib/Base/src/Net/Http/Header.enso
Normal file
68
distribution/std-lib/Base/src/Net/Http/Header.enso
Normal file
@ -0,0 +1,68 @@
|
||||
from Base import all
|
||||
|
||||
polyglot java import org.enso.base.Http_Utils
|
||||
|
||||
type Header
|
||||
|
||||
type Header name value
|
||||
|
||||
## Header equality.
|
||||
== : Header -> Boolean
|
||||
== that = (this.name.equals_ignore_case that.name) && this.value==that.value
|
||||
|
||||
## Create a new Header.
|
||||
new : Text -> Text -> Header
|
||||
new name value = Header name value
|
||||
|
||||
# Accept
|
||||
|
||||
## Create "Accept" header.
|
||||
accept : Text -> Header
|
||||
accept value = Header "Accept" value
|
||||
|
||||
## Header "Accept: */*".
|
||||
accept_all : Header
|
||||
accept_all = here.accept "*/*"
|
||||
|
||||
# Authorization
|
||||
|
||||
## Create "Authorization" header.
|
||||
authorization : Text -> Header
|
||||
authorization value = Header "Authorization" value
|
||||
|
||||
## Create HTTP basic auth header.
|
||||
|
||||
> Example
|
||||
Create basic auth header.
|
||||
Header.authorization_basic "user" "pass"
|
||||
authorization_basic : Text -> Text -> Header
|
||||
authorization_basic user pass =
|
||||
here.authorization (Http_Utils.header_basic_auth [user, pass])
|
||||
|
||||
# Content-Type
|
||||
|
||||
## Create "Content-Type" header.
|
||||
content_type : Text -> Header
|
||||
content_type value = Header "Content-Type" value
|
||||
|
||||
## Header "Content-Type: application/json".
|
||||
application_json : Header
|
||||
application_json = here.content_type "application/json"
|
||||
|
||||
## Header "Content-Type: application/octet-stream".
|
||||
application_octet_stream : Header
|
||||
application_octet_stream = here.content_type "application/octet-stream"
|
||||
|
||||
## Header "Content-Type: application/x-www-form-urlencoded".
|
||||
application_x_www_form_urlencoded : Header
|
||||
application_x_www_form_urlencoded = here.content_type "application/x-www-form-urlencoded"
|
||||
|
||||
## Header "Content-Type: multipart/form-data".
|
||||
multipart_form_data : Text -> Header
|
||||
multipart_form_data (boundary = "") =
|
||||
if boundary == "" then here.content_type "multipart/form-data" else
|
||||
here.content_type ("multipart/form-data; boundary=" + boundary)
|
||||
|
||||
## Header "Content-Type: text/plain".
|
||||
text_plain : Header
|
||||
text_plain = here.content_type "text/plain"
|
10
distribution/std-lib/Base/src/Net/Http/Method.enso
Normal file
10
distribution/std-lib/Base/src/Net/Http/Method.enso
Normal file
@ -0,0 +1,10 @@
|
||||
type Method
|
||||
|
||||
type Options
|
||||
type Get
|
||||
type Head
|
||||
type Post
|
||||
type Put
|
||||
type Delete
|
||||
type Trace
|
||||
type Connect
|
79
distribution/std-lib/Base/src/Net/Http/Request.enso
Normal file
79
distribution/std-lib/Base/src/Net/Http/Request.enso
Normal file
@ -0,0 +1,79 @@
|
||||
from Base import all
|
||||
import Base.Vector
|
||||
import Base.Net.Uri
|
||||
import Base.Net.Http.Form
|
||||
import Base.Net.Http.Header
|
||||
import Base.Net.Http.Method
|
||||
import Base.Net.Http.Request.Body as Request_Body
|
||||
import Base.System.File
|
||||
|
||||
polyglot java import org.enso.base.Text_Utils
|
||||
|
||||
type Request
|
||||
|
||||
type Request method uri headers body
|
||||
|
||||
## Set header.
|
||||
with_header : Text -> Text -> Request
|
||||
with_header key val =
|
||||
new_header = Header.new key val
|
||||
update_header p h = case p of
|
||||
Pair acc True -> Pair (acc + [h]) True
|
||||
Pair acc False ->
|
||||
if Text_Utils.equals_ignore_case [h.name, key] then Pair (acc + [new_header]) True else Pair (acc + [h]) False
|
||||
new_headers = case this.headers.fold (Pair [] False) update_header of
|
||||
Pair acc True -> acc
|
||||
Pair acc False -> acc + [new_header]
|
||||
Request this.method this.uri new_headers this.body
|
||||
|
||||
## Set headers.
|
||||
with_headers : [Header] -> Request
|
||||
with_headers new_headers =
|
||||
update_header req new_header = req.with_header new_header.name new_header.value
|
||||
new_headers.fold this update_header
|
||||
|
||||
## Set body.
|
||||
with_body : Request_Body -> Request
|
||||
with_body new_body = Request this.method this.uri this.headers new_body
|
||||
|
||||
## Set body encoded as "application/json".
|
||||
with_json : Text -> Request
|
||||
with_json json_body =
|
||||
new_body = Request_Body.Json json_body
|
||||
Request this.method this.uri this.headers new_body . with_headers [Header.application_json]
|
||||
|
||||
## Set body as vector of parts encoded as
|
||||
"application/x-www-form-urlencoded".
|
||||
with_form : To_Form -> Request
|
||||
with_form parts =
|
||||
new_body = Request_Body.Form parts.to_form
|
||||
Request this.method this.uri this.headers new_body . with_headers [Header.application_x_www_form_urlencoded]
|
||||
|
||||
## Create new HTTP request.
|
||||
new : Method -> To_Uri -> Vector -> Request_Body -> Request
|
||||
new method addr (headers = []) (body = Request_Body.Empty) =
|
||||
Request method (Uri.panic_on_error (addr.to_uri)) headers body
|
||||
|
||||
## Create an Options request.
|
||||
options : To_Uri -> Vector -> Request
|
||||
options addr (headers = []) = here.new Method.Options addr headers
|
||||
|
||||
## Create a Get request.
|
||||
get : To_Uri -> Vector -> Request
|
||||
get addr (headers = []) = here.new Method.Get addr headers
|
||||
|
||||
## Create a Head request.
|
||||
head : To_Uri -> Vector -> Request
|
||||
head addr (headers = []) = here.new Method.Head addr headers
|
||||
|
||||
## Create a Post request.
|
||||
post : To_Uri -> Request_Body -> Vector -> Request
|
||||
post addr body (headers = []) = here.new Method.Post addr headers body
|
||||
|
||||
## Create a Put request.
|
||||
put : To_Uri -> Request_Body -> Vector -> Request
|
||||
put addr body (headers = []) = here.new Method.Put addr headers body
|
||||
|
||||
## Create a Delete request.
|
||||
delete : To_Uri -> Vector -> Request
|
||||
delete addr (headers = []) = here.new Method.Delete addr headers
|
22
distribution/std-lib/Base/src/Net/Http/Request/Body.enso
Normal file
22
distribution/std-lib/Base/src/Net/Http/Request/Body.enso
Normal file
@ -0,0 +1,22 @@
|
||||
from Base import all
|
||||
|
||||
## The HTTP request body.
|
||||
type Body
|
||||
|
||||
## Empty request body.
|
||||
type Empty
|
||||
|
||||
## Request body with text.
|
||||
type Text text
|
||||
|
||||
## Request body with JSON.
|
||||
type Json json
|
||||
|
||||
## Request body with form data.
|
||||
type Form form
|
||||
|
||||
## Request body with file data.
|
||||
type File file
|
||||
|
||||
## Request body with binary.
|
||||
type Bytes bytes
|
26
distribution/std-lib/Base/src/Net/Http/Response.enso
Normal file
26
distribution/std-lib/Base/src/Net/Http/Response.enso
Normal file
@ -0,0 +1,26 @@
|
||||
from Base import all
|
||||
|
||||
import Base.Net.Http.Header
|
||||
import Base.Net.Http.Response.Body as Response_Body
|
||||
import Base.Net.Http.Status_Code
|
||||
import Base.Vector
|
||||
|
||||
polyglot java import org.enso.base.Http_Utils
|
||||
|
||||
type Response
|
||||
|
||||
type Response internal_http_response
|
||||
|
||||
## Get the response headers.
|
||||
headers : Vector
|
||||
headers =
|
||||
header_entries = Vector.vector (Http_Utils.get_headers [this.internal_http_response.headers []])
|
||||
header_entries.map e-> Header.new (e.getKey []) (e.getValue [])
|
||||
|
||||
## Get the response body.
|
||||
body : Response_Body
|
||||
body = Response_Body.body (Vector.vector (this.internal_http_response.body []))
|
||||
|
||||
## Get the response status code.
|
||||
code : Status_Code
|
||||
code = Status_Code.status_code (this.internal_http_response.statusCode [])
|
22
distribution/std-lib/Base/src/Net/Http/Response/Body.enso
Normal file
22
distribution/std-lib/Base/src/Net/Http/Response/Body.enso
Normal file
@ -0,0 +1,22 @@
|
||||
from Base import all
|
||||
import Base.Data.Json
|
||||
import Base.System.File
|
||||
|
||||
type Body
|
||||
|
||||
## Response body
|
||||
type Body bytes
|
||||
|
||||
## Convert response body to Text.
|
||||
to_text : Text
|
||||
to_text = Text.from_utf_8 this.bytes
|
||||
|
||||
## Convert response body to Json.
|
||||
to_json : Json
|
||||
to_json = Json.parse this.to_text
|
||||
|
||||
## Write response body to a File.
|
||||
to_file : File -> File
|
||||
to_file path =
|
||||
path.write_bytes this.bytes
|
||||
path
|
166
distribution/std-lib/Base/src/Net/Http/Status_Code.enso
Normal file
166
distribution/std-lib/Base/src/Net/Http/Status_Code.enso
Normal file
@ -0,0 +1,166 @@
|
||||
from Base import all
|
||||
|
||||
type Status_Code
|
||||
|
||||
## HTTP status code.
|
||||
type Status_Code code
|
||||
|
||||
## 100 Continue.
|
||||
continue : Status_Code
|
||||
continue = Status_Code 100
|
||||
|
||||
## 101 Switching Protocols.
|
||||
switching_protocols : Status_Code
|
||||
switching_protocols = Status_Code 101
|
||||
|
||||
## 200 OK.
|
||||
ok : Status_Code
|
||||
ok = Status_Code 200
|
||||
|
||||
## 201 Created.
|
||||
created : Status_Code
|
||||
created = Status_Code 201
|
||||
|
||||
## 202 Accepted.
|
||||
accepted : Status_Code
|
||||
accepted = Status_Code 202
|
||||
|
||||
## 203 Non-Authoritative Information.
|
||||
non_authoritative_information : Status_Code
|
||||
non_authoritative_information = Status_Code 203
|
||||
|
||||
## 204 No Content.
|
||||
no_content : Status_Code
|
||||
no_content = Status_Code 204
|
||||
|
||||
## 205 Reset Content.
|
||||
reset_content : Status_Code
|
||||
reset_content = Status_Code 205
|
||||
|
||||
## 206 Partial Content.
|
||||
partial_content : Status_Code
|
||||
partial_content = Status_Code 206
|
||||
|
||||
## 300 Multiple Choices.
|
||||
multiple_choices : Status_Code
|
||||
multiple_choices = Status_Code 300
|
||||
|
||||
## 301 Moved Permanently.
|
||||
moved_permanently : Status_Code
|
||||
moved_permanently = Status_Code 301
|
||||
|
||||
## 302 Found.
|
||||
found : Status_Code
|
||||
found = Status_Code 302
|
||||
|
||||
## 303 See Other.
|
||||
see_other : Status_Code
|
||||
see_other = Status_Code 303
|
||||
|
||||
## 304 Not Modified.
|
||||
not_modified : Status_Code
|
||||
not_modified = Status_Code 304
|
||||
|
||||
## 305 Use Proxy.
|
||||
use_proxy : Status_Code
|
||||
use_proxy = Status_Code 305
|
||||
|
||||
## 307 Temporary Redirect.
|
||||
temporary_redirect : Status_Code
|
||||
temporary_redirect = Status_Code 307
|
||||
|
||||
## 400 Bad Request.
|
||||
bad_request : Status_Code
|
||||
bad_request = Status_Code 400
|
||||
|
||||
## 401 Unauthorized.
|
||||
unauthorized : Status_Code
|
||||
unauthorized = Status_Code 401
|
||||
|
||||
## 402 Payment Required.
|
||||
payment_required : Status_Code
|
||||
payment_required = Status_Code 402
|
||||
|
||||
## 403 Forbidden.
|
||||
forbidden : Status_Code
|
||||
forbidden = Status_Code 403
|
||||
|
||||
## 404 Not Found.
|
||||
not_found : Status_Code
|
||||
not_found = Status_Code 404
|
||||
|
||||
## 405 Method Not Allowed.
|
||||
method_not_allowed : Status_Code
|
||||
method_not_allowed = Status_Code 405
|
||||
|
||||
## 406 Not Acceptable.
|
||||
not_acceptable : Status_Code
|
||||
not_acceptable = Status_Code 406
|
||||
|
||||
## 407 Proxy Authentication Required.
|
||||
proxy_authentication_required : Status_Code
|
||||
proxy_authentication_required = Status_Code 407
|
||||
|
||||
## 408 Request Timeout.
|
||||
request_timeout : Status_Code
|
||||
request_timeout = Status_Code 408
|
||||
|
||||
## 409 Conflict.
|
||||
conflict : Status_Code
|
||||
conflict = Status_Code 409
|
||||
|
||||
## 410 Gone.
|
||||
gone : Status_Code
|
||||
gone = Status_Code 410
|
||||
|
||||
## 411 Length Required.
|
||||
length_required : Status_Code
|
||||
length_required = Status_Code 411
|
||||
|
||||
## 412 Precondition Failed.
|
||||
precondition_failed : Status_Code
|
||||
precondition_failed = Status_Code 412
|
||||
|
||||
## 413 Request Entity Too Large.
|
||||
request_entity_too_large : Status_Code
|
||||
request_entity_too_large = Status_Code 413
|
||||
|
||||
## 414 Request-URI Too Long.
|
||||
request_uri_too_long : Status_Code
|
||||
request_uri_too_long = Status_Code 414
|
||||
|
||||
## 415 Unsupported Media Type.
|
||||
unsupported_media_type : Status_Code
|
||||
unsupported_media_type = Status_Code 415
|
||||
|
||||
## 416 Requested Range Not Satisfiable.
|
||||
requested_range_not_satisfiable : Status_Code
|
||||
requested_range_not_satisfiable = Status_Code 416
|
||||
|
||||
## 417 Expectation Failed.
|
||||
expectation_failed : Status_Code
|
||||
expectation_failed = Status_Code 417
|
||||
|
||||
## 500 Internal Server Error.
|
||||
internal_server_error : Status_Code
|
||||
internal_server_error = Status_Code 500
|
||||
|
||||
## 501 Not Implemented.
|
||||
not_implemented : Status_Code
|
||||
not_implemented = Status_Code 501
|
||||
|
||||
## 502 Bad Gateway.
|
||||
bad_gateway : Status_Code
|
||||
bad_gateway = Status_Code 502
|
||||
|
||||
## 503 Service Unavailable.
|
||||
service_unavailable : Status_Code
|
||||
service_unavailable = Status_Code 503
|
||||
|
||||
## 504 Gateway Timeout
|
||||
gateway_timeout : Status_Code
|
||||
gateway_timeout = Status_Code 504
|
||||
|
||||
## 505 HTTP Version Not Supported.
|
||||
http_version_not_supported : Status_Code
|
||||
http_version_not_supported = Status_Code 505
|
7
distribution/std-lib/Base/src/Net/Http/Version.enso
Normal file
7
distribution/std-lib/Base/src/Net/Http/Version.enso
Normal file
@ -0,0 +1,7 @@
|
||||
type Version
|
||||
|
||||
## HTTP version 1.1.
|
||||
type Http_1_1
|
||||
|
||||
## HTTP version 2.
|
||||
type Http_2
|
17
distribution/std-lib/Base/src/Net/Proxy.enso
Normal file
17
distribution/std-lib/Base/src/Net/Proxy.enso
Normal file
@ -0,0 +1,17 @@
|
||||
from Base import all
|
||||
|
||||
## Proxy settings.
|
||||
type Proxy
|
||||
|
||||
## Proxy is disabled.
|
||||
type None
|
||||
|
||||
## Use a sysem proxy settings.
|
||||
type System
|
||||
|
||||
## Use provided proxy.
|
||||
type Proxy_Addr proxy_host proxy_port
|
||||
|
||||
## Create new proxy settins from host and port.
|
||||
new : Text -> Integer -> Proxy
|
||||
new host port=80 = Proxy_Addr host port
|
148
distribution/std-lib/Base/src/Net/Uri.enso
Normal file
148
distribution/std-lib/Base/src/Net/Uri.enso
Normal file
@ -0,0 +1,148 @@
|
||||
from Base import all
|
||||
|
||||
polyglot java import java.net.URI as Java_URI
|
||||
polyglot java import java.util.Optional
|
||||
|
||||
## A type that can be converted to Uri.
|
||||
type To_Uri
|
||||
|
||||
type To_Uri to_uri
|
||||
|
||||
## Implement To_Uri for Text
|
||||
Text.to_uri = here.parse this
|
||||
|
||||
type Uri_Error
|
||||
type Syntax_Error message
|
||||
|
||||
## PRIVATE
|
||||
panic_on_error ~action =
|
||||
action . catch <| case _ of
|
||||
Syntax_Error msg -> Panic.throw (Syntax_Error msg)
|
||||
|
||||
type Uri
|
||||
|
||||
## Represents a Uniform Resource Identifier (URI) reference.
|
||||
type Uri internal_uri
|
||||
|
||||
## Implement To_Uri
|
||||
to_uri : Uri
|
||||
to_uri = this
|
||||
|
||||
## Get scheme part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "http" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com/foo/bar?key=val"
|
||||
Uri.parse addr . scheme
|
||||
scheme : Text
|
||||
scheme = Optional.ofNullable [this.internal_uri.getScheme []] . orElse [""]
|
||||
|
||||
## Get user info part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "user:pass" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com/foo/bar?key=val"
|
||||
Uri.parse addr . user_info
|
||||
user_info : Text
|
||||
user_info = Optional.ofNullable [this.internal_uri.getUserInfo []] . orElse [""]
|
||||
|
||||
## Get host part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "example.com" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com/foo/bar?key=val"
|
||||
Uri.parse addr . host
|
||||
host : Text
|
||||
host = Optional.ofNullable [this.internal_uri.getHost []] . orElse [""]
|
||||
|
||||
## Get authority (user info and host) part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "user:pass@example.com" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com/foo/bar?key=val"
|
||||
Uri.parse addr . authority
|
||||
authority : Text
|
||||
authority = Optional.ofNullable [this.internal_uri.getAuthority []] . orElse [""]
|
||||
|
||||
## Get port part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "80" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com:80/foo/bar?key=val"
|
||||
Uri.parse addr . port
|
||||
|
||||
> Example
|
||||
Return the empty string if the port is not specified.
|
||||
addr = "http://user:pass@example.com:80/foo/bar?key=val"
|
||||
Uri.parse addr . port
|
||||
port : Text
|
||||
port =
|
||||
port_number = this.internal_uri.getPort []
|
||||
if port_number == -1 then "" else port_number.to_text
|
||||
|
||||
## Get path part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "/foo/bar" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com:80/foo/bar?key=val"
|
||||
Uri.parse addr . path
|
||||
path : Text
|
||||
path = Optional.ofNullable [this.internal_uri.getPath []] . orElse [""]
|
||||
|
||||
## Get query part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the "key=val" part of the HTTP address.
|
||||
addr = "http://user:pass@example.com:80/foo/bar?key=val"
|
||||
Uri.parse addr . query
|
||||
query : Text
|
||||
query = Optional.ofNullable [this.internal_uri.getQuery []] . orElse [""]
|
||||
|
||||
## Get fragment part of this Uri.
|
||||
|
||||
> Example
|
||||
Return the empty fragment of the HTTP address.
|
||||
addr = "http://user:pass@example.com:80/foo/bar?key=val"
|
||||
Uri.parse addr . fragment
|
||||
fragment : Text
|
||||
fragment = Optional.ofNullable [this.internal_uri.getFragment []] . orElse [""]
|
||||
|
||||
## Get unescaped user info part of this Uri.
|
||||
raw_user_info : Text
|
||||
raw_user_info = Optional.ofNullable [this.internal_uri.getRawUserInfo []] . orElse [""]
|
||||
|
||||
## Get unescaped authority part of this Uri.
|
||||
raw_authority : Text
|
||||
raw_authority = Optional.ofNullable [this.internal_uri.getRawAuthority []] . orElse [""]
|
||||
|
||||
## Get unescaped path part of this Uri.
|
||||
raw_path : Text
|
||||
raw_path = Optional.ofNullable [this.internal_uri.getRawPath []] . orElse [""]
|
||||
|
||||
## Get unescaped query part of this Uri.
|
||||
raw_query : Text
|
||||
raw_query = Optional.ofNullable [this.internal_uri.getRawQuery []] . orElse [""]
|
||||
|
||||
## Get unescaped fragment part of this Uri.
|
||||
raw_fragment : Text
|
||||
raw_fragment = Optional.ofNullable [this.internal_uri.getRawFragment []] . orElse [""]
|
||||
|
||||
## Convert this Uri to text.
|
||||
to_text : Text
|
||||
to_text = this.internal_uri.toString []
|
||||
|
||||
## Check Uri equality.
|
||||
== : Uri -> Boolean
|
||||
== that = this.internal_uri.equals [that.internal_uri]
|
||||
|
||||
## Parse Uri from text.
|
||||
Return Syntax_Error when the text cannot be parsed as Uri.
|
||||
|
||||
> Example
|
||||
Parse Uri text.
|
||||
Uri.parse "http://example.com"
|
||||
parse : Text -> Uri
|
||||
parse text =
|
||||
Panic.recover (Uri (Java_URI.create [text])) . catch <| case _ of
|
||||
Polyglot_Error ex -> Error.throw (Syntax_Error (ex.getMessage []))
|
||||
other -> Panic.throw other
|
@ -69,6 +69,28 @@ Text.split_at separator =
|
||||
Text.== : Text -> Boolean
|
||||
Text.== that = Text_Utils.equals [this, that]
|
||||
|
||||
## Checks whether `this` is equal to `that`, ignoring case considerations.
|
||||
|
||||
Two texts are considered equal ignoring case if they are of the same length
|
||||
and corresponding characters are equal ignoring case.
|
||||
|
||||
The definition of equality includes Unicode canonicalization. I.e. two texts
|
||||
are equal if they are identical after canonical decomposition. This ensures
|
||||
that different ways of expressing the same character in the underlying
|
||||
binary representation are considered equal.
|
||||
|
||||
> Example
|
||||
|
||||
The string 'É' (i.e. the character U+00C9, LATIN CAPITAL LETTER E WITH
|
||||
ACUTE) is equal ignore case to the string 'é' (i.e. the character U+00E9,
|
||||
LATIN SMALL LETTER E WITH ACUTE), which is canonically the same as the
|
||||
string 'e\u0301' (i.e. the letter `e` followed by U+0301, COMBINING ACUTE
|
||||
ACCENT). Therefore:
|
||||
(('É' . equals_ignore_case 'é') && ('é' == 'e\u0301')) == True
|
||||
|
||||
Text.equals_ignore_case : Text -> Boolean
|
||||
Text.equals_ignore_case that = Text_Utils.equals_ignore_case [this, that]
|
||||
|
||||
## Checks if `this` is lexicographically before `that`.
|
||||
Text.< : Text -> Boolean
|
||||
Text.< that = Text_Utils.lt [this, that]
|
||||
|
@ -46,7 +46,7 @@ type Date
|
||||
Add 6 months to a local date.
|
||||
Date.new 2020 + 6.months
|
||||
+ : Duration -> Date
|
||||
+ amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else Date (this . internal_local_date . plus [amount.interval_period])
|
||||
+ amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else Date (this . internal_local_date . plus [amount.internal_period])
|
||||
|
||||
## Subtract specified amount of time to this instant.
|
||||
|
||||
@ -54,7 +54,7 @@ type Date
|
||||
Subtract 7 days from a local date.
|
||||
Date.new 2020 - 7.days
|
||||
- : Duration -> Date
|
||||
- amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else Date (this . internal_local_date . minus [amount.interval_period])
|
||||
- amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else Date (this . internal_local_date . minus [amount.internal_period])
|
||||
|
||||
## Format this date using the default formatter.
|
||||
to_text : Text
|
||||
|
@ -8,7 +8,7 @@ type Duration
|
||||
|
||||
## An amount of time in terms of years, months, days, hours, minutes,
|
||||
seconds and nanoseconds.
|
||||
type Duration interval_period interval_duration
|
||||
type Duration internal_period internal_duration
|
||||
|
||||
## Add specified amount of time to this duration.
|
||||
|
||||
@ -20,7 +20,7 @@ type Duration
|
||||
Add 12 hours to a duration of a month.
|
||||
1.month + 12.hours
|
||||
+ : Duration -> Duration
|
||||
+ other = Duration (this.interval_period . plus [other.interval_period]) (this.interval_duration . plus [other.interval_duration])
|
||||
+ other = Duration (this.internal_period . plus [other.internal_period] . normalized []) (this.internal_duration . plus [other.internal_duration])
|
||||
|
||||
## Subtract specified amount of time from this duration.
|
||||
> Example
|
||||
@ -31,39 +31,39 @@ type Duration
|
||||
Substract 30 minutes from a duration of 7 months.
|
||||
7.months - 30.minutes
|
||||
- : Duration -> Duration
|
||||
- other = Duration (this.interval_period . minus [other.interval_period]) (this.interval_duration . minus [other.interval_duration])
|
||||
- other = Duration (this.internal_period . minus [other.internal_period] . normalized []) (this.internal_duration . minus [other.internal_duration])
|
||||
|
||||
## Get the amount of nanoseconds of this duration.
|
||||
nanoseconds : Integer
|
||||
nanoseconds = this.interval_duration . toNanosPart []
|
||||
nanoseconds = this.internal_duration . toNanosPart []
|
||||
|
||||
## Get the amount of milliseconds of this duration.
|
||||
milliseconds : Integer
|
||||
milliseconds = this.interval_duration . toMillisPart []
|
||||
milliseconds = this.internal_duration . toMillisPart []
|
||||
|
||||
## Get the amount of minutes of this duration.
|
||||
seconds : Integer
|
||||
seconds = this.interval_duration . toSecondsPart []
|
||||
seconds = this.internal_duration . toSecondsPart []
|
||||
|
||||
## Get the amount of minutes of this duration.
|
||||
minutes : Integer
|
||||
minutes = this.interval_duration . toMinutesPart []
|
||||
minutes = this.internal_duration . toMinutesPart []
|
||||
|
||||
## Get the amount of hours of this duration.
|
||||
hours : Integer
|
||||
hours = this.interval_duration . toHours []
|
||||
hours = this.internal_duration . toHours []
|
||||
|
||||
## Get the amount of days of this duration.
|
||||
days : Integer
|
||||
days = this.interval_period . getDays []
|
||||
days = this.internal_period . getDays []
|
||||
|
||||
## Get the amount of months of this duration.
|
||||
months : Integer
|
||||
months = this.interval_period . getMonths []
|
||||
months = this.internal_period . getMonths []
|
||||
|
||||
## Get the amount of days of this duration.
|
||||
years : Integer
|
||||
years = this.interval_period . getYears []
|
||||
years = this.internal_period . getYears []
|
||||
|
||||
## Convert this duration to a Vector of years, months, days, hours, minutes,
|
||||
seconds and nanosecnods.
|
||||
@ -90,6 +90,10 @@ type Duration
|
||||
is_empty : Boolean
|
||||
is_empty = (not this.is_date) && (not this.is_time)
|
||||
|
||||
## Check the durations equality.
|
||||
== : Duration -> Boolean
|
||||
== that = this.to_vector == that.to_vector
|
||||
|
||||
## Duration in nanoseconds.
|
||||
Integer.nanosecond : Duration
|
||||
Integer.nanosecond = Duration (Java_Period.ofDays [0]) (Java_Duration.ofNanos [this])
|
||||
@ -132,7 +136,7 @@ Integer.hours = this.hour
|
||||
|
||||
## Duration in days.
|
||||
Integer.day : Duration
|
||||
Integer.day = Duration (Java_Period.ofDays [this]) (Java_Duration.ofSeconds [0])
|
||||
Integer.day = Duration (Java_Period.ofDays [this] . normalized []) (Java_Duration.ofSeconds [0])
|
||||
|
||||
## Duration in days.
|
||||
Integer.days : Duration
|
||||
@ -140,7 +144,7 @@ Integer.days = this.day
|
||||
|
||||
## Duration in months.
|
||||
Integer.month : Duration
|
||||
Integer.month = Duration (Java_Period.ofMonths [this]) (Java_Duration.ofSeconds [0])
|
||||
Integer.month = Duration (Java_Period.ofMonths [this] . normalized []) (Java_Duration.ofSeconds [0])
|
||||
|
||||
## Duration in months.
|
||||
Integer.months : Duration
|
||||
@ -148,7 +152,7 @@ Integer.months = this.month
|
||||
|
||||
## Duration in years.
|
||||
Integer.year : Duration
|
||||
Integer.year = Duration (Java_Period.ofYears [this]) (Java_Duration.ofSeconds [0])
|
||||
Integer.year = Duration (Java_Period.ofYears [this] . normalized []) (Java_Duration.ofSeconds [0])
|
||||
|
||||
## Duration in years.
|
||||
Integer.years : Duration
|
||||
@ -161,4 +165,4 @@ Integer.years = this.year
|
||||
Duration.between Time.now (Time.new 2010 10 20)
|
||||
between : Time -> Time -> Duration
|
||||
between start_inclusive end_exclusive =
|
||||
Duration (Java_Period.ofDays [0]) (Java_Duration.between [start_inclusive.internal_zoned_date_time, end_exclusive.internal_zoned_date_time])
|
||||
Duration (Java_Period.ofDays [0] . normalized []) (Java_Duration.between [start_inclusive.internal_zoned_date_time, end_exclusive.internal_zoned_date_time])
|
||||
|
@ -93,7 +93,7 @@ type Time
|
||||
Add 15 years and 3 hours to a zoned date time.
|
||||
Time.new 2020 + 15.years + 3.hours
|
||||
+ : Duration -> Time
|
||||
+ amount = Time (this . internal_zoned_date_time . plus [amount.interval_period] . plus [amount.interval_duration])
|
||||
+ amount = Time (this . internal_zoned_date_time . plus [amount.internal_period] . plus [amount.internal_duration])
|
||||
|
||||
## Subtract specified amount of time to this instant.
|
||||
|
||||
@ -105,7 +105,7 @@ type Time
|
||||
Subtract 1 year and 9 months from a zoned date time.
|
||||
Time.new 2020 - 1.year - 9.months
|
||||
- : Duration -> Time
|
||||
- amount = Time (this . internal_zoned_date_time . minus [amount.interval_period] . minus [amount.interval_duration])
|
||||
- amount = Time (this . internal_zoned_date_time . minus [amount.internal_period] . minus [amount.internal_duration])
|
||||
|
||||
## Format this time using the default formatter.
|
||||
to_text : Text
|
||||
|
@ -50,7 +50,7 @@ type Time_Of_Day
|
||||
Add 3 seconds to a local time.
|
||||
Time_Of_Day.new + 3.seconds
|
||||
+ : Duration -> Time_Of_Day
|
||||
+ amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else Time_Of_Day (this . internal_local_time . plus [amount.interval_duration])
|
||||
+ amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else Time_Of_Day (this . internal_local_time . plus [amount.internal_duration])
|
||||
|
||||
## Subtract specified amount of time to this instant.
|
||||
|
||||
@ -58,7 +58,7 @@ type Time_Of_Day
|
||||
Subtract 12 hours from a local time.
|
||||
Time_Of_Day.new - 12.hours
|
||||
- : Duration -> Time_Of_Day
|
||||
- amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else Time_Of_Day (this . internal_local_time . minus [amount.interval_duration])
|
||||
- amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else Time_Of_Day (this . internal_local_time . minus [amount.internal_duration])
|
||||
|
||||
## Format this time of day using the default formatter.
|
||||
to_text : Text
|
||||
|
@ -77,6 +77,22 @@ type Vector
|
||||
f = acc -> ix -> function acc (arr.at ix)
|
||||
0.upto this.length . fold initial f
|
||||
|
||||
## Checks whether a predicate holds for at least one element of this vector.
|
||||
exists : (Any -> Any) -> Boolean
|
||||
exists p =
|
||||
check found ix = if found then found else p ix
|
||||
this.fold False check
|
||||
|
||||
## Checks whether this vector contains a given value as an element.
|
||||
contains : Any -> Boolean
|
||||
contains elem = this.exists ix-> ix == elem
|
||||
|
||||
## Selects all elements of this vector which satisfy a predicate.
|
||||
filter : (Any -> Boolean) -> Vector
|
||||
filter p =
|
||||
check acc ix = if p ix then acc + [ix] else acc
|
||||
this.fold [] check
|
||||
|
||||
## Creates a new vector of the given length, initializing elements using
|
||||
the provided constructor function.
|
||||
|
||||
|
@ -19,12 +19,14 @@ import org.enso.interpreter.runtime.type.TypesGen;
|
||||
|
||||
@NodeInfo(shortName = "<new>", description = "Instantiates a polyglot constructor.")
|
||||
public class ConstructorDispatchNode extends BuiltinRootNode {
|
||||
|
||||
private ConstructorDispatchNode(Language language) {
|
||||
super(language);
|
||||
}
|
||||
|
||||
private @Child InteropLibrary library =
|
||||
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
|
||||
private @Child HostValueToEnsoNode hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||
private final BranchProfile err = BranchProfile.create();
|
||||
|
||||
/**
|
||||
@ -53,7 +55,7 @@ public class ConstructorDispatchNode extends BuiltinRootNode {
|
||||
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
|
||||
try {
|
||||
Object[] arguments = TypesGen.expectArray(args[1]).getItems();
|
||||
Object res = library.instantiate(cons, arguments);
|
||||
Object res = hostValueToEnsoNode.execute(library.instantiate(cons, arguments));
|
||||
return new Stateful(state, res);
|
||||
} catch (UnsupportedMessageException
|
||||
| ArityException
|
||||
|
55
std-bits/src/main/java/org/enso/base/Http_Utils.java
Normal file
55
std-bits/src/main/java/org/enso/base/Http_Utils.java
Normal file
@ -0,0 +1,55 @@
|
||||
package org.enso.base;
|
||||
|
||||
import org.enso.base.net.http.BasicAuthorization;
|
||||
import org.enso.base.net.http.MultipartBodyBuilder;
|
||||
import org.enso.base.net.http.UrlencodedBodyBuilder;
|
||||
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Utils for standard HTTP library. */
|
||||
public class Http_Utils {
|
||||
|
||||
/**
|
||||
* Create the header for HTTP basic auth.
|
||||
*
|
||||
* @param user the user name.
|
||||
* @param password the password.
|
||||
* @return the new header.
|
||||
*/
|
||||
public static String header_basic_auth(String user, String password) {
|
||||
return BasicAuthorization.header(user, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the builder for a multipart form data.
|
||||
*
|
||||
* @return the multipart form builder.
|
||||
*/
|
||||
public static MultipartBodyBuilder multipart_body_builder() {
|
||||
return new MultipartBodyBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the builder for an url-encoded form data.
|
||||
*
|
||||
* @return the url-encoded form builder.
|
||||
*/
|
||||
public static UrlencodedBodyBuilder urlencoded_body_builder() {
|
||||
return new UrlencodedBodyBuilder();
|
||||
}
|
||||
/**
|
||||
* Get HTTP response headers as a list of map entries.
|
||||
*
|
||||
* @param headers HTTP response headers.
|
||||
* @return the key-value list of headers.
|
||||
*/
|
||||
public static Object[] get_headers(HttpHeaders headers) {
|
||||
Map<String, List<String>> map = headers.map();
|
||||
return map.keySet().stream()
|
||||
.flatMap(k -> map.get(k).stream().map(v -> new AbstractMap.SimpleImmutableEntry<>(k, v)))
|
||||
.toArray();
|
||||
}
|
||||
}
|
@ -65,6 +65,20 @@ public class Text_Utils {
|
||||
.equals(Normalizer2.getNFDInstance().normalize(str2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether two strings are equal up to Unicode canonicalization ignoring case
|
||||
* considerations.
|
||||
*
|
||||
* @param str1 the first string
|
||||
* @param str2 the second string
|
||||
* @return the result of comparison
|
||||
*/
|
||||
public static boolean equals_ignore_case(String str1, String str2) {
|
||||
return Normalizer2.getNFDInstance()
|
||||
.normalize(str1)
|
||||
.equalsIgnoreCase(Normalizer2.getNFDInstance().normalize(str2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of codepoints into a string.
|
||||
*
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.enso.base.net.http;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
/** An authenticator for HTTP basic auth. */
|
||||
public final class BasicAuthorization {
|
||||
|
||||
/**
|
||||
* Build HTTP basic authorization header.
|
||||
*
|
||||
* @param user the user name.
|
||||
* @param password the password
|
||||
* @return return base64 encoded header for HTTP basic auth.
|
||||
*/
|
||||
public static String header(String user, String password) {
|
||||
String auth = user + ":" + password;
|
||||
String authEncoded = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + authEncoded;
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package org.enso.base.net.http;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** A builder for a multipart form data. */
|
||||
public class MultipartBodyBuilder {
|
||||
private final List<PartsSpecification> partsSpecificationList = new ArrayList<>();
|
||||
private final String boundary = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* Create HTTP body publisher for a multipart form data.
|
||||
*
|
||||
* @return the body publisher.
|
||||
*/
|
||||
public HttpRequest.BodyPublisher build() {
|
||||
if (partsSpecificationList.size() == 0) {
|
||||
throw new IllegalStateException("Must have at least one part to build multipart message.");
|
||||
}
|
||||
addFinalBoundaryPart();
|
||||
return HttpRequest.BodyPublishers.ofByteArrays(PartsIterator::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the multipart boundary separator.
|
||||
*
|
||||
* @return the multipart boundary string.
|
||||
*/
|
||||
public String get_boundary() {
|
||||
return boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add text field to the multipart form.
|
||||
*
|
||||
* @param name the field name.
|
||||
* @param value the field value.
|
||||
* @return this builder.
|
||||
*/
|
||||
public MultipartBodyBuilder add_part_text(String name, String value) {
|
||||
PartsSpecification newPart = new PartsSpecification();
|
||||
newPart.type = PartsSpecification.TYPE.STRING;
|
||||
newPart.name = name;
|
||||
newPart.value = value;
|
||||
partsSpecificationList.add(newPart);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file field to the multipart form.
|
||||
*
|
||||
* @param name the field name.
|
||||
* @param path the file path.
|
||||
* @return this builder.
|
||||
*/
|
||||
public MultipartBodyBuilder add_part_file(String name, String path) {
|
||||
PartsSpecification newPart = new PartsSpecification();
|
||||
newPart.type = PartsSpecification.TYPE.FILE;
|
||||
newPart.name = name;
|
||||
newPart.path = Paths.get(path);
|
||||
partsSpecificationList.add(newPart);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the data field to the multipart form.
|
||||
*
|
||||
* @param name the field name.
|
||||
* @param value the field value.
|
||||
* @param filename the file name.
|
||||
* @param contentType the content type.
|
||||
* @return this builder.
|
||||
*/
|
||||
public MultipartBodyBuilder add_part_bytes(
|
||||
String name, byte[] value, String filename, String contentType) {
|
||||
PartsSpecification newPart = new PartsSpecification();
|
||||
newPart.type = PartsSpecification.TYPE.STREAM;
|
||||
newPart.name = name;
|
||||
newPart.stream = () -> new ByteArrayInputStream(value);
|
||||
newPart.filename = filename;
|
||||
newPart.contentType = contentType;
|
||||
partsSpecificationList.add(newPart);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addFinalBoundaryPart() {
|
||||
PartsSpecification newPart = new PartsSpecification();
|
||||
newPart.type = PartsSpecification.TYPE.FINAL_BOUNDARY;
|
||||
newPart.value = "--" + boundary + "--";
|
||||
partsSpecificationList.add(newPart);
|
||||
}
|
||||
|
||||
private static final class PartsSpecification {
|
||||
|
||||
public enum TYPE {
|
||||
STRING,
|
||||
FILE,
|
||||
STREAM,
|
||||
FINAL_BOUNDARY
|
||||
}
|
||||
|
||||
TYPE type;
|
||||
String name;
|
||||
String value;
|
||||
Path path;
|
||||
Supplier<InputStream> stream;
|
||||
String filename;
|
||||
String contentType;
|
||||
}
|
||||
|
||||
private final class PartsIterator implements Iterator<byte[]> {
|
||||
|
||||
private final Iterator<PartsSpecification> iter;
|
||||
private InputStream currentFileInput;
|
||||
|
||||
private boolean done;
|
||||
private byte[] next;
|
||||
|
||||
PartsIterator() {
|
||||
iter = partsSpecificationList.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (done) return false;
|
||||
if (next != null) return true;
|
||||
try {
|
||||
next = computeNext();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
if (next == null) {
|
||||
done = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
byte[] res = next;
|
||||
next = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
private byte[] computeNext() throws IOException {
|
||||
if (currentFileInput == null) {
|
||||
if (!iter.hasNext()) return null;
|
||||
PartsSpecification nextPart = iter.next();
|
||||
if (PartsSpecification.TYPE.STRING.equals(nextPart.type)) {
|
||||
String part =
|
||||
"--"
|
||||
+ boundary
|
||||
+ "\r\n"
|
||||
+ "Content-Disposition: form-data; name="
|
||||
+ nextPart.name
|
||||
+ "\r\n"
|
||||
+ "Content-Type: text/plain; charset=UTF-8\r\n\r\n"
|
||||
+ nextPart.value
|
||||
+ "\r\n";
|
||||
return part.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
if (PartsSpecification.TYPE.FINAL_BOUNDARY.equals(nextPart.type)) {
|
||||
return nextPart.value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
String filename;
|
||||
String contentType;
|
||||
if (PartsSpecification.TYPE.FILE.equals(nextPart.type)) {
|
||||
Path path = nextPart.path;
|
||||
filename = path.getFileName().toString();
|
||||
contentType = Files.probeContentType(path);
|
||||
if (contentType == null) contentType = "application/octet-stream";
|
||||
currentFileInput = Files.newInputStream(path);
|
||||
} else {
|
||||
filename = nextPart.filename;
|
||||
contentType = nextPart.contentType;
|
||||
if (contentType == null) contentType = "application/octet-stream";
|
||||
currentFileInput = nextPart.stream.get();
|
||||
}
|
||||
String partHeader =
|
||||
"--"
|
||||
+ boundary
|
||||
+ "\r\n"
|
||||
+ "Content-Disposition: form-data; name="
|
||||
+ nextPart.name
|
||||
+ "; filename="
|
||||
+ filename
|
||||
+ "\r\n"
|
||||
+ "Content-Type: "
|
||||
+ contentType
|
||||
+ "\r\n\r\n";
|
||||
return partHeader.getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
byte[] buf = new byte[8192];
|
||||
int length = currentFileInput.read(buf);
|
||||
if (length > 0) {
|
||||
byte[] actualBytes = new byte[length];
|
||||
System.arraycopy(buf, 0, actualBytes, 0, length);
|
||||
return actualBytes;
|
||||
} else {
|
||||
currentFileInput.close();
|
||||
currentFileInput = null;
|
||||
return "\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package org.enso.base.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** A builder for an url-encoded form data. */
|
||||
public final class UrlencodedBodyBuilder {
|
||||
|
||||
private final ArrayList<String> parts = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create HTTP body publisher for an url-encoded form data.
|
||||
*
|
||||
* @return the body publisher.
|
||||
*/
|
||||
public HttpRequest.BodyPublisher build() {
|
||||
String contents = String.join("&", parts);
|
||||
return HttpRequest.BodyPublishers.ofString(contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add text field to the form.
|
||||
*
|
||||
* @param name the field name.
|
||||
* @param value the field value.
|
||||
* @return this builder.
|
||||
*/
|
||||
public UrlencodedBodyBuilder add_part_text(String name, String value) {
|
||||
parts.add(encodePart(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file field to the form.
|
||||
*
|
||||
* @param name the field name.
|
||||
* @param path the file path.
|
||||
* @return this builder.
|
||||
*/
|
||||
public UrlencodedBodyBuilder add_part_file(String name, String path) throws IOException {
|
||||
String contents = Files.readString(Paths.get(path), StandardCharsets.UTF_8);
|
||||
parts.add(encodePart(name, contents));
|
||||
return this;
|
||||
}
|
||||
|
||||
private String encodePart(String name, String value) {
|
||||
return
|
||||
URLEncoder.encode(name, StandardCharsets.UTF_8)
|
||||
+ "="
|
||||
+ URLEncoder.encode(value, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,10 @@ import Test.Data.Json_Spec
|
||||
import Test.Number_Spec
|
||||
import Test.Process_Spec
|
||||
import Test.Vector.Spec as Vector_Spec
|
||||
import Test.Net.Http_Spec
|
||||
import Test.Net.Uri_Spec
|
||||
import Test.Net.Http.Header_Spec
|
||||
import Test.Net.Http.Request_Spec
|
||||
import Test.Numbers.Spec as Numbers_Spec
|
||||
import Test.Text.Spec as Text_Spec
|
||||
import Test.Time.Spec as Time_Spec
|
||||
@ -35,3 +39,7 @@ main = Test.Suite.runMain <|
|
||||
Meta_Spec.spec
|
||||
Map_Spec.spec
|
||||
Json_Spec.spec
|
||||
Uri_Spec.spec
|
||||
Header_Spec.spec
|
||||
Request_Spec.spec
|
||||
Http_Spec.spec
|
||||
|
12
test/Test/src/Net/Http/Header_Spec.enso
Normal file
12
test/Test/src/Net/Http/Header_Spec.enso
Normal file
@ -0,0 +1,12 @@
|
||||
from Base import all
|
||||
|
||||
import Base.Test
|
||||
import Base.Net.Http.Header
|
||||
|
||||
spec =
|
||||
describe "Header" <|
|
||||
it "should check equality" <|
|
||||
Header.new "A" "B" . should_equal (Header.new "A" "B")
|
||||
Header.new "A" "B" . should_equal (Header.new "a" "B")
|
||||
(Header.new "A" "B" == Header.new "A" "b") . should_equal False
|
||||
(Header.new "A" "B" == Header.new "a" "b") . should_equal False
|
48
test/Test/src/Net/Http/Request_Spec.enso
Normal file
48
test/Test/src/Net/Http/Request_Spec.enso
Normal file
@ -0,0 +1,48 @@
|
||||
from Base import all
|
||||
|
||||
import Base.Test
|
||||
import Base.Net.Http.Form
|
||||
import Base.Net.Http.Header
|
||||
import Base.Net.Http.Method
|
||||
import Base.Net.Http.Request
|
||||
import Base.Net.Http.Request.Body as Request_Body
|
||||
import Base.Net.Uri
|
||||
|
||||
spec =
|
||||
test_uri = Uri.parse "https://httpbin.org/post"
|
||||
test_headers = [Header.application_json, Header.new "X-Foo-Id" "0123456789"]
|
||||
describe "Request" <|
|
||||
it "should get method" <|
|
||||
req = Request.new Method.Post test_uri
|
||||
req.method.should_equal Method.Post
|
||||
it "should get uri" <|
|
||||
req = Request.get test_uri
|
||||
req.uri.should_equal test_uri
|
||||
it "should get headers" <|
|
||||
req = Request.get test_uri test_headers
|
||||
req.headers.should_equal test_headers
|
||||
it "should add header" <|
|
||||
new_header = Header.accept_all
|
||||
req = Request.get test_uri test_headers . with_header new_header.name new_header.value
|
||||
req.headers.should_equal (test_headers + [new_header])
|
||||
it "should update header" <|
|
||||
req = Request.get test_uri test_headers . with_header "X-Foo-Id" "42"
|
||||
req.headers.should_equal [Header.application_json, Header.new "X-Foo-Id" "42"]
|
||||
it "should add headers" <|
|
||||
req = Request.get test_uri . with_headers test_headers
|
||||
req.headers.should_equal test_headers
|
||||
it "should update headers" <|
|
||||
new_headers = [Header.multipart_form_data, Header.accept_all]
|
||||
req = Request.get test_uri test_headers . with_headers new_headers
|
||||
req.headers.should_equal [Header.multipart_form_data, test_headers.at 1, Header.accept_all]
|
||||
it "should set json body" <|
|
||||
json = '''
|
||||
{"key":"val"}
|
||||
req = Request.get test_uri . with_json json
|
||||
req.body.should_equal (Request_Body.Json json)
|
||||
req.headers.should_equal [Header.application_json]
|
||||
it "should set form body" <|
|
||||
body_form = [Form.text_field "key" "val"]
|
||||
req = Request.get test_uri . with_form body_form
|
||||
req.body.should_equal (Request_Body.Form body_form.to_form)
|
||||
req.headers.should_equal [Header.application_x_www_form_urlencoded]
|
225
test/Test/src/Net/Http_Spec.enso
Normal file
225
test/Test/src/Net/Http_Spec.enso
Normal file
@ -0,0 +1,225 @@
|
||||
from Base import all
|
||||
|
||||
import Base.Data.Json
|
||||
import Base.Test
|
||||
import Base.Net.Http
|
||||
import Base.Net.Http.Form
|
||||
import Base.Net.Http.Header
|
||||
import Base.Net.Http.Request
|
||||
import Base.Net.Http.Request.Body as Request_Body
|
||||
import Base.Net.Http.Status_Code
|
||||
import Base.Net.Http.Version
|
||||
import Base.Net.Proxy
|
||||
import Base.Net.Uri
|
||||
import Base.System.File
|
||||
import Base.Time.Duration
|
||||
|
||||
polyglot java import java.lang.System
|
||||
polyglot java import java.util.Objects
|
||||
|
||||
spec =
|
||||
is_ci = Objects.equals ["true", System.getenv ["CI"]]
|
||||
if is_ci then here.spec_impl else Unit
|
||||
|
||||
spec_impl =
|
||||
describe "Http" <|
|
||||
it "should create HTTP client with timeout setting" <|
|
||||
http = Http.new (timeout = 30.seconds)
|
||||
http.timeout.should_equal 30.seconds
|
||||
it "should create HTTP client with follow_redirects setting" <|
|
||||
http = Http.new (follow_redirects = False)
|
||||
http.follow_redirects.should_equal False
|
||||
it "should create HTTP client with proxy setting" <|
|
||||
proxy_setting = Proxy.Proxy_Addr "example.com" 80
|
||||
http = Http.new (proxy = proxy_setting)
|
||||
http.proxy.should_equal proxy_setting
|
||||
it "should create HTTP client with version setting" <|
|
||||
version_setting = Version.Http_2
|
||||
http = Http.new (version = version_setting)
|
||||
http.version.should_equal version_setting
|
||||
it "should throw error when requesting invalid Uri" <|
|
||||
case Panic.recover (Http.new.get "not a uri") of
|
||||
Uri.Syntax_Error _ -> Unit
|
||||
other -> Test.fail ("Unexpected result: " + other)
|
||||
it "should send Get request" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "0",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {}
|
||||
}
|
||||
res = Http.new.get "http://localhost:8080/get"
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should send Get request using module method" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "0",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {}
|
||||
}
|
||||
res = Http.get "http://localhost:8080/get"
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post empty body" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "0",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": null
|
||||
}
|
||||
body_empty = Request_Body.Empty
|
||||
res = Http.new.post "http://localhost:8080/post" body_empty
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post empty body using module method" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "0",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": null
|
||||
}
|
||||
body_empty = Request_Body.Empty
|
||||
res = Http.post "http://localhost:8080/post" body_empty
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post text body" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "12",
|
||||
"Content-Type": "text/plain",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "Hello World!",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": null
|
||||
}
|
||||
body_text = Request_Body.Text "Hello World!"
|
||||
res = Http.new.post "http://localhost:8080/post" body_text
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post form text" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "7",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "key=val",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": null
|
||||
}
|
||||
form_parts = [Form.text_field "key" "val"]
|
||||
res = Http.new.post_form "http://localhost:8080/post" form_parts
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post form text using module method" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "7",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "key=val",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": null
|
||||
}
|
||||
form_parts = [Form.text_field "key" "val"]
|
||||
res = Http.post_form "http://localhost:8080/post" form_parts
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post form file" <|
|
||||
test_file = Enso_Project.data / "sample.txt"
|
||||
form_parts = [Form.text_field "key" "val", Form.file_field "sample" test_file]
|
||||
res = Http.new.post_form "http://localhost:8080/post" form_parts
|
||||
res.code.should_equal Status_Code.ok
|
||||
it "should Post form multipart" <|
|
||||
test_file = Enso_Project.data / "sample.txt"
|
||||
form_parts = [Form.text_field "key" "val", Form.file_field "sample" test_file]
|
||||
res = Http.new.post_form "http://localhost:8080/post" form_parts [Header.multipart_form_data]
|
||||
res.code.should_equal Status_Code.ok
|
||||
it "should Post Json" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "13",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "{\\"key\\":\\"val\\"}",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": {
|
||||
"key": "val"
|
||||
}
|
||||
}
|
||||
json = Json.parse <| '''
|
||||
{"key":"val"}
|
||||
res = Http.new.post_json "http://localhost:8080/post" json
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
||||
it "should Post Json using module method" <|
|
||||
expected_response = Json.parse <| '''
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "13",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Java-http-client/11.0.8"
|
||||
},
|
||||
"origin": "127.0.0.1",
|
||||
"url": "",
|
||||
"args": {},
|
||||
"data": "{\\"key\\":\\"val\\"}",
|
||||
"files": null,
|
||||
"form": null,
|
||||
"json": {
|
||||
"key": "val"
|
||||
}
|
||||
}
|
||||
json = Json.parse <| '''
|
||||
{"key":"val"}
|
||||
res = Http.post_json "http://localhost:8080/post" json
|
||||
res.code.should_equal Status_Code.ok
|
||||
res.body.to_json.should_equal expected_response
|
35
test/Test/src/Net/Uri_Spec.enso
Normal file
35
test/Test/src/Net/Uri_Spec.enso
Normal file
@ -0,0 +1,35 @@
|
||||
from Base import all
|
||||
|
||||
import Base.Test
|
||||
import Base.Net.Uri
|
||||
|
||||
spec =
|
||||
describe "Uri" <|
|
||||
it "should parse Uri from string" <|
|
||||
addr = Uri.parse "http://user:pass@example.com/foo/bar?key=val"
|
||||
addr.scheme.should_equal "http"
|
||||
addr.user_info.should_equal "user:pass"
|
||||
addr.host.should_equal "example.com"
|
||||
addr.authority.should_equal "user:pass@example.com"
|
||||
addr.port.should_equal ""
|
||||
addr.path.should_equal "/foo/bar"
|
||||
addr.query.should_equal "key=val"
|
||||
addr.fragment.should_equal ""
|
||||
it "should escape Uri" <|
|
||||
addr = Uri.parse "https://%D0%9B%D0%B8%D0%BD%D1%83%D1%81:pass@ru.wikipedia.org/wiki/%D0%AF%D0%B4%D1%80%D0%BE_Linux?%D0%9A%D0%BE%D0%B4"
|
||||
addr.user_info.should_equal "Линус:pass"
|
||||
addr.authority.should_equal "Линус:pass@ru.wikipedia.org"
|
||||
addr.path.should_equal "/wiki/Ядро_Linux"
|
||||
addr.query.should_equal "Код"
|
||||
addr.fragment.should_equal ""
|
||||
addr.raw_user_info.should_equal "%D0%9B%D0%B8%D0%BD%D1%83%D1%81:pass"
|
||||
addr.raw_authority.should_equal "%D0%9B%D0%B8%D0%BD%D1%83%D1%81:pass@ru.wikipedia.org"
|
||||
addr.raw_path.should_equal "/wiki/%D0%AF%D0%B4%D1%80%D0%BE_Linux"
|
||||
addr.raw_query.should_equal "%D0%9A%D0%BE%D0%B4"
|
||||
addr.raw_fragment.should_equal ""
|
||||
it "should return Syntax_Error when parsing invalid Uri" <|
|
||||
Uri.parse "a b c" . catch <| case _ of
|
||||
Uri.Syntax_Error msg ->
|
||||
msg.should_equal "Illegal character in path at index 1: a b c"
|
||||
other ->
|
||||
Test.fail ("Unexpected result: " + other.to_text)
|
@ -47,3 +47,23 @@ spec =
|
||||
it "should check if empty" <|
|
||||
interval = 0.seconds
|
||||
interval.is_empty . should_be_true
|
||||
it "should normalize periods" <|
|
||||
interval = 12.months
|
||||
interval.to_vector . should_equal [1, 0, 0, 0, 0, 0, 0]
|
||||
it "should normalize addition" <|
|
||||
interval = 11.months + 1.month
|
||||
interval.to_vector . should_equal [1, 0, 0, 0, 0, 0, 0]
|
||||
it "should normalize subtraction" <|
|
||||
interval = 13.months - 1.month
|
||||
interval.to_vector . should_equal [1, 0, 0, 0, 0, 0, 0]
|
||||
it "should check equality" <|
|
||||
3.seconds.should_equal 3.seconds
|
||||
60.seconds.should_equal 1.minute
|
||||
61.seconds.should_equal (1.minute + 1.second)
|
||||
60.minutes.should_equal 1.hour
|
||||
(24.hours == 1.day) . should_be_false
|
||||
(30.days == 1.month) . should_be_false
|
||||
12.months.should_equal 1.year
|
||||
18.months.should_equal (1.year + 6.months)
|
||||
1.year.should_equal (11.months + 1.month)
|
||||
10.years.should_equal 10.years
|
||||
|
@ -34,4 +34,16 @@ spec = describe "Vectors" <|
|
||||
vec.drop_right 2 . should_equal first_four
|
||||
vec.take 4 . should_equal first_four
|
||||
vec.take_right 4 . should_equal last_four
|
||||
|
||||
it "should check exists" <|
|
||||
vec = [1, 2, 3, 4, 5]
|
||||
vec.exists (ix -> ix > 3) . should_be_true
|
||||
vec.exists (ix -> ix < 0) . should_be_false
|
||||
it "should check contains" <|
|
||||
vec = [1, 2, 3, 4, 5]
|
||||
vec.contains 1 . should_be_true
|
||||
vec.contains 0 . should_be_false
|
||||
it "should filter elements" <|
|
||||
vec = [1, 2, 3, 4, 5]
|
||||
vec.filter (ix -> ix > 3) . should_equal [4, 5]
|
||||
vec.filter (ix -> ix == 1) . should_equal [1]
|
||||
vec.filter (ix -> ix < 0) . should_equal []
|
||||
|
Loading…
Reference in New Issue
Block a user