Integrating Enso Cloud with the libraries (part 1...) (#8006)

- Add a `File_For_Read` type. Used for `File_Format` to read files.
- Added `Enso_User` representing the current user in `Enso_Cloud`.
- *Will be later able to list known users.*
- Added `Enso_Secret` representing a value defined in `Enso_Cloud`.
- Value not used within Enso only accessed within polyglot Java.
- Integrated into `Username_And_Password` and can be used within JDBC connections.
- Integrated into HTTP Headers so a secret can be used as a value.
- New `URI_With_Query` with the same API as `URI`. Supporting secrets in the value.
- *Will be integrated with AWS credentials.*
- Added `Enso_File` representing a file or a folder in the cloud.
- Support the same API as `File` (like the `S3_File`).
- *Will support `enso://` URI style access.*
This commit is contained in:
James Dunkerley 2023-11-20 23:21:14 +00:00 committed by GitHub
parent 1138dfe147
commit ecaca12df1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1268 additions and 374 deletions

View File

@ -592,6 +592,7 @@
- [Added `Previous_Value` option to `fill_nothing` and `fill_empty`.][8105]
- [Added `Table.format` for the in-memory backend.][8150]
- [Implemented truncate `Date_Time` for database backend (Postgres only).][8235]
- [Initial Enso Cloud APIs.][8006]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -841,6 +842,7 @@
[7947]: https://github.com/enso-org/enso/pull/7947
[7979]: https://github.com/enso-org/enso/pull/7979
[8005]: https://github.com/enso-org/enso/pull/8005
[8006]: https://github.com/enso-org/enso/pull/8006
[8029]: https://github.com/enso-org/enso/pull/8029
[8083]: https://github.com/enso-org/enso/pull/8083
[8105]: https://github.com/enso-org/enso/pull/8105

View File

@ -46,7 +46,8 @@ list_objects : Text -> Text -> AWS_Credential | Nothing -> Integer -> Vector Tex
list_objects bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing max_count=1000 =
read_bucket bucket prefix credentials delimiter="" max_count=max_count . second
## Reads an S3 bucket returning a pair of vectors, one with common prefixes and
## PRIVATE
Reads an S3 bucket returning a pair of vectors, one with common prefixes and
one with object keys.
@credentials AWS_Credential.default_widget
read_bucket : Text -> Text -> AWS_Credential | Nothing -> Integer -> Text -> Pair Vector Vector ! S3_Error
@ -73,7 +74,8 @@ read_bucket bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing deli
iterator request 0 [] [] True
## Gets the metadata of a bucket or object.
## ADVANCED
Gets the metadata of a bucket or object.
Arguments:
- bucket: the name of the bucket.

View File

@ -1,7 +1,9 @@
from Standard.Base import all
import Standard.Base.Errors.Common.Syntax_Error
import Standard.Base.Errors.File_Error.File_Error
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.Errors.Unimplemented.Unimplemented
import Standard.Base.System.File_Format.File_For_Read
import Standard.Base.System.Input_Stream.Input_Stream
import Standard.Base.System.Output_Stream.Output_Stream
@ -12,7 +14,13 @@ import project.S3.S3
## Represents an S3 file or folder
If the path ends with a slash, it is a folder. Otherwise, it is a file.
type S3_File
## PUBLIC
## Given an S3 URI create a file representation.
Arguments:
- uri: The URI of the file.
The URI must be in the form `s3://bucket/path/to/file`.
- credentials: The credentials to use when accessing the file.
If not specified, the default credentials are used.
new : Text -> AWS_Credential | Nothing -> S3_File
new uri="s3://" credentials=Nothing =
parts = S3.parse_uri uri
@ -77,8 +85,8 @@ type S3_File
The created stream is automatically closed when `action` returns (even
if it returns exceptionally).
with_input_stream : Vector File_Access -> (Input_Stream -> Any ! File_Error) -> Any ! File_Error
with_input_stream self open_options action =
with_input_stream : Vector File_Access -> (Input_Stream -> Any ! File_Error) -> Any ! S3_Error | Illegal_Argument
with_input_stream self open_options action = if self.is_directory then Error.throw (Illegal_Argument.Error "S3 folders cannot be opened as a stream." self.uri) else
if (open_options != [File_Access.Read]) then Error.throw (S3_Error.Error "S3 files can only be opened for reading." self.uri) else
response_body = S3.get_object self.bucket self.prefix self.credentials
response_body.with_stream action
@ -206,3 +214,6 @@ type S3_File
sub_folders = pair.first . map key-> S3_File.Value self.bucket key self.credentials
files = pair.second . map key-> S3_File.Value self.bucket key self.credentials
sub_folders + files
## PRIVATE
File_For_Read.from (that:S3_File) = File_For_Read.Value that.uri that.name that.extension

View File

@ -15,6 +15,7 @@ import project.Network.HTTP.Request.Request
import project.Network.HTTP.Request_Body.Request_Body
import project.Network.HTTP.Request_Error
import project.Network.URI.URI
import project.Network.URI_With_Query.URI_With_Query
import project.Nothing.Nothing
import project.System.File.File
from project.Data.Boolean import Boolean, False, True
@ -173,8 +174,8 @@ list_directory directory name_filter=Nothing recursive=False =
import Standard.Base.Data
file = enso_project.data / "spreadsheet.xls"
Data.fetch URL . body . to_file file
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
fetch : (URI | URI_With_Query | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
fetch (uri:(URI | URI_With_Query | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
response = HTTP.fetch uri method headers
if try_auto_parse_response.not then response.with_materialized_body else
response.decode if_unsupported=response.with_materialized_body
@ -297,8 +298,8 @@ fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (
test_file = enso_project.data / "sample.txt"
form_data = Map.from_vector [["key", "val"], ["a_file", test_file]]
response = Data.post url_post (Request_Body.Form_Data form_data url_encoded=True)
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
post : (URI | URI_With_Query | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
post (uri:(URI | URI_With_Query | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
response = HTTP.post uri body method headers
if try_auto_parse_response.not then response.with_materialized_body else
response.decode if_unsupported=response.with_materialized_body

View File

@ -144,7 +144,7 @@ type Array
sort self (order = Sort_Direction.Ascending) on=Nothing by=Nothing on_incomparable=Problem_Behavior.Ignore =
Vector.sort self order on by on_incomparable
## ALIAS first, last, slice, sample
## ALIAS first, last, sample, slice
GROUP Selections
Creates a new `Vector` with only the specified range of elements from the
input, removing any elements outside the range.
@ -515,7 +515,7 @@ type Array
map_with_index : (Integer -> Any -> Any) -> Vector Any
map_with_index self function = Vector.map_with_index self function
## GROUP Selections
## PRIVATE
Creates a new array with the skipping elements until `start` and then
continuing until `end` index.
@ -674,7 +674,7 @@ type Array
contains : Any -> Boolean
contains self elem = self.any (== elem)
## ALIAS combine, merge, join by row position
## ALIAS combine, join by row position, merge
GROUP Calculations
Performs a pair-wise operation passed in `function` on consecutive
elements of `self` and `that`.
@ -752,7 +752,7 @@ type Array
each_with_index : (Integer -> Any -> Any) -> Nothing
each_with_index self f = Vector.each_with_index self f
## ALIAS concatenate, union, append
## ALIAS append, concatenate, union
GROUP Operators
Concatenates two arrays, resulting in a new `Vector`, containing all the
elements of `self`, followed by all the elements of `that`.

View File

@ -0,0 +1,213 @@
import project.Any.Any
import project.Data.Enso_Cloud.Utils
import project.Data.Index_Sub_Range.Index_Sub_Range
import project.Data.Json.JS_Object
import project.Data.Text.Encoding.Encoding
import project.Data.Text.Matching_Mode.Matching_Mode
import project.Data.Text.Text
import project.Data.Text.Text_Sub_Range.Text_Sub_Range
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.File_Error.File_Error
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Errors.Problem_Behavior.Problem_Behavior
import project.Errors.Unimplemented.Unimplemented
import project.Network.HTTP.HTTP
import project.Network.HTTP.HTTP_Method.HTTP_Method
import project.Nothing.Nothing
import project.System.File.File_Access.File_Access
import project.System.File_Format.File_For_Read
import project.System.Input_Stream.Input_Stream
import project.System.Output_Stream.Output_Stream
from project.Data.Boolean import Boolean, False, True
from project.Data.Text.Extensions import all
from project.System.File_Format import Auto_Detect, File_Format, Bytes, Plain_Text_Format
type Enso_File
## PRIVATE
Represents a file or folder within the Enso cloud.
Value name:Text id:Text organisation:Text asset_type:Enso_Asset_Type
## Represents the root folder of the current users.
root : Enso_File
root = Enso_File.Value "" "" "" Enso_Asset_Type.Directory
## PRIVATE
Target URI for the api
internal_uri : Text
internal_uri self = case self.id of
"" -> if self.asset_type == Enso_Asset_Type.Directory then Utils.directory_api else
Error.throw (Illegal_Argument.Error "Invalid ID for a "+self.asset_type.to_text+".")
_ -> case self.asset_type of
Enso_Asset_Type.Directory -> Utils.directory_api + "?parent_id=" + self.id
Enso_Asset_Type.File -> Utils.files_api + "/" + self.id
Enso_Asset_Type.Project -> Utils.projects_api + "/" + self.id
Enso_Asset_Type.Data_Link -> Utils.secrets_api + "/" + self.id
Enso_Asset_Type.Secret -> Error.throw (Illegal_Argument.Error "Secrets cannot be accessed directly.")
## Checks if the folder or file exists
exists : Boolean
exists self =
auth_header = Utils.authorization_header
response = HTTP.fetch self.internal_uri HTTP_Method.Get [auth_header]
response.code.is_success
## Checks if this is a folder
is_directory : Boolean
is_directory self = self.asset_type == Enso_Asset_Type.Directory
## PRIVATE
ADVANCED
Creates a new output stream for this file and runs the specified action
on it.
The created stream is automatically closed when `action` returns (even
if it returns exceptionally).
Arguments:
- open_options: A vector of `File_Access` objects determining how to open
the stream. These options set the access properties of the stream.
- action: A function that operates on the output stream and returns some
value. The value is returned from this method.
with_output_stream : Vector File_Access -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
with_output_stream self open_options action =
_ = [open_options, action]
Unimplemented.throw "Writing to Enso_Files is not currently implemented."
## PRIVATE
ADVANCED
Creates a new input stream for this file and runs the specified action
on it.
Arguments:
- open_options: A vector of `File_Access` objects determining how to open
the stream. These options set the access properties of the stream.
- action: A function that operates on the input stream and returns some
value. The value is returned from this method.
The created stream is automatically closed when `action` returns (even
if it returns exceptionally).
with_input_stream : Vector File_Access -> (Input_Stream -> Any ! File_Error) -> Any ! File_Error | Illegal_Argument
with_input_stream self open_options action = if self.asset_type != Enso_Asset_Type.File then Error.throw (Illegal_Argument.Error "Only files can be opened as a stream.") else
if (open_options != [File_Access.Read]) then Error.throw (Illegal_Argument.Error "Files can only be opened for reading.") else
auth_header = Utils.authorization_header
response = HTTP.fetch self.internal_uri HTTP_Method.Get [auth_header]
response.if_not_error <|
js_object = response.decode_as_json
path = js_object.get "path"
if path.is_nothing then Error.throw (Illegal_Argument.Error "Invalid JSON for an Enso_File.") else
url = path.replace "s3://production-enso-organizations-files/" "https://production-enso-organizations-files.s3.eu-west-1.amazonaws.com/"
response = HTTP.fetch url HTTP_Method.Get []
response.if_not_error <| response.with_stream action
## ALIAS load, open
GROUP Input
Read a file using the specified file format
Arguments:
- format: A `File_Format` object used to read file into memory.
If `Auto_Detect` is specified; the provided file determines the specific
type and configures it appropriately. If there is no matching type then
a `File_Error.Unsupported_Type` error is returned.
- on_problems: Specifies the behavior when a problem occurs during the
function.
By default, a warning is issued, but the operation proceeds.
If set to `Report_Error`, the operation fails with a dataflow error.
If set to `Ignore`, the operation proceeds without errors or warnings.
@format File_Format.default_widget
read : File_Format -> Problem_Behavior -> Any ! Illegal_Argument | File_Error
read self format=Auto_Detect (on_problems=Problem_Behavior.Report_Warning) = case self.asset_type of
Enso_Asset_Type.Project -> Error.throw (Illegal_Argument.Error "Projects cannot be read within Enso code. Open using the IDE.")
Enso_Asset_Type.Secret -> Error.throw (Illegal_Argument.Error "Secrets cannot be read directly.")
Enso_Asset_Type.Data_Link -> Unimplemented.throw "Reading from a Data Link is not implemented yet."
Enso_Asset_Type.Directory -> if format == Auto_Detect then self.list else Error.throw (Illegal_Argument.Error "Directories can only be read using the Auto_Detect format.")
Enso_Asset_Type.File -> case format of
Auto_Detect ->
real_format = Auto_Detect.get_reading_format self
if real_format == Nothing then Error.throw (File_Error.Unsupported_Type self) else
self.read real_format on_problems
_ -> self.with_input_stream [File_Access.Read] format.read_stream
## ALIAS load bytes, open bytes
Reads all bytes in this file into a byte vector.
read_bytes : Vector ! File_Error
read_bytes self =
self.read Bytes
## ALIAS load text, open text
Reads the whole file into a `Text`, with specified encoding.
Arguments:
- encoding: The text encoding to decode the file with. Defaults to UTF-8.
- on_problems: Specifies the behavior when a problem occurs during the
function.
By default, a warning is issued, but the operation proceeds.
If set to `Report_Error`, the operation fails with a dataflow error.
If set to `Ignore`, the operation proceeds without errors or warnings.
@encoding Encoding.default_widget
read_text : Encoding -> Problem_Behavior -> Text ! File_Error
read_text self (encoding=Encoding.utf_8) (on_problems=Problem_Behavior.Report_Warning) =
self.read (Plain_Text_Format.Plain_Text encoding) on_problems
## GROUP Metadata
Returns the extension of the file.
extension : Text
extension self = case self.asset_type of
Enso_Asset_Type.Directory -> Error.throw (Illegal_Argument.Error "Directories do not have extensions.")
Enso_Asset_Type.Secret -> Error.throw (Illegal_Argument.Error "Secrets cannot be accessed directly.")
Enso_Asset_Type.Project -> ".enso"
_ ->
name = self.name
last_dot = name.locate "." mode=Matching_Mode.Last
if last_dot.is_nothing then "" else
extension = name.drop (Index_Sub_Range.First last_dot.start)
if extension == "." then "" else extension
## Gets a list of assets within self.
list : Vector Enso_File
list self = if self.asset_type != Enso_Asset_Type.Directory then Error.throw (Illegal_Argument.Error "Only directories can be listed.") else
auth_header = Utils.authorization_header
response = HTTP.fetch self.internal_uri HTTP_Method.Get [auth_header]
response.if_not_error <|
js_object = response.decode_as_json
assets = js_object.get "assets" []
files = assets.map t-> t.into Enso_File
## Remove secrets from the list
files.filter f-> f.asset_type != Enso_Asset_Type.Secret
## PRIVATE
Enso_File.from (that:JS_Object) = if ["title", "id", "parentId"].any (k-> that.contains_key k . not) then Error.throw (Illegal_Argument.Error "Invalid JSON for an Enso_File.") else
name = that.get "title"
id = that.get "id"
org = that.get "organizationId" ""
asset_type = (id.take (Text_Sub_Range.Before "-")):Enso_Asset_Type
Enso_File.Value name id org asset_type
type Enso_Asset_Type
## Represents an Enso project.
Project
## Represents an file.
File
## Represents a folder.
Directory
## Represents a secret.
Secret
## Represents a connection to another data source.
Data_Link
## PRIVATE
Enso_Asset_Type.from (that:Text) = case that of
"project" -> Enso_Asset_Type.Project
"file" -> Enso_Asset_Type.File
"directory" -> Enso_Asset_Type.Directory
"secret" -> Enso_Asset_Type.Secret
"connection" -> Enso_Asset_Type.Data_Link
_ -> Error.throw (Illegal_Argument.Error "Invalid asset type.")
## PRIVATE
File_For_Read.from (that:Enso_File) = File_For_Read.Value that.uri that.name that.extension

View File

@ -0,0 +1,94 @@
import project.Data.Enso_Cloud.Enso_File.Enso_File
import project.Data.Enso_Cloud.Utils
import project.Data.Json.JS_Object
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Forbidden_Operation
import project.Errors.Common.Not_Found
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Network.HTTP.HTTP
import project.Network.HTTP.HTTP_Method.HTTP_Method
import project.Network.HTTP.Request_Body.Request_Body
import project.Nothing.Nothing
import project.Runtime.Context
from project.Data.Boolean import Boolean, False, True
from project.Data.Text.Extensions import all
type Enso_Secret
## PRIVATE
Value name:Text id:Text
## Create a new secret.
Arguments:
- name: The name of the secret
- value: The value of the secret
- parent: The parent folder for the secret. If `Nothing` then it will be
created in the root folder.
create : Text -> Text -> Enso_File | Nothing -> Enso_Secret
create name:Text value:Text parent:(Enso_File|Nothing)=Nothing = if name == "" then Error.throw (Illegal_Argument.Error "Secret name cannot be empty") else
if Context.Output.is_enabled.not then Error.throw (Forbidden_Operation.Error "Creating a secret is forbidden as the Output context is disabled.") else
if name.starts_with "connection-" then Error.throw (Illegal_Argument.Error "Secret name cannot start with 'connection-'") else
if Enso_Secret.exists name parent then Error.throw (Illegal_Argument.Error "Secret with this name already exists") else
auth_header = Utils.authorization_header
body = JS_Object.from_pairs [["secretName", name], ["secretValue", value]]
headers = if parent.is_nothing then [auth_header] else [auth_header, ["parent_id", Enso_File.id]]
response = HTTP.post Utils.secrets_api body HTTP_Method.Post headers
response.if_not_error <|
Enso_Secret.get name parent
## Deletes a secret.
delete : Enso_Secret
delete self =
if Context.Output.is_enabled.not then Error.throw (Forbidden_Operation.Error "Deleting a secret is forbidden as the Output context is disabled.") else
auth_header = Utils.authorization_header
uri = Utils.secrets_api + "/" + self.id
response = HTTP.post uri Request_Body.Empty HTTP_Method.Delete [auth_header]
response.if_not_error self
## Gets a list of all the secrets in the folder.
Arguments:
- folder: The folder to get the secrets from. If `Nothing` then will get
the secrets from the root folder.
list : Enso_File | Nothing -> Vector Enso_Secret
list parent:(Enso_File|Nothing)=Nothing =
auth_header = Utils.authorization_header
auth_header.if_not_error <|
headers = if parent.is_nothing then [auth_header] else [auth_header, ["parent_id", Enso_File.id]]
response = HTTP.fetch Utils.secrets_api HTTP_Method.Get headers
response.if_not_error <|
js_object = response.decode_as_json
secrets = js_object.get "secrets" []
raw_secrets = secrets.map v-> v.into Enso_Secret
raw_secrets.filter s-> (s.name.starts_with "connection-" == False)
## Get a Secret if it exists.
Arguments:
- name: The name of the secret
- parent: The parent folder for the secret. If `Nothing` then will check
in the root folder.
get : Text -> Enso_File | Nothing -> Enso_Secret ! Not_Found
get name:Text parent:(Enso_File|Nothing)=Nothing =
Enso_Secret.list parent . find s-> s.name == name
## Checks if a Secret exists.
Arguments:
- name: The name of the secret
- parent: The parent folder for the secret. If `Nothing` then will check
in the root folder.
exists : Text -> Enso_File | Nothing -> Boolean
exists name:Text parent:(Enso_File|Nothing)=Nothing =
Enso_Secret.list parent . any s-> s.name == name
## PRIVATE
type Enso_Secret_Error
## PRIVATE
Access_Denied
## PRIVATE
to_display_text : Text
to_display_text self = "Cannot read secret value into Enso."

View File

@ -0,0 +1,51 @@
import project.Data.Enso_Cloud.Enso_File.Enso_Asset_Type
import project.Data.Enso_Cloud.Enso_File.Enso_File
import project.Data.Enso_Cloud.Utils
import project.Data.Json.JS_Object
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Network.HTTP.HTTP
import project.Network.HTTP.HTTP_Method.HTTP_Method
from project.Data.Boolean import Boolean, False, True
type Enso_User
## PRIVATE
Represents a user within Enso Cloud.
Fields:
- name: The user's name.
- email: The user's email address.
- id: The user's unique ID.
- home: The user's home directory.
- is_enabled: Whether the user is enabled.
User name:Text email:Text id:Text home:Enso_File is_enabled:Boolean=True
## Fetch the current user.
current : Enso_User
current =
auth_header = Utils.authorization_header
user_api = Utils.cloud_root_uri + "users/me"
response = HTTP.fetch user_api HTTP_Method.Get [auth_header]
response.if_not_error <|
js_object = response.decode_as_json
js_object.into Enso_User
## Lists all known users.
list : Vector Enso_User
list =
auth_header = Utils.authorization_header
user_api = Utils.cloud_root_uri + "users"
response = HTTP.fetch user_api HTTP_Method.Get [auth_header]
response.if_not_error <|
js_object = response.decode_as_json
users = js_object.get 'users' []
users.map (user-> user.into Enso_User)
## PRIVATE
Enso_User.from (that:JS_Object) = if ["name", "email", "id"].any (k-> that.contains_key k . not) then Error.throw (Illegal_Argument.Error "Invalid JSON for an Enso_User.") else
root_folder_id = that.get "rootDirectoryId" ""
root_folder = Enso_File.Value "" root_folder_id "" Enso_Asset_Type.Directory
is_enabled = that.get "isEnabled" True
Enso_User.User (that.get "name") (that.get "email") (that.get "id") root_folder is_enabled

View File

@ -0,0 +1,44 @@
import project.Data.Pair.Pair
import project.Data.Text.Text
import project.Error.Error
import project.Nothing.Nothing
import project.Runtime.Ref.Ref
import project.System.File.File
polyglot java import org.enso.base.enso_cloud.AuthenticationProvider
## PRIVATE
cloud_root_uri = "" + AuthenticationProvider.getAPIRootURI
## PRIVATE
Construct the authoization header for the request
authorization_header : Pair Text Text
authorization_header =
result = AuthenticationProvider.getToken.if_nothing <|
cred_file = File.home / ".enso" / "credentials"
if cred_file.exists.not then Error.throw Not_Logged_In else
AuthenticationProvider.setToken (cred_file.read_text)
Pair.new "Authorization" "Bearer "+result
## PRIVATE
Root address for listing folders
directory_api = cloud_root_uri + "directories"
## PRIVATE
Root address for listing folders
files_api = cloud_root_uri + "files"
## PRIVATE
Root address for listing folders
projects_api = cloud_root_uri + "projects"
## PRIVATE
Root address for Secrets API
secrets_api = cloud_root_uri + "secrets"
## PRIVATE
Error if the user is not logged into Enso Cloud.
type Not_Logged_In
## PRIVATE
to_display_text : Text
to_display_text self = "Not logged into Enso cloud. Please log in and restart."

View File

@ -215,7 +215,7 @@ type JS_Object
into self target_type = case target_type of
JS_Object -> self
Vector -> self.to_vector
Map -> Map.from_pairs self.to_vector
Map -> Map.from_vector self.to_vector
_ ->
## First try a conversion
Panic.catch No_Such_Conversion (self.to target_type) _->

View File

@ -1171,6 +1171,7 @@ type Number_Parse_Error
to_display_text self =
"Could not parse " + self.text.to_text + " as a double."
## PRIVATE
Float.from (that:Integer) = (1.0 * that):Float
## A wrapper type that ensures that a function may only take positive integers.
@ -1190,7 +1191,8 @@ type Positive_Integer
if integer > 0 then Positive_Integer.Value integer else
Error.throw (Illegal_Argument.Error "Expected a positive integer, but got "+integer.to_display_text)
## Allows to create a `Positive_Integer` from an `Integer`.
## PRIVATE
Allows to create a `Positive_Integer` from an `Integer`.
It will throw `Illegal_Argument` if the provided integer is not positive.
Positive_Integer.from (that : Integer) = Positive_Integer.new that

View File

@ -30,7 +30,6 @@ import project.Errors.Illegal_Argument.Illegal_Argument
import project.Errors.Problem_Behavior.Problem_Behavior
import project.Errors.Time_Error.Time_Error
import project.Meta
import project.Network.URI.URI
import project.Nothing.Nothing
from project.Data.Boolean import Boolean, False, True
from project.Data.Json import Invalid_JSON, JS_Object, Json

View File

@ -261,7 +261,8 @@ type Date_Time_Formatter
iso_time =
Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_TIME "iso_time")
## Returns a text representation of this formatter.
## PRIVATE
Returns a text representation of this formatter.
to_text : Text
to_text self = case self.underlying.getFormatterKind of
FormatterKind.CONSTANT ->
@ -275,7 +276,8 @@ type Date_Time_Formatter
original_pattern : Text -> "Date_Time_Formatter.from_java " + original_pattern.pretty
Nothing -> "Date_Time_Formatter.from_java " + self.underlying.getFormatter.to_text
## Parses a human-readable representation of this formatter.
## PRIVATE
Parses a human-readable representation of this formatter.
to_display_text : Text
to_display_text self = self.to_text

View File

@ -79,7 +79,8 @@ type Day_Of_Week_Comparator
## PRIVATE
Comparable.from (_:Day_Of_Week) = Day_Of_Week_Comparator
## Convert from an integer to a Day_Of_Week
## PRIVATE
Convert from an integer to a Day_Of_Week
Arguments:
- `that`: The first day of the week.

View File

@ -710,7 +710,7 @@ type Vector a
"and " + remaining_count.to_text + " more elements"
prefix.map .to_text . join ", " "[" " "+remaining_text+"]"
## ALIAS concatenate, union, append
## ALIAS append, concatenate, union
GROUP Operators
Concatenates two vectors, resulting in a new `Vector`, containing all the
elements of `self`, followed by all the elements of `that`.
@ -723,10 +723,9 @@ type Vector a
[1] + [2]
+ : Vector Any -> Vector Any
+ self that = case that of
+ self that:(Array|Vector) = case that of
_ : Vector -> Vector.insert_builtin self self.length that
_ : Array -> self + Vector.from_polyglot_array that
_ -> Error.throw (Type_Error.Error Vector that "that")
## GROUP Calculations
Inserts the given item into the vector at the given index.
@ -807,7 +806,7 @@ type Vector a
slice : Integer -> Integer -> Vector Any
slice self start end = @Builtin_Method "Vector.slice"
## ALIAS first, last, slice, sample
## ALIAS first, last, sample, slice
GROUP Selections
Creates a new `Vector` with only the specified range of elements from the
input, removing any elements outside the range.
@ -843,7 +842,7 @@ type Vector a
drop self range=(Index_Sub_Range.First 1) =
drop_helper self.length (self.at _) self.slice (slice_ranges self) range
## ALIAS combine, merge, join by row position
## ALIAS combine, join by row position, merge
GROUP Calculations
Performs a pair-wise operation passed in `function` on consecutive
elements of `self` and `that`.

View File

@ -21,10 +21,11 @@ from project.Data.Text.Extensions import all
polyglot java import java.io.StringReader
polyglot java import java.lang.Exception as JException
polyglot java import javax.xml.parsers.DocumentBuilderFactory
polyglot java import javax.xml.parsers.DocumentBuilder
polyglot java import javax.xml.parsers.DocumentBuilderFactory
polyglot java import javax.xml.xpath.XPathConstants
polyglot java import javax.xml.xpath.XPathFactory
polyglot java import org.enso.base.XML_Utils
polyglot java import org.w3c.dom.Document
polyglot java import org.w3c.dom.Element
polyglot java import org.w3c.dom.Node
@ -34,8 +35,6 @@ polyglot java import org.xml.sax.InputSource
polyglot java import org.xml.sax.SAXException
polyglot java import org.xml.sax.SAXParseException
polyglot java import org.enso.base.XML_Utils
type XML_Document
## Read an XML document from a file.

View File

@ -5,6 +5,7 @@ import project.Errors.Problem_Behavior.Problem_Behavior
import project.Network.URI.URI
import project.Nothing.Nothing
import project.System.File.File
import project.System.File_Format.File_For_Read
import project.System.Input_Stream.Input_Stream
from project.Data.Text.Extensions import all
@ -12,8 +13,8 @@ from project.Data.Text.Extensions import all
type XML_Format
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> XML_Format | Nothing
for_file_read file:File =
for_file_read : File_For_Read -> XML_Format | Nothing
for_file_read file:File_For_Read =
case file.extension of
".xml" -> XML_Format
_ -> Nothing

View File

@ -5,6 +5,7 @@ import project.Meta
import project.Nothing.Nothing
import project.Panic.Panic
import project.System.File.File
import project.System.File_Format.File_For_Read
import project.System.File_Format.File_Format
polyglot java import java.io.IOException
@ -35,7 +36,7 @@ type File_Error
IO_Error (file : File) (message : Text)
## Indicates that the given file's type is not supported.
Unsupported_Type (file : File)
Unsupported_Type (file : File_For_Read)
## Indicates that the given type cannot be serialized to the provided file
format.

View File

@ -2,6 +2,9 @@ import project.Any.Any
import project.Data
import project.Data.Array.Array
import project.Data.Boolean
import project.Data.Enso_Cloud.Enso_File.Enso_File
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Data.Enso_Cloud.Enso_User.Enso_User
import project.Data.Filter_Condition.Filter_Condition
import project.Data.Index_Sub_Range.Index_Sub_Range
import project.Data.Interval.Bound
@ -96,6 +99,9 @@ from project.System.File_Format.Plain_Text_Format import Plain_Text
export project.Any.Any
export project.Data
export project.Data.Array.Array
export project.Data.Enso_Cloud.Enso_File.Enso_File
export project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
export project.Data.Enso_Cloud.Enso_User.Enso_User
export project.Data.Filter_Condition.Filter_Condition
export project.Data.Index_Sub_Range.Index_Sub_Range
export project.Data.Interval.Bound

View File

@ -11,11 +11,11 @@ import project.Data.Time.Time_Zone.Time_Zone
import project.Data.Vector.Vector
import project.Error.Error as Base_Error
import project.Errors.Common.Not_Found
import project.Nothing.Nothing
import project.Function.Function
import project.Nothing.Nothing
import project.Polyglot.Java
from project.Runtime.Managed_Resource import Managed_Resource
from project.Data.Boolean import Boolean, False, True
from project.Runtime.Managed_Resource import Managed_Resource
type Type
## PRIVATE
@ -588,6 +588,7 @@ get_short_type_name typ = @Builtin_Method "Meta.get_short_type_name"
get_constructor_declaring_type : Any -> Any
get_constructor_declaring_type constructor = @Builtin_Method "Meta.get_constructor_declaring_type"
## PRIVATE
instrumentor_builtin op args = @Builtin_Method "Meta.instrumentor_builtin"
## PRIVATE

View File

@ -8,6 +8,7 @@ import project.Network.HTTP.Header.Header
import project.Network.HTTP.HTTP_Method.HTTP_Method
import project.Network.HTTP.Request_Body.Request_Body
import project.Network.URI.URI
import project.Network.URI_With_Query.URI_With_Query
from project.Data.Boolean import Boolean, False, True
## ALIAS parse_uri, uri from text
@ -42,6 +43,23 @@ URI.fetch : HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
URI.fetch self (method:HTTP_Method=HTTP_Method.Get) headers=[] try_auto_parse_response=True =
Data.fetch self method headers try_auto_parse_response
## ALIAS download, http get
GROUP Input
Fetches from the URI and returns the response, parsing the body if the
content-type is recognised. Returns an error if the status code does not
represent a successful response.
Arguments:
- method: The HTTP method to use. Must be one of `HTTP_Method.Get`,
`HTTP_Method.Head`, `HTTP_Method.Delete`, `HTTP_Method.Options`.
Defaults to `HTTP_Method.Get`.
- headers: The headers to send with the request. Defaults to an empty vector.
- try_auto_parse_response: If successful should the body be attempted to be
parsed to an Enso native object.
URI_With_Query.fetch : HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
URI_With_Query.fetch self (method:HTTP_Method=HTTP_Method.Get) headers=[] try_auto_parse_response=True =
Data.fetch self method headers try_auto_parse_response
## ALIAS upload, http post
GROUP Input
Writes the provided data to the provided URI. Returns the response,
@ -81,3 +99,43 @@ URI.fetch self (method:HTTP_Method=HTTP_Method.Get) headers=[] try_auto_parse_re
URI.post : Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
URI.post self (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
Data.post self body method headers try_auto_parse_response
## ALIAS upload, http post
GROUP Input
Writes the provided data to the provided URI. Returns the response,
parsing the body if the content-type is recognised. Returns an error if the
status code does not represent a successful response.
Arguments:
- body: The data to write. See `Supported Body Types` below.
- method: The HTTP method to use. Must be one of `HTTP_Method.Post`,
`HTTP_Method.Put`, `HTTP_Method.Patch`. Defaults to `HTTP_Method.Post`.
- headers: The headers to send with the request. Defaults to an empty vector.
- try_auto_parse_response: If successful should the body be attempted to be
parsed to an Enso native object.
! Specifying Content Types
If the `body` parameter specifies an explicit content type, then it is an
error to also specify additional `Content-Type` headers in the `headers`
parameter. (It is not an error to specify multiple `Content-Type` values in
`headers`, however.)
! Supported Body Types
- Request_Body.Text: Sends a text string, with optional encoding and content
type.
- Request_Body.Json: Sends an Enso object, after converting it to JSON.
- Request_Body.Binary: Sends a file.
- Request_Body.Form_Data: Sends a form encoded as key/value pairs. The keys
must be `Text`, and the values must be `Text` or `File`.
- Request_Body.Empty: Sends an empty body.
Additionally, the following types are allowed as the `body` parameter:
- Text: shorthand for `Request_Body.Text that_text`.
- File: shorthand for `Request_Body.Binary that_file`.
- Any other Enso object: shorthand for `Request_Body.Json that_object`.
URI_With_Query.post : Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any
URI_With_Query.post self (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
Data.post self body method headers try_auto_parse_response

View File

@ -1,4 +1,5 @@
import project.Any.Any
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Data.Map.Map
import project.Data.Pair.Pair
import project.Data.Set.Set
@ -18,6 +19,7 @@ import project.Network.HTTP.Request_Body.Request_Body
import project.Network.HTTP.Response.Response
import project.Network.Proxy.Proxy
import project.Network.URI.URI
import project.Network.URI_With_Query.URI_With_Query
import project.Nothing.Nothing
import project.Panic.Panic
import project.Runtime.Context
@ -29,11 +31,13 @@ polyglot java import java.lang.Exception as JException
polyglot java import java.net.http.HttpClient
polyglot java import java.net.http.HttpRequest
polyglot java import java.net.http.HttpRequest.BodyPublisher
polyglot java import java.net.http.HttpResponse
polyglot java import java.net.InetSocketAddress
polyglot java import java.net.ProxySelector
polyglot java import java.nio.file.Path
polyglot java import org.enso.base.Http_Utils
polyglot java import org.enso.base.enso_cloud.EnsoKeyValuePair
polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper
polyglot java import org.enso.base.net.http.MultipartBodyBuilder
polyglot java import org.enso.base.net.http.UrlencodedBodyBuilder
type HTTP
## PRIVATE
@ -64,7 +68,7 @@ type HTTP
example_new =
HTTP.new (timeout = (Duration.new seconds=30)) (proxy = Proxy.Address "example.com" 8080)
new : Duration -> Boolean -> Proxy -> HTTP_Version -> HTTP
new (timeout = (Duration.new seconds=10)) (follow_redirects = True) (proxy = Proxy.System) (version = HTTP_Version.HTTP_1_1) =
new (timeout:Duration=(Duration.new seconds=10)) (follow_redirects:Boolean=True) (proxy:Proxy=Proxy.System) (version:HTTP_Version=HTTP_Version.HTTP_2) =
HTTP.Value timeout follow_redirects proxy version
## PRIVATE
@ -97,72 +101,49 @@ type HTTP
Panic.catch JException handler=(cause-> Error.throw (Request_Error.Error 'IllegalArgumentException' cause.payload.getMessage))
Panic.recover Any <| handle_request_error <| check_output_context <|
body_publishers = HttpRequest.BodyPublishers
builder = HttpRequest.newBuilder
# set uri
uri = case req.uri of
_ : Text -> req.uri.to_uri
_ : URI -> req.uri
builder.uri uri.internal_uri
headers = resolve_headers req
headers.if_not_error <|
# Generate body publisher and optional form content boundary
body_publisher_and_boundary = case req.body of
Request_Body.Text text encoding _ ->
body_publisher = case encoding of
Nothing -> body_publishers.ofString text
_ : Encoding -> body_publishers.ofString text encoding.to_java_charset
Pair.new body_publisher Nothing
Request_Body.Json x ->
json = x.to_json
json.if_not_error <|
Pair.new (body_publishers.ofString json) Nothing
Request_Body.Binary file ->
path = Path.of file.path
Pair.new (body_publishers.ofFile path) Nothing
Request_Body.Form_Data form_data url_encoded ->
build_form_body_publisher form_data url_encoded
Request_Body.Empty ->
Pair.new (body_publishers.noBody) Nothing
_ ->
Error.throw (Illegal_Argument.Error ("Unsupported POST body: " + req.body.to_display_text + "; this is a bug in the Data library"))
# Send request
body_publisher_and_boundary = resolve_body_to_publisher_and_boundary req.body
body_publisher_and_boundary.if_not_error <|
body_publisher = body_publisher_and_boundary.first
# Create builder and set method and body
builder = HttpRequest.newBuilder
builder.method req.method.to_http_method_name body_publisher_and_boundary.first
# Create Unified Header list
boundary = body_publisher_and_boundary.second
boundary_header_list = if boundary.is_nothing then [] else [Header.multipart_form_data boundary]
# set method and body
builder.method req.method.to_http_method_name body_publisher
# set headers
all_headers = headers + boundary_header_list
all_headers.map h-> builder.header h.name h.value
mapped_headers = all_headers.map h-> case h.value of
_ : Enso_Secret -> EnsoKeyValuePair.ofSecret h.name h.value.id
_ -> EnsoKeyValuePair.ofText h.name h.value
http_request = builder.build
body_handler = HttpResponse.BodyHandlers . ofInputStream
# Get uri and resolve query arguments
uri_args = case req.uri of
_ : Text -> [req.uri.to_uri, []]
_ : URI -> [req.uri, []]
_ : URI_With_Query ->
mapped_arguments = req.uri.parameters.map arg-> case arg.second of
_ : Enso_Secret -> EnsoKeyValuePair.ofSecret arg.first arg.second.id
_ : Text -> EnsoKeyValuePair.ofText arg.first arg.second
_ -> Error.throw (Illegal_Argument.Error "Invalid query argument type - all values must be Vector or Pair.")
[req.uri.uri, mapped_arguments]
response = Response.Value (self.internal_http_client.send http_request body_handler)
response = Response.Value (EnsoSecretHelper.makeRequest self.internal_http_client builder uri_args.first.internal_uri uri_args.second mapped_headers)
if error_on_failure_code.not || response.code.is_success then response else
Error.throw (Request_Error.Error "Status Code" ("Request failed with status code: " + response.code.to_text + ". " + response.body.decode_as_text))
## PRIVATE
Static helper for get-like methods
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Any
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
fetch : (URI | URI_With_Query | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Any
fetch (uri:(URI | URI_With_Query | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
check_method fetch_methods method <|
request = Request.new method uri (parse_headers headers) Request_Body.Empty
HTTP.new.request request
## PRIVATE
Static helper for post-like methods
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Any
post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) =
post : (URI | URI_With_Query | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Any
post (uri:(URI | URI_With_Query | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) =
check_method post_methods method <|
request = Request.new method uri (parse_headers headers) body
HTTP.new.request request
@ -172,31 +153,20 @@ type HTTP
Build an HTTP client.
internal_http_client : HttpClient
internal_http_client self =
builder = HttpClient.newBuilder
builder.connectTimeout self.timeout
# redirect
redirect = HttpClient.Redirect
redirect_policy = case self.follow_redirects of
True -> redirect.ALWAYS
False -> redirect.NEVER
builder = HttpClient.newBuilder.connectTimeout self.timeout
redirect_policy = if self.follow_redirects then HttpClient.Redirect.ALWAYS else HttpClient.Redirect.NEVER
builder.followRedirects redirect_policy
# proxy
case self.proxy of
Proxy.Address proxy_host proxy_port ->
proxy_selector = ProxySelector.of (InetSocketAddress.new proxy_host proxy_port)
builder.proxy proxy_selector
Proxy.System ->
proxy_selector = ProxySelector.getDefault
builder.proxy proxy_selector
Proxy.None ->
Nothing
# version
Proxy.Address proxy_host proxy_port -> builder.proxy (ProxySelector.of (InetSocketAddress.new proxy_host proxy_port))
Proxy.System -> builder.proxy ProxySelector.getDefault
Proxy.None -> Nothing
case self.version of
HTTP_Version.HTTP_1_1 ->
builder.version HttpClient.Version.HTTP_1_1
HTTP_Version.HTTP_2 ->
builder.version HttpClient.Version.HTTP_2
# build http client
HTTP_Version.HTTP_1_1 -> builder.version HttpClient.Version.HTTP_1_1
HTTP_Version.HTTP_2 -> builder.version HttpClient.Version.HTTP_2
builder.build
## PRIVATE
@ -249,6 +219,32 @@ resolve_headers req =
all_headers + default_content_type
## PRIVATE
Generate body publisher and optional form content boundary
resolve_body_to_publisher_and_boundary : Request_Body -> Pair BodyPublisher Text
resolve_body_to_publisher_and_boundary body:Request_Body =
body_publishers = HttpRequest.BodyPublishers
case body of
Request_Body.Text text encoding _ ->
body_publisher = case encoding of
Nothing -> body_publishers.ofString text
_ : Encoding -> body_publishers.ofString text encoding.to_java_charset
Pair.new body_publisher Nothing
Request_Body.Json x ->
json = x.to_json
json.if_not_error <|
Pair.new (body_publishers.ofString json) Nothing
Request_Body.Binary file ->
path = Path.of file.path
Pair.new (body_publishers.ofFile path) Nothing
Request_Body.Form_Data form_data url_encoded ->
build_form_body_publisher form_data url_encoded
Request_Body.Empty ->
Pair.new (body_publishers.noBody) Nothing
_ ->
Error.throw (Illegal_Argument.Error ("Unsupported POST body: " + body.to_display_text + "; this is a bug in the Data library"))
## PRIVATE
Build a BodyPublisher from the given form data.
@ -256,14 +252,14 @@ resolve_headers req =
build_form_body_publisher : Map Text (Text | File) -> Boolean -> Pair BodyPublisher Text
build_form_body_publisher (form_data:(Map Text (Text | File))) (url_encoded:Boolean=False) = case url_encoded of
True ->
body_builder = Http_Utils.urlencoded_body_builder
body_builder = UrlencodedBodyBuilder.new
form_data.map_with_key key-> value->
case value of
_ : Text -> body_builder.add_part_text key value
_ : File -> body_builder.add_part_file key value.path
Pair.new body_builder.build Nothing
False ->
body_builder = Http_Utils.multipart_body_builder
body_builder = MultipartBodyBuilder.new
form_data.map_with_key key-> value->
case value of
_ : Text -> body_builder.add_part_text key value

View File

@ -1,6 +1,7 @@
import project.Any.Any
import project.Data.Text.Text
import project.Network.URI.URI
import project.Network.URI_With_Query.URI_With_Query
import project.Panic.Panic
polyglot java import java.io.IOException
@ -12,7 +13,7 @@ type HTTP_Error
Arguments:
- uri: The uri that couldn't be read.
- message: The message for the error.
IO_Error (uri:URI) (message:Text)
IO_Error (uri:URI|URI_With_Query) (message:Text)
## PRIVATE
Convert the HTTP_Error to a human-readable format.
@ -21,6 +22,6 @@ type HTTP_Error
## PRIVATE
Utility method for running an action with Java exceptions mapping.
handle_java_exceptions uri:URI ~action =
handle_java_exceptions (uri:URI|URI_With_Query) ~action =
Panic.catch IOException action caught_panic->
HTTP_Error.IO_Error uri ("An IO error has occurred: " + caught_panic.payload.to_text)

View File

@ -1,11 +1,13 @@
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Data.Numbers.Integer
import project.Data.Text.Encoding.Encoding
import project.Data.Text.Text
import project.Nothing.Nothing
from project.Data.Boolean import Boolean, False, True
from project.Data.Ordering import all
from project.Data.Text.Extensions import all
polyglot java import org.enso.base.Http_Utils
polyglot java import java.util.Base64
type Header
## PRIVATE
@ -15,7 +17,7 @@ type Header
Arguments:
- name: The header name.
- value: The header value.
Value name value
Value name:Text value:(Text|Enso_Secret)
## ALIAS Build a Header
@ -31,8 +33,8 @@ type Header
import Standard.Base.Network.HTTP.Header.Header
example_new = Header.new "My_Header" "my header's value"
new : Text -> Text -> Header
new name value = Header.Value name value
new : Text -> Text | Enso_Secret -> Header
new name:Text value:(Text|Enso_Secret) = Header.Value name value
## Create an "Accept" header.
@ -89,14 +91,13 @@ type Header
example_auth_basic = Header.authorization_basic "user" "pass"
authorization_basic : Text -> Text -> Header
authorization_basic user pass =
Header.authorization (Http_Utils.header_basic_auth user pass)
Header.authorization (make_basic_auth user pass)
## Create bearer token auth header.
Arguments:
- token: The token.
authorization_bearer : Text -> Header
authorization_bearer token =
authorization_bearer token:Text =
Header.authorization ("Bearer " + token)
## Create "Content-Type" header.
@ -113,8 +114,8 @@ type Header
import Standard.Base.Network.HTTP.Header.Header
example_content_type = Header.content_type "my_type"
content_type : Text -> Encoding -> Header
content_type value encoding=Nothing =
content_type : Text -> Encoding | Nothing -> Header
content_type value:Text encoding:(Encoding|Nothing)=Nothing =
charset = if encoding.is_nothing then "" else "; charset="+encoding.character_set
Header.Value Header.content_type_header_name value+charset
@ -163,7 +164,7 @@ type Header
example_multipart = Header.multipart_form_data
multipart_form_data : Text -> Header
multipart_form_data (boundary = "") =
multipart_form_data (boundary:Text="") =
if boundary == "" then Header.content_type "multipart/form-data" else
Header.content_type ("multipart/form-data; boundary=" + boundary)
@ -184,7 +185,8 @@ type Header
to_display_text : Text
to_display_text self = self.name + ": " + self.value.to_display_text
## Gets the name for content_type
## PRIVATE
Gets the name for content_type
content_type_header_name : Text
content_type_header_name = "Content-Type"
@ -204,3 +206,9 @@ type Header_Comparator
## PRIVATE
Comparable.from (_:Header) = Header_Comparator
## PRIVATE
make_basic_auth : Text -> Text -> Text
make_basic_auth user:Text pass:Text =
base64 = Base64.getEncoder.encodeToString (user + ":" + pass).utf_8
"Basic " + base64

View File

@ -10,6 +10,7 @@ import project.Network.HTTP.Header.Header
import project.Network.HTTP.HTTP_Method.HTTP_Method
import project.Network.HTTP.Request_Body.Request_Body
import project.Network.URI.URI
import project.Network.URI_With_Query.URI_With_Query
import project.Nothing.Nothing
import project.Panic.Panic
from project.Data.Boolean import Boolean, False, True
@ -32,9 +33,9 @@ type Request
import Standard.Base.Network.URI.URI
example_new = Request.new Method.Post (URI.parse "http://example.com")
new : HTTP_Method -> (Text | URI) -> Vector Header -> Request_Body -> Request
new (method:HTTP_Method) (url:(Text | URI)) (headers:(Vector Header)=[]) (body:Request_Body=Request_Body.Empty) =
Panic.recover Any (Request.Value method (Panic.rethrow (url.to_uri)) headers body)
new : HTTP_Method -> (URI | URI_With_Query) -> Vector Header -> Request_Body -> Request
new (method:HTTP_Method) (url:(URI | URI_With_Query)) (headers:(Vector Header)=[]) (body:Request_Body=Request_Body.Empty) =
Request.Value method url headers body
## Create an Options request.
@ -47,7 +48,7 @@ type Request
import Standard.Base.Network.URI.URI
example_options = Request.options (URI.parse "http://example.com")
options : (Text | URI) -> Vector -> Request
options : (Text | URI | URI_With_Query) -> Vector -> Request
options url (headers = []) = Request.new HTTP_Method.Options url headers
## Create a Get request.
@ -63,7 +64,7 @@ type Request
import Standard.Base.Network.URI.URI
example_get = Request.get (URI.parse "http://example.com")
get : (Text | URI) -> Vector -> Request
get : (Text | URI | URI_With_Query) -> Vector -> Request
get url (headers = []) = Request.new HTTP_Method.Get url headers
## Create a Head request.
@ -79,7 +80,7 @@ type Request
import Standard.Base.Network.URI.URI
example_head = Request.head (URI.parse "http://example.com")
head : (Text | URI) -> Vector -> Request
head : (Text | URI | URI_With_Query) -> Vector -> Request
head url (headers = []) = Request.new HTTP_Method.Head url headers
## Create a Post request.
@ -97,7 +98,7 @@ type Request
import Standard.Base.Network.URI.URI
example_post = Request.post (URI.parse "http://example.com") Request_Body.Empty
post : (Text | URI) -> Request_Body -> Vector -> Request
post : (Text | URI | URI_With_Query) -> Request_Body -> Vector -> Request
post url body (headers = []) = Request.new HTTP_Method.Post url headers body
## Create a Put request.
@ -115,7 +116,7 @@ type Request
import Standard.Base.Network.URI.URI
example_put = Request.put (URI.parse "http://example.com") Request_Body.Empty
put : (Text | URI) -> Request_Body -> Vector -> Request
put : (Text | URI | URI_With_Query) -> Request_Body -> Vector -> Request
put url body (headers = []) = Request.new HTTP_Method.Put url headers body
## Create a Delete request.
@ -131,7 +132,7 @@ type Request
import Standard.Base.Network.URI.URI
example_delete = Request.delete (URI.parse "http://example.com")
delete : (Text | URI) -> Vector -> Request
delete : (Text | URI | URI_With_Query) -> Vector -> Request
delete url (headers = []) = Request.new HTTP_Method.Delete url headers
## PRIVATE

View File

@ -18,8 +18,6 @@ import project.System.File_Format.File_Format
from project.Data.Text.Extensions import all
from project.Network.HTTP.Response_Body import decode_format_selector
polyglot java import org.enso.base.Http_Utils
type Response
## PRIVATE
@ -57,8 +55,8 @@ type Response
example_headers = Examples.get_response.headers
headers : Vector
headers self =
header_entries = Vector.from_polyglot_array (Http_Utils.get_headers self.internal_http_response.headers)
header_entries.map e-> Header.new e.getKey e.getValue
header_keys = self.internal_http_response.headerNames
header_keys.flat_map k-> (self.internal_http_response.headers.allValues k).map v-> Header.new k v
## Get the response content type.
content_type : Text | Nothing

View File

@ -40,7 +40,8 @@ maximum_body_in_memory = 4192
## PRIVATE
type Response_Body
## Create a Response_Body.
## PRIVATE
Create a Response_Body.
Arguments:
- stream: The body of the response as an InputStream.

View File

@ -1,26 +1,16 @@
import project.Any.Any
import project.Data.Boolean.Boolean
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Data.Json.JS_Object
import project.Data.Pair.Pair
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Syntax_Error
import project.Network.URI_With_Query.URI_With_Query
import project.Nothing.Nothing
import project.Panic.Panic
polyglot java import java.lang.Exception as JException
polyglot java import java.lang.Exception
polyglot java import java.net.URI as Java_URI
polyglot java import java.util.Optional
## PRIVATE
Handle a nothing value.
Arguments:
- value: The value that may possibly be nothing.
handle_nothing : Any -> Any ! Nothing
handle_nothing value = case value of
Nothing -> Error.throw Nothing
_ -> value
type URI
## ALIAS get uri
@ -41,7 +31,7 @@ type URI
example_parse = URI.parse "http://example.com"
parse : Text -> URI ! Syntax_Error
parse uri:Text =
Panic.catch JException (URI.Value (Java_URI.create uri)) caught_panic->
Panic.catch Exception (URI.Value (Java_URI.create uri)) caught_panic->
message = caught_panic.payload.getMessage
truncated = if message.is_nothing || message.length > 100 then "Invalid URI '" + uri.to_display_text + "'" else
"URI syntax error: " + message
@ -76,8 +66,8 @@ type URI
import Standard.Examples
example_scheme = Examples.uri.scheme
scheme : Text ! Nothing
scheme self = handle_nothing self.internal_uri.getScheme
scheme : Text | Nothing
scheme self = self.internal_uri.getScheme
## GROUP Metadata
Get the user info part of this URI.
@ -88,8 +78,8 @@ type URI
import Standard.Examples
example_user_info = Examples.uri.user_info
user_info : Text ! Nothing
user_info self = handle_nothing self.internal_uri.getUserInfo
user_info : Text | Nothing
user_info self = self.internal_uri.getUserInfo
## GROUP Metadata
Get the host part of this URI.
@ -100,8 +90,8 @@ type URI
import Standard.Examples
example_host = Examples.uri.host
host : Text ! Nothing
host self = handle_nothing self.internal_uri.getHost
host : Text | Nothing
host self = self.internal_uri.getHost
## Get the authority (user info and host) part of this URI.
@ -111,8 +101,8 @@ type URI
import Standard.Examples
example_authority = Examples.uri.authority
authority : Text ! Nothing
authority self = handle_nothing self.internal_uri.getAuthority
authority : Text | Nothing
authority self = self.internal_uri.getAuthority
## Get the port part of this URI.
@ -122,11 +112,10 @@ type URI
import Standard.Examples
example_port = Examples.uri.port
port : Text ! Nothing
port : Text | Nothing
port self =
port_number = self.internal_uri.getPort
handle_nothing <|
if port_number == -1 then Nothing else port_number.to_text
if port_number == -1 then Nothing else port_number.to_text
## GROUP Metadata
Get the path part of this URI.
@ -137,8 +126,8 @@ type URI
import Standard.Examples
example_path = Examples.uri.path
path : Text ! Nothing
path self = handle_nothing self.internal_uri.getPath
path : Text | Nothing
path self = self.internal_uri.getPath
## GROUP Metadata
Get the query part of this URI.
@ -149,8 +138,17 @@ type URI
import Standard.Examples
example_query = Examples.uri.query
query : Text ! Nothing
query self = handle_nothing self.internal_uri.getQuery
query : Text | Nothing
query self = self.internal_uri.getQuery
## Adds a query parameter to the URI
Arguments:
- key: The key of the query parameter.
- value: The value of the query parameter.
add_query_argument : Text -> Text | Enso_Secret -> URI
add_query_argument self key:Text value:(Text|Enso_Secret) =
URI_With_Query.Value self [Pair.new key value]
## Get the fragment part of this URI.
@ -160,38 +158,38 @@ type URI
import Standard.Examples
example_fragment = Examples.uri.fragment
fragment : Text ! Nothing
fragment self = handle_nothing self.internal_uri.getFragment
fragment : Text | Nothing
fragment self = self.internal_uri.getFragment
## PRIVATE
ADVANCED
Get the unescaped user info part of this URI.
raw_user_info : Text ! Nothing
raw_user_info self = handle_nothing self.internal_uri.getRawUserInfo
raw_user_info : Text | Nothing
raw_user_info self = self.internal_uri.getRawUserInfo
## PRIVATE
ADVANCED
Get the unescaped authority part of this URI.
raw_authority : Text ! Nothing
raw_authority self = handle_nothing self.internal_uri.getRawAuthority
raw_authority : Text | Nothing
raw_authority self = self.internal_uri.getRawAuthority
## PRIVATE
ADVANCED
Get the unescaped path part of this URI.
raw_path : Text ! Nothing
raw_path self = handle_nothing self.internal_uri.getRawPath
raw_path : Text | Nothing
raw_path self = self.internal_uri.getRawPath
## PRIVATE
ADVANCED
Get the unescaped query part of this URI.
raw_query : Text ! Nothing
raw_query self = handle_nothing self.internal_uri.getRawQuery
raw_query : Text | Nothing
raw_query self = self.internal_uri.getRawQuery
## PRIVATE
ADVANCED
Get the unescaped fragment part of this URI.
raw_fragment : Text ! Nothing
raw_fragment self = handle_nothing self.internal_uri.getRawFragment
raw_fragment : Text | Nothing
raw_fragment self = self.internal_uri.getRawFragment
## PRIVATE
Convert this URI to text.
@ -218,3 +216,6 @@ type URI
type_pair = ["type", "URI"]
cons_pair = ["constructor", "parse"]
JS_Object.from_pairs [type_pair, cons_pair, ["uri", self.to_text]]
## PRIVATE
URI.from (that:Text) = URI.parse that

View File

@ -0,0 +1,112 @@
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Data.Enso_Cloud.Enso_Secret.Enso_Secret_Error
import project.Data.Json.JS_Object
import project.Data.Pair.Pair
import project.Data.Text.Encoding.Encoding
import project.Data.Text.Extensions
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Syntax_Error
import project.Meta
import project.Network.URI.URI
import project.Nothing.Nothing
import project.Panic.Panic
from project.Data.Boolean import Boolean, False, True
polyglot java import java.lang.Exception
polyglot java import java.net.URI as Java_URI
polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper
## Represents a URI with a set of query parameters
type URI_With_Query
## PRIVATE
Value uri:URI parameters:Vector
## PRIVATE
Convert this to URI.
Materialize the URI_With_Query into a URI, but will error if any secrets
are used.
to_uri : URI ! Enso_Secret_Error
to_uri self =
Panic.catch Exception (URI.Value (EnsoSecretHelper.replaceQuery self.uri.internal_uri self.query)) caught_panic->
message = caught_panic.payload.getMessage
Error.throw (Syntax_Error.Error "Unable to collapse to a URI:"+message)
## GROUP Metadata
Get the query part of this URI, but will error if any secrets are used.
query : Text | Nothing ! Enso_Secret_Error
query self =
base_query = self.uri.query.if_nothing ""
query_params = self.parameters.map p->
if p.second.is_a Enso_Secret then Error.throw Enso_Secret_Error.Access_Denied else
(EnsoSecretHelper.encodeArg p.first True) + "=" + (EnsoSecretHelper.encodeArg p.second False)
(if base_query == "" then "" else base_query + "&") + (query_params.join "&")
## Adds a query parameter to the URI
Arguments:
- key: The key of the query parameter.
- value: The value of the query parameter.
add_query_argument : Text -> Text | Enso_Secret -> URI
add_query_argument self key:Text value:(Text|Enso_Secret) =
URI_With_Query.Value self.uri self.parameters+[Pair.new key value]
## PRIVATE
Convert this URI to text.
to_text : Text
to_text self =
base_query = self.uri.query.if_nothing ""
query_params = self.parameters.map p->
if p.second.is_a Enso_Secret then p.first + "=__SECRET__" else
p.first + "=" + p.second
new_query = "?" + (if base_query == "" then "" else base_query + "&") + (query_params.join "&")
Panic.catch Exception (EnsoSecretHelper.replaceQuery self.uri.internal_uri new_query . toString) caught_panic->
message = caught_panic.payload.getMessage
Error.throw (Syntax_Error.Error "Unable to render URI_With_Query:"+message)
## PRIVATE
Convert to a display representation of this URI.
to_display_text : Text
to_display_text self = self.to_text.to_display_text
## PRIVATE
Convert to a JavaScript Object representing this URI.
to_js_object : JS_Object
to_js_object self =
type_pair = ["type", "URI_With_Query"]
cons_pair = ["constructor", "parse"]
JS_Object.from_pairs [type_pair, cons_pair, ["uri", self.uri.to_text], ["parameters", self.parameters.to_js_object]]
## GROUP Metadata
Get the scheme part of this URI.
scheme : Text | Nothing
scheme self = self.uri.scheme
## GROUP Metadata
Get the user info part of this URI.
user_info : Text | Nothing
user_info self = self.uri.user_info
## GROUP Metadata
Get the host part of this URI.
host : Text | Nothing
host self = self.uri.host
## Get the authority (user info and host) part of this URI.
authority : Text | Nothing
authority self = self.uri.authority
## Get the port part of this URI.
port : Text | Nothing
port self = self.uri.port
## GROUP Metadata
Get the path part of this URI.
path : Text | Nothing
path self = self.uri.path
## Get the fragment part of this URI.
fragment : Text | Nothing
fragment self = self.uri.fragment

View File

@ -1,14 +1,14 @@
import project.Any.Any
import project.Data.Array.Array
import project.Data.Map.Map
import project.Data.Range.Range
import project.Data.Set.Set
import project.Data.Text.Text
import project.Data.Time.Date.Date
import project.Data.Time.Date_Range.Date_Range
import project.Data.Time.Period.Period
import project.Data.Time.Time_Of_Day.Time_Of_Day
import project.Data.Time.Time_Period.Time_Period
import project.Data.Map.Map
import project.Data.Set.Set
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Illegal_Argument.Illegal_Argument
@ -44,8 +44,8 @@ type Random
new_generator : Integer -> Random_Generator
new_generator (seed:Integer|Nothing=Nothing) = Random_Generator.new seed
## TEXT_ONLY
GROUP Random
## GROUP Random
TEXT_ONLY
Set the seed of the default `Random_Generator` instance.
@ -245,7 +245,7 @@ type Random
permute : Vector Any -> Vector Any
permute (v:Vector) = Random_Generator.global_random_generator.permute v
# PRIVATE
## PRIVATE
type Random_Generator
## PRIVATE
Create a new rng with the given seed.

View File

@ -53,7 +53,8 @@ get_stack_trace =
gc : Nothing
gc = @Builtin_Method "Runtime.gc"
## ADVANCED
## PRIVATE
ADVANCED
Asserts that the given action succeeds, otherwise throws a panic.
@ -66,7 +67,8 @@ assert ~action message="" = assert_builtin action message
## PRIVATE
assert_builtin ~action message = @Builtin_Method "Runtime.assert_builtin"
## ADVANCED
## PRIVATE
ADVANCED
Executes the provided action without allowing it to inline.

View File

@ -1,5 +1,6 @@
import project.Data.Numbers.Integer
import project.Data.Text.Text
import project.Nothing.Nothing
import project.System.File.File
from project.Data.Boolean import Boolean, False, True
@ -47,13 +48,21 @@ type Source_Location
row + ":" + start + "-" + end
False ->
start_line.to_text + '-' + end_line.to_text
cwd = File.current_directory
file = self.file.absolute
formatted_file = case file.is_child_of cwd of
True -> cwd.relativize file . path
_ -> file.path
formatted_file + ":" + indices
if self.file.is_nothing then "? Unknown ?:" + indices else
cwd = File.current_directory
file = self.file.absolute
formatted_file = case file.is_child_of cwd of
True -> cwd.relativize file . path
_ -> file.path
formatted_file + ":" + indices
## Return the source file corresponding to this location.
file : File
file self = File.new self.prim_location.getSource.getPath
file : File | Nothing
file self =
source = self.prim_location.getSource
path = if source.is_nothing then Nothing else source.getPath
if path.is_nothing then Nothing else File.new path
## PRIVATE
to_display_text : Text
to_display_text self = self.formatted_coordinates

View File

@ -54,8 +54,8 @@ type Auto_Detect
Finds a matching format for reading the file.
It assumes that `file` already exists.
get_reading_format : File -> Any | Nothing
get_reading_format file =
get_reading_format : File_For_Read -> Any | Nothing
get_reading_format file:File_For_Read =
get_format f-> f.for_file_read file
## PRIVATE
@ -121,8 +121,8 @@ type Plain_Text_Format
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> Plain_Text_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> Plain_Text_Format | Nothing
for_file_read file:File_For_Read =
case file.extension of
".txt" -> Plain_Text_Format.Plain_Text
".log" -> Plain_Text_Format.Plain_Text
@ -164,8 +164,8 @@ type Plain_Text_Format
type Bytes
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> Bytes | Nothing
for_file_read file =
for_file_read : File_For_Read -> Bytes | Nothing
for_file_read file:File_For_Read =
case file.extension of
".dat" -> Bytes
_ -> Nothing
@ -200,8 +200,8 @@ type Bytes
type JSON_Format
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> JSON_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> JSON_Format | Nothing
for_file_read file:File_For_Read =
case file.extension of
".json" -> JSON_Format
".geojson" -> JSON_Format
@ -239,3 +239,17 @@ type JSON_Format
## A setting to infer the default behaviour of some option.
type Infer
## PRIVATE
A minimal interface allowing for_file_read
type File_For_Read
## PRIVATE
Arguments:
- `path` - the path or the URI of the file.
- `name` - the name of the file.
- `extension` - the extension of the file.
- `read_first_bytes` - a function that reads the first bytes of the file.
Value path:Text name:Text extension:Text (read_first_bytes:Function=(_->Nothing))
## PRIVATE
File_For_Read.from (that:File) = File_For_Read.Value that.path that.name that.extension that.read_first_bytes

View File

@ -2,7 +2,7 @@ from Standard.Base import all
type Credentials
## Simple username and password type.
Username_And_Password username:Text password:Text
Username_And_Password username:Text password:(Text|Enso_Secret)
## PRIVATE
Override `to_text` to mask the password field.

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.System.File_Format.File_For_Read
import project.Connection.Database
import project.Connection.SQLite_Details.SQLite_Details
@ -11,11 +12,16 @@ type SQLite_Format
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> SQLite_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> SQLite_Format | Nothing
for_file_read file:File_For_Read =
expected_header = magic_header_string
got_header = file.read_first_bytes expected_header.length
if got_header == expected_header then SQLite_Format.For_File else Nothing
if got_header == expected_header then SQLite_Format.For_File else
## To allow for reading a SQLite file by extension if we cannot read the file header.
if got_header.is_nothing.not then Nothing else case file.extension of
".db" -> SQLite_Format.For_File
".sqlite" -> SQLite_Format.For_File
_ -> Nothing
## PRIVATE
If the File_Format supports writing to the file, return a configured instance.

View File

@ -1780,3 +1780,9 @@ simple_unary_op column op_kind = column.make_unary_op op_kind
Materialized_Column.from (that:Column) =
_ = [that]
Error.throw (Illegal_Argument.Error "Currently cross-backend operations are not supported. Materialize the column using `.read` before mixing it with an in-memory Table.")
## PRIVATE
Conversion method to a Column to a Vector.
Vector.from (that:Column) =
_ = [that]
Error.throw (Illegal_Argument.Error "To convert from a Database column to a vector you must first call `read` on the column.")

View File

@ -13,10 +13,10 @@ from Standard.Base.Metadata import make_single_choice
from Standard.Base.Runtime import assert
from Standard.Base.Widget_Helpers import make_delimiter_selector
import Standard.Table.Data.Blank_Selector.Blank_Selector
import Standard.Table.Data.Calculations.Column_Operation.Column_Operation
import Standard.Table.Data.Column_Ref.Column_Ref
import Standard.Table.Data.Constants.Previous_Value
import Standard.Table.Data.Blank_Selector.Blank_Selector
import Standard.Table.Data.Expression.Expression
import Standard.Table.Data.Expression.Expression_Error
import Standard.Table.Data.Join_Condition.Join_Condition
@ -618,7 +618,7 @@ type Table
column = self.evaluate_expression expression on_problems
self.filter column Filter_Condition.Is_True
## ALIAS first, last, slice, sample
## ALIAS first, last, sample, slice
GROUP Standard.Base.Selections
Creates a new Table with the specified range of rows from the input
Table.
@ -928,7 +928,7 @@ type Table
make_temp_column_name : Text
make_temp_column_name self = self.column_naming_helper.make_temp_column_name self.column_names
## PRIVSATE
## PRIVATE
Run a table transformer with a temporary column added.
with_temporary_column : Column -> (Text -> Table -> Table) -> Table
with_temporary_column self new_column:Column f:(Text -> Table -> Table) =
@ -2095,36 +2095,36 @@ type Table
Error.throw (Unsupported_Database_Operation.Error "Table.expand_column is currently not implemented for the Database backend. You may download the table to memory using `.read` to use this feature.")
## GROUP Standard.Base.Conversions
Expand aggregate values in a column to separate rows.
Expand aggregate values in a column to separate rows.
For each value in the specified column, if it is an aggregate (`Vector`,
`Range`, etc.), expand it to multiple rows, duplicating the values in the
other columns.
For each value in the specified column, if it is an aggregate (`Vector`,
`Range`, etc.), expand it to multiple rows, duplicating the values in the
other columns.
Arguments:
- column: The column to expand.
- at_least_one_row: for an empty aggregate value, if `at_least_one_row` is
true, a single row is output with `Nothing` for the aggregates column; if
false, no row is output at all.
Arguments:
- column: The column to expand.
- at_least_one_row: for an empty aggregate value, if `at_least_one_row` is
true, a single row is output with `Nothing` for the aggregates column; if
false, no row is output at all.
The following aggregate values are supported:
- `Array`
- `Vector`
- `List`
- `Range`
- `Date_Range`
- `Pair
The following aggregate values are supported:
- `Array`
- `Vector`
- `List`
- `Range`
- `Date_Range`
- `Pair
Any other values are treated as non-aggregate values, and their rows are kept
unchanged.
Any other values are treated as non-aggregate values, and their rows are kept
unchanged.
In in-memory tables, it is permitted to mix values of different types.
In in-memory tables, it is permitted to mix values of different types.
> Example
Expand a column of integer `Vectors` to a column of `Integer`
> Example
Expand a column of integer `Vectors` to a column of `Integer`
table = Table.new [["aaa", [1, 2]], ["bbb", [[30, 31], [40, 41]]]]
# => Table.new [["aaa", [1, 1, 2, 2]], ["bbb", [30, 31, 40, 41]]]
table = Table.new [["aaa", [1, 2]], ["bbb", [[30, 31], [40, 41]]]]
# => Table.new [["aaa", [1, 1, 2, 2]], ["bbb", [30, 31, 40, 41]]]
@column Widget_Helpers.make_column_name_selector
expand_to_rows : Text | Integer -> Boolean -> Table ! Type_Error | No_Such_Column | Index_Out_Of_Bounds
expand_to_rows self column at_least_one_row=False =

View File

@ -23,7 +23,7 @@ polyglot java import java.sql.DatabaseMetaData
polyglot java import java.sql.PreparedStatement
polyglot java import java.sql.SQLException
polyglot java import java.sql.SQLTimeoutException
polyglot java import java.util.Properties
polyglot java import org.enso.base.enso_cloud.EnsoKeyValuePair
polyglot java import org.enso.database.dryrun.OperationSynchronizer
polyglot java import org.enso.database.JDBCProxy
@ -259,11 +259,10 @@ type JDBC_Connection
- properties: A vector of properties for the connection.
create : Text -> Vector -> JDBC_Connection
create url properties = handle_sql_errors <|
java_props = Properties.new
properties.each pair->
case pair.second of
Nothing -> Polyglot.invoke java_props "remove" [pair.first]
_ -> Polyglot.invoke java_props "setProperty" [pair.first, pair.second]
java_props = properties.map pair-> case pair.second of
Nothing -> EnsoKeyValuePair.ofNothing pair.first
_ : Enso_Secret -> EnsoKeyValuePair.ofSecret pair.first pair.second.id
_ : Text -> EnsoKeyValuePair.ofText pair.first pair.second
java_connection = JDBCProxy.getConnection url java_props
resource = Managed_Resource.register java_connection close_connection

View File

@ -1,7 +1,7 @@
from Standard.Base import all
import Standard.Table.Internal.Naming_Properties.Unlimited_Naming_Properties
import Standard.Table.Internal.Naming_Properties.Enso_Length_Limited_Naming_Properties
import Standard.Table.Internal.Naming_Properties.Unlimited_Naming_Properties
import project.Internal.Connection.Entity_Naming_Properties.Entity_Naming_Properties

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.System.File_Format.File_For_Read
import project.Data.Image.Image
@ -14,8 +15,8 @@ type Image_File_Format
## PRIVATE
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> Image_File_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> Image_File_Format | Nothing
for_file_read file:File_For_Read =
extension = file.extension
if supported.contains extension then Image_File_Format.For_File else Nothing

View File

@ -3,4 +3,4 @@ type Blank_Selector
## Blank_Selector is used as a constructor for other functions.
Any_Cell_Blank
All_Cells_Blank
All_Cells_Blank

View File

@ -2557,6 +2557,10 @@ fill_previous column is_missing =
Conversion method to a Column from a Vector.
Column.from (that:Vector) (name:Text="Vector") = Column.from_vector name that
## PRIVATE
Conversion method to a Column to a Vector.
Vector.from (that:Column) = that.to_vector
## PRIVATE
Conversion method to a Column from a Vector.
Column.from (that:Range) (name:Text="Range") = Column.from_vector name that.to_vector

View File

@ -25,10 +25,10 @@ polyglot java import org.enso.table.parsing.DateParser
polyglot java import org.enso.table.parsing.DateTimeParser
polyglot java import org.enso.table.parsing.IdentityParser
polyglot java import org.enso.table.parsing.NumberParser
polyglot java import org.enso.table.parsing.problems.ParseProblemAggregator
polyglot java import org.enso.table.parsing.TimeOfDayParser
polyglot java import org.enso.table.parsing.TypeInferringParser
polyglot java import org.enso.table.parsing.WhitespaceStrippingParser
polyglot java import org.enso.table.parsing.problems.ParseProblemAggregator
type Data_Formatter
## Specifies options for reading text data in a table to more specific types and

View File

@ -15,8 +15,8 @@ from Standard.Base.Metadata import make_single_choice
from Standard.Base.Widget_Helpers import make_delimiter_selector
import project.Data.Aggregate_Column.Aggregate_Column
import project.Data.Calculations.Column_Operation.Column_Operation
import project.Data.Blank_Selector.Blank_Selector
import project.Data.Calculations.Column_Operation.Column_Operation
import project.Data.Column as Column_Module
import project.Data.Column.Column
import project.Data.Column_Ref.Column_Ref
@ -33,9 +33,9 @@ import project.Data.Report_Unmatched.Report_Unmatched
import project.Data.Row.Row
import project.Data.Set_Mode.Set_Mode
import project.Data.Sort_Column.Sort_Column
import project.Extensions.Table_Conversions
import project.Delimited.Delimited_Format.Delimited_Format
import project.Extensions.Prefix_Name.Prefix_Name
import project.Extensions.Table_Conversions
import project.Extensions.Table_Ref.Table_Ref
import project.Internal.Add_Row_Number
import project.Internal.Aggregate_Column_Helper
@ -54,11 +54,9 @@ import project.Internal.Split_Tokenize
import project.Internal.Table_Helpers
import project.Internal.Table_Helpers.Table_Column_Helper
import project.Internal.Widget_Helpers
from project.Data.Column import get_item_string, normalize_string_for_display
from project.Data.Type.Value_Type import Auto, Value_Type
from project.Errors import all
#from project.Extensions.Table_Conversions import all
from project.Internal.Filter_Condition_Helpers import make_filter_column
from project.Internal.Lookup_Helpers import make_java_lookup_column_description
from project.Internal.Rows_View import Rows_View
@ -73,10 +71,10 @@ polyglot java import org.enso.table.data.table.join.conditions.Equals as Java_Jo
polyglot java import org.enso.table.data.table.join.conditions.EqualsIgnoreCase as Java_Join_Equals_Ignore_Case
polyglot java import org.enso.table.data.table.join.lookup.LookupJoin
polyglot java import org.enso.table.data.table.Table as Java_Table
polyglot java import org.enso.table.error.TooManyColumnsException
polyglot java import org.enso.table.error.NullValuesInKeyColumns
polyglot java import org.enso.table.error.UnmatchedRow
polyglot java import org.enso.table.error.NonUniqueLookupKey
polyglot java import org.enso.table.error.NullValuesInKeyColumns
polyglot java import org.enso.table.error.TooManyColumnsException
polyglot java import org.enso.table.error.UnmatchedRow
polyglot java import org.enso.table.operations.OrderBuilder
polyglot java import org.enso.table.parsing.problems.ParseProblemAggregator
@ -1287,36 +1285,36 @@ type Table
Expand_Objects_Helpers.expand_column self column fields prefix
## GROUP Standard.Base.Conversions
Expand aggregate values in a column to separate rows.
Expand aggregate values in a column to separate rows.
For each value in the specified column, if it is an aggregate (`Vector`,
`Range`, etc.), expand it to multiple rows, duplicating the values in the
other columns.
For each value in the specified column, if it is an aggregate (`Vector`,
`Range`, etc.), expand it to multiple rows, duplicating the values in the
other columns.
Arguments:
- column: The column to expand.
- at_least_one_row: for an empty aggregate value, if `at_least_one_row` is
true, a single row is output with `Nothing` for the aggregates column; if
false, no row is output at all.
Arguments:
- column: The column to expand.
- at_least_one_row: for an empty aggregate value, if `at_least_one_row` is
true, a single row is output with `Nothing` for the aggregates column; if
false, no row is output at all.
The following aggregate values are supported:
- `Array`
- `Vector`
- `List`
- `Range`
- `Date_Range`
- `Pair
The following aggregate values are supported:
- `Array`
- `Vector`
- `List`
- `Range`
- `Date_Range`
- `Pair
Any other values are treated as non-aggregate values, and their rows are kept
unchanged.
Any other values are treated as non-aggregate values, and their rows are kept
unchanged.
In in-memory tables, it is permitted to mix values of different types.
In in-memory tables, it is permitted to mix values of different types.
> Example
Expand a column of integer `Vectors` to a column of `Integer`
> Example
Expand a column of integer `Vectors` to a column of `Integer`
table = Table.new [["aaa", [1, 2]], ["bbb", [[30, 31], [40, 41]]]]
# => Table.new [["aaa", [1, 1, 2, 2]], ["bbb", [30, 31, 40, 41]]]
table = Table.new [["aaa", [1, 2]], ["bbb", [[30, 31], [40, 41]]]]
# => Table.new [["aaa", [1, 1, 2, 2]], ["bbb", [30, 31, 40, 41]]]
@column Widget_Helpers.make_column_name_selector
expand_to_rows : Text | Integer -> Boolean -> Table ! Type_Error | No_Such_Column | Index_Out_Of_Bounds
expand_to_rows self column at_least_one_row=False =
@ -1423,7 +1421,7 @@ type Table
column = self.evaluate_expression expression on_problems
self.filter column Filter_Condition.Is_True
## ALIAS first, last, slice, sample
## ALIAS first, last, sample, slice
GROUP Standard.Base.Selections
Creates a new Table with the specified range of rows from the input
Table.
@ -2662,7 +2660,8 @@ concat_columns column_set all_tables result_type result_row_count on_problems =
Conversion method to a Table from a Column.
Table.from (that:Column) = that.to_table
## Converts a Text value into a Table.
## PRIVATE
Converts a Text value into a Table.
The format of the text is determined by the `format` argument.
@ -2675,7 +2674,8 @@ Table.from (that : Text) (format:Delimited_Format = Delimited_Format.Delimited '
_ : Delimited_Format -> Delimited_Reader.read_text that format on_problems
_ -> Unimplemented.throw "Table.from is currently only implemented for Delimited_Format."
## Converts a Table into a Text value.
## PRIVATE
Converts a Table into a Text value.
The format of the text is determined by the `format` argument.
@ -2691,7 +2691,8 @@ Text.from (that : Table) (format:Delimited_Format = Delimited_Format.Delimited '
Conversion method to a Table from a Vector.
Table.from (that:Vector) (fields : (Vector | Nothing) = Nothing) = that.to_table fields
## Convert an `XML_Element` into a `Table`
## PRIVATE
Convert an `XML_Element` into a `Table`
Generates a single-row table with columns for the tag's contents.
@ -2711,7 +2712,8 @@ Table.from (that:XML_Element) =
# Generating a table from an `XML_Element` is the same logic as `expand_column`
Table.new [["x", [that]]] . expand_column 'x' prefix=Prefix_Name.None
## Convert an `XML_Document` into a `Table`
## PRIVATE
Convert an `XML_Document` into a `Table`
Generates a single-row table with columns for the root tag's contents.

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Network.HTTP.Response.Response
import Standard.Base.System.File_Format.File_For_Read
import Standard.Base.System.Input_Stream.Input_Stream
from Standard.Base.Widget_Helpers import make_delimiter_selector
@ -58,8 +59,8 @@ type Delimited_Format
## PRIVATE
ADVANCED
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> Delimited_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> Delimited_Format | Nothing
for_file_read file:File_For_Read =
case file.extension of
".csv" -> Delimited_Format.Delimited ','
".tab" -> Delimited_Format.Delimited '\t'

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.System.File_Format.File_For_Read
import Standard.Base.System.Input_Stream.Input_Stream
import project.Data.Match_Columns.Match_Columns
@ -49,8 +50,8 @@ type Excel_Format
## PRIVATE
ADVANCED
If the File_Format supports reading from the file, return a configured instance.
for_file_read : File -> Excel_Format | Nothing
for_file_read file =
for_file_read : File_For_Read -> Excel_Format | Nothing
for_file_read file:File_For_Read =
is_xls = should_treat_as_xls_format Infer file
if is_xls.is_error then Nothing else
Excel_Format.Excel xls_format=is_xls

View File

@ -207,6 +207,7 @@ type Excel_Workbook
Arguments:
- name: the name of the worksheet to read.
@name (self-> Single_Choice display=Display.Always values=(self.sheet_names.map t-> Option t t.pretty))
sheet : Text | Integer -> Table
sheet self name:(Text|Integer) =
self.read_section (Excel_Section.Worksheet name 0 Nothing)

View File

@ -1,8 +1,14 @@
from Standard.Base import all
type Prefix_Name
None
Column_Name
Custom value:Text
## Do not add a prefix to the column name.
None
Prefix_Name.from (that:Text) = Prefix_Name.Custom that
## Use the column name as a prefix.
Column_Name
## Add a custom prefix to the new name.
Custom value:Text
## PRIVATE
Prefix_Name.from (that:Text) = Prefix_Name.Custom that

View File

@ -8,7 +8,6 @@ import project.Data.Set_Mode.Set_Mode
import project.Data.Table.Table
from project.Errors import No_Such_Column, Existing_Column, Missing_Column
## PRIVATE
A helper type allowing to resolve column references in a context of an underlying table.
type Table_Ref

View File

@ -1,8 +1,8 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import project.Internal.Naming_Properties.Unlimited_Naming_Properties
import project.Internal.Naming_Properties.Enso_Length_Limited_Naming_Properties
import project.Internal.Naming_Properties.Unlimited_Naming_Properties
import project.Internal.Unique_Name_Strategy.Unique_Name_Strategy
from project.Errors import Clashing_Column_Name, Invalid_Column_Names
from project.Internal.Table_Helpers import is_column

View File

@ -1,13 +1,12 @@
from Standard.Base import all
import Standard.Base.Errors.Common.Index_Out_Of_Bounds
import Standard.Base.Errors.Common.Type_Error
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import project.Data.Table.Table
import project.Data.Column.Column
import project.Data.Conversions.Convertible_To_Columns.Convertible_To_Columns
import project.Data.Conversions.Convertible_To_Rows.Convertible_To_Rows
import project.Data.Table.Table
import project.Errors.No_Such_Column
import project.Extensions.Prefix_Name.Prefix_Name
import project.Internal.Fan_Out
@ -41,7 +40,8 @@ expand_column (table : Table) (column : Text | Integer) (fields : (Vector Text)
Table.new output_builder.to_vector
## GROUP Standard.Base.Conversions
## PRIVATE
GROUP Standard.Base.Conversions
Expand aggregate values in a column to separate rows.
For each value in the specified column, if it is an aggregate (`Vector`,

View File

@ -1,8 +1,8 @@
from Standard.Base import all
import project.Data.Aggregate_Column.Aggregate_Column
import project.Data.Calculations.Column_Operation.Column_Operation
import project.Data.Blank_Selector.Blank_Selector
import project.Data.Calculations.Column_Operation.Column_Operation
import project.Data.Column.Column
import project.Data.Column_Ref.Column_Ref
import project.Data.Column_Vector_Extensions
@ -31,8 +31,9 @@ from project.Excel.Excel_Section.Excel_Section import Cell_Range, Range_Names, S
from project.Extensions.Table_Conversions import all
export project.Data.Aggregate_Column.Aggregate_Column
export project.Data.Calculations.Column_Operation.Column_Operation
export project.Data.Blank_Selector.Blank_Selector
export project.Data.Calculations.Column_Operation.Column_Operation
export project.Data.Column.Column
export project.Data.Column_Ref.Column_Ref
export project.Data.Column_Vector_Extensions
@ -59,3 +60,4 @@ from project.Delimited.Delimited_Format.Delimited_Format export Delimited
from project.Excel.Excel_Format.Excel_Format export Excel
from project.Excel.Excel_Section.Excel_Section export Cell_Range, Range_Names, Sheet_Names, Worksheet
from project.Extensions.Table_Conversions export all

View File

@ -95,6 +95,9 @@ object DistributionPackage {
def executableName(baseName: String): String =
if (Platform.isWindows) baseName + ".exe" else baseName
private def batName(baseName: String): String =
if (Platform.isWindows) baseName + ".bat" else baseName
def createProjectManagerPackage(
distributionRoot: File,
cacheFactory: CacheStoreFactory
@ -266,7 +269,7 @@ object DistributionPackage {
): Boolean = {
import scala.collection.JavaConverters._
val enso = distributionRoot / "bin" / "enso"
val enso = distributionRoot / "bin" / batName("enso")
log.info(s"Executing $enso ${args.mkString(" ")}")
val pb = new java.lang.ProcessBuilder()
val all = new java.util.ArrayList[String]()

View File

@ -1,54 +0,0 @@
package org.enso.base;
import java.net.http.HttpHeaders;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import org.enso.base.net.http.BasicAuthorization;
import org.enso.base.net.http.MultipartBodyBuilder;
import org.enso.base.net.http.UrlencodedBodyBuilder;
/** 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();
}
}

View File

@ -0,0 +1,23 @@
package org.enso.base.enso_cloud;
public class AuthenticationProvider {
private static String token;
public static String setToken(String token) {
AuthenticationProvider.token = token;
return AuthenticationProvider.token;
}
public static String getToken() {
return AuthenticationProvider.token;
}
public static String getAPIRootURI() {
var envUri = System.getenv("ENSO_CLOUD_API_URI");
return envUri == null ? "https://7aqkn3tnbc.execute-api.eu-west-1.amazonaws.com/" : envUri;
}
public static void flushCloudCaches() {
EnsoSecretReader.flushCache();
}
}

View File

@ -0,0 +1,21 @@
package org.enso.base.enso_cloud;
record EnsoKeySecretPair(String key, String secretId) implements EnsoKeyValuePair {}
record EnsoKeyStringPair(String key, String value) implements EnsoKeyValuePair {}
public sealed interface EnsoKeyValuePair permits EnsoKeySecretPair, EnsoKeyStringPair {
String key();
static EnsoKeyValuePair ofNothing(String key) {
return new EnsoKeyStringPair(key, null);
}
static EnsoKeyValuePair ofText(String key, String value) {
return new EnsoKeyStringPair(key, value);
}
static EnsoKeyValuePair ofSecret(String key, String secretId) {
return new EnsoKeySecretPair(key, secretId);
}
}

View File

@ -0,0 +1,144 @@
package org.enso.base.enso_cloud;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Makes HTTP requests with secrets in either header or query string.
*/
public class EnsoSecretHelper {
/**
* Gets the value of an EnsoKeyValuePair resolving secrets.
* @param pair The pair to resolve.
* @return The pair's value. Should not be returned to Enso.
*/
private static String resolveValue(EnsoKeyValuePair pair) {
return switch (pair) {
case EnsoKeyStringPair stringPair -> stringPair.value();
case EnsoKeySecretPair secretPair ->
EnsoSecretReader.readSecret(secretPair.secretId());
case null ->
throw new IllegalArgumentException("EnsoKeyValuePair should not be NULL.");
};
}
/**
* Converts an EnsoKeyValuePair into a string for display purposes. Does not include secrets.
* @param pair The pair to render.
* @return The rendered string.
*/
private static String renderValue(EnsoKeyValuePair pair) {
return switch (pair) {
case EnsoKeyStringPair stringPair -> stringPair.value();
case EnsoKeySecretPair _ -> "__SECRET__";
case null ->
throw new IllegalArgumentException("EnsoKeyValuePair should not be NULL.");
};
}
/**
* Substitutes the minimal parts within the string for the URI parse.
* */
public static String encodeArg(String arg, boolean includeEquals) {
var encoded = arg.replace("%", "%25")
.replace("&", "%26")
.replace(" ", "%20");
if (includeEquals) {
encoded = encoded.replace("=", "%3D");
}
return encoded;
}
/**
* Replaces the query string in a URI.
* */
public static URI replaceQuery(URI uri, String newQuery) throws URISyntaxException {
var baseURI = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), null, null).toString();
var baseFragment = uri.getFragment();
baseFragment = baseFragment != null && !baseFragment.isBlank() ? "#" + baseFragment : "";
return URI.create(baseURI + newQuery + baseFragment);
}
private static String makeQueryAry(EnsoKeyValuePair pair, Function<EnsoKeyValuePair, String> resolver) {
String resolvedKey = pair.key() != null && !pair.key().isBlank() ? encodeArg(pair.key(), true) + "=" : "";
String resolvedValue = encodeArg(resolver.apply(pair), false);
return resolvedKey + resolvedValue;
}
//** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. **//
public static Connection getJDBCConnection(String url, EnsoKeyValuePair[] properties)
throws SQLException {
var javaProperties = new Properties();
for (EnsoKeyValuePair pair : properties) {
javaProperties.setProperty(pair.key(), resolveValue(pair));
}
return DriverManager.getConnection(url, javaProperties);
}
//** Makes a request with secrets in the query string or headers. **//
public static EnsoHttpResponse makeRequest(HttpClient client, Builder builder, URI uri, List<EnsoKeyValuePair> queryArguments, List<EnsoKeyValuePair> headerArguments)
throws IOException, InterruptedException {
// Build a new URI with the query arguments.
URI resolvedURI = uri;
URI renderedURI = uri;
if (queryArguments != null && !queryArguments.isEmpty()) {
try {
var baseQuery = uri.getQuery();
baseQuery = baseQuery != null && !baseQuery.isBlank() ? "?" + baseQuery + "&" : "?";
var query = baseQuery + queryArguments.stream().map(p -> makeQueryAry(p, EnsoSecretHelper::resolveValue)).collect(Collectors.joining("&"));
resolvedURI = replaceQuery(uri, query);
renderedURI = resolvedURI;
if (queryArguments.stream().anyMatch(p -> p instanceof EnsoKeySecretPair)) {
if (!resolvedURI.getScheme().equals("https")) {
// If used a secret then only allow HTTPS
throw new IllegalArgumentException("Cannot use secrets in query string with non-HTTPS URI.");
}
var renderedQuery = baseQuery + queryArguments.stream().map(p -> makeQueryAry(p, EnsoSecretHelper::renderValue)).collect(Collectors.joining("&"));
renderedURI = replaceQuery(uri, renderedQuery);
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Unable to build a valid URI.");
}
}
builder.uri(resolvedURI);
// Resolve the header arguments.
if (headerArguments != null && !headerArguments.isEmpty()) {
for (EnsoKeyValuePair header : headerArguments) {
builder.header(header.key(), resolveValue(header));
}
}
// Build and Send the request.
var httpRequest = builder.build();
var bodyHandler = HttpResponse.BodyHandlers.ofInputStream();
var javaResponse = client.send(httpRequest, bodyHandler);
// Extract parts of the response
return new EnsoHttpResponse(renderedURI, javaResponse.headers().map().keySet().stream().toList(), javaResponse.headers(), javaResponse.body(), javaResponse.statusCode());
}
/**
* A subset of the HttpResponse to avoid leaking the decrypted Enso secrets.
*/
public record EnsoHttpResponse(URI uri, List<String> headerNames, HttpHeaders headers, InputStream body, int statusCode) { }
}

View File

@ -0,0 +1,60 @@
package org.enso.base.enso_cloud;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
/** * Internal class to read secrets from the Enso Cloud. */
class EnsoSecretReader {
private static final Map<String, String> secrets = new HashMap<>();
static void flushCache() {
secrets.clear();
}
/**
* * Reads a secret from the Enso Cloud.
*
* @param secretId the ID of the secret to read.
* @return the secret value.
*/
static String readSecret(String secretId) {
if (secrets.containsKey(secretId)) {
return secrets.get(secretId);
}
var apiUri = AuthenticationProvider.getAPIRootURI() + "/secrets/" + secretId;
var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();
var request =
HttpRequest.newBuilder()
.uri(URI.create(apiUri))
.header("Authorization", "Bearer " + AuthenticationProvider.getToken())
.GET()
.build();
HttpResponse<String> response;
try {
response = client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new IllegalArgumentException("Unable to read secret.");
}
if (response.statusCode() != 200) {
throw new IllegalArgumentException("Unable to read secret.");
}
var secretJSON = response.body();
var secretValue = readValueFromString(secretJSON);
secrets.put(secretId, secretValue);
return secretValue;
}
private static String readValueFromString(String json) {
return json.substring(1, json.length() - 1).translateEscapes();
}
}

View File

@ -1,21 +0,0 @@
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;
}
}

View File

@ -3,8 +3,9 @@ package org.enso.database;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.ServiceLoader;
import org.enso.base.enso_cloud.EnsoKeyValuePair;
import org.enso.base.enso_cloud.EnsoSecretHelper;
/**
* A helper class for accessing the JDBC components.
@ -35,7 +36,8 @@ public class JDBCProxy {
* @param properties configuration for the connection
* @return a connection
*/
public static Connection getConnection(String url, Properties properties) throws SQLException {
public static Connection getConnection(String url, EnsoKeyValuePair[] properties)
throws SQLException {
// We need to manually register all the drivers because the DriverManager is not able
// to correctly use our class loader, it only delegates to the platform class loader when
// loading the java.sql.Driver service.
@ -43,6 +45,7 @@ public class JDBCProxy {
for (var driver : sl) {
DriverManager.registerDriver(driver);
}
return DriverManager.getConnection(url, properties);
return EnsoSecretHelper.getJDBCConnection(url, properties);
}
}

View File

@ -387,7 +387,7 @@ type_spec name alter = Test.group name <|
Test.specify "should define concatenation" <|
concat = (alter [1, 2, 3]) + (alter [4, 5, 6])
concat.should_equal [1, 2, 3, 4, 5, 6]
(alter [1, 2, 3])+1 . should_fail_with Type_Error
Test.expect_panic_with matcher=Type_Error ((alter [1, 2, 3])+1)
Test.specify "should allow finding a value" <|
input = alter [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

View File

@ -78,7 +78,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Length": "0"
},
"origin": "127.0.0.1",
@ -119,7 +122,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Length": "0"
},
"origin": "127.0.0.1",
@ -166,7 +172,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "11"
},
@ -189,7 +198,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "20"
},
@ -209,7 +221,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "20"
},
@ -228,7 +243,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "50"
},
@ -247,7 +265,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "47"
},
@ -270,7 +291,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-16LE",
"Content-Length": "24"
},
@ -289,7 +313,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/csv; charset=UTF-8",
"Content-Length": "6"
},
@ -343,7 +370,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "11"
},
@ -362,7 +392,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "11"
},
@ -381,7 +414,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/diff; charset=UTF-8",
"Content-Length": "11"
},
@ -400,7 +436,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Length": "0"
},
"origin": "127.0.0.1",
@ -418,7 +457,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "11"
},
@ -437,7 +479,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "11",
"Custom": "asdf"
@ -483,7 +528,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "23"
},
@ -502,7 +550,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "23"
},
@ -521,7 +572,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "application/json",
"Content-Length": "23"
},
@ -540,7 +594,10 @@ spec =
expected_response = Json.parse <| '''
{
"headers": {
"Connection": "Upgrade, HTTP2-Settings",
"Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA",
"User-Agent": "Java-http-client/21.0.1",
"Upgrade": "h2c",
"Content-Type": "text/plain; charset=UTF-8",
"Content-Length": "23"
},

View File

@ -12,22 +12,22 @@ spec =
addr.user_info.should_equal "user:pass"
addr.host.should_equal "example.com"
addr.authority.should_equal "user:pass@example.com"
addr.port.should_fail_with Nothing
addr.port.should_equal Nothing
addr.path.should_equal "/foo/bar"
addr.query.should_equal "key=val"
addr.fragment.should_fail_with Nothing
addr.fragment.should_equal Nothing
Test.specify "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_fail_with Nothing
addr.fragment.should_equal Nothing
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_fail_with Nothing
addr.raw_fragment.should_equal Nothing
Test.specify "should return Syntax_Error when parsing invalid URI" <|
URI.parse "a b c" . should_fail_with Syntax_Error