Widget support for Data.fetch and Data.post. (#11199)

- Add `pretty` for `Date` and `Time`.
- Add constructors for `JS_Object` and `Dictionary` to the component browser.
- Add widgets to `Dictionary` methods.
![image](https://github.com/user-attachments/assets/4f6c58d5-9eb5-40e5-96c1-2e06e23051d0)
- Add conversion from Vector to Dictionary.
- Add `pair` method shorthand for `Pair.Value`.
- Created widget for `Header`.
- Added widgets for `Data.fetch` and `Data.post`.
- Added widgets for `Request_Body` constructors.
- Update the Forbidden Operation message to be friendlier.
![image](https://github.com/user-attachments/assets/eaac5def-a91f-450f-b814-d776311962e3)

Video before fixing Forbidden Message:

https://github.com/user-attachments/assets/f9c4bde4-3f0a-49f1-a3ca-a0aaa3219286
This commit is contained in:
James Dunkerley 2024-09-27 19:08:12 +01:00 committed by GitHub
parent ecfe959c29
commit 28bbc34257
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 191 additions and 55 deletions

View File

@ -16,7 +16,7 @@ instance =
## PRIVATE
create_dry_run_file file copy_original =
_ = [file, copy_original]
Error.throw (Forbidden_Operation.Error "Currently dry-run is not supported for S3_File, so writing to an S3_File is forbidden if the Output context is disabled.")
Error.throw (Forbidden_Operation.Error "As writing is disabled, no action has been performed. Press the Write button ▶ to write the file.")
## PRIVATE
remote_write_with_local_file file existing_file_behavior action =

View File

@ -67,7 +67,7 @@ type S3_File
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 : Vector) action = if self.is_directory then Error.throw (S3_Error.Error "S3 directory cannot be opened as a stream." self.uri) else
Context.Output.if_enabled disabled_message="Writing to an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to S3. Press the Write button ▶ to perform the operation." panic=False <|
open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && (Data_Link.is_data_link self)
if open_as_data_link then Data_Link_Helpers.write_data_link_as_stream self open_options action else
if open_options.contains File_Access.Append then Error.throw (S3_Error.Error "S3 does not support appending to a file. Instead you may read it, modify and then write the new contents." self.uri) else
@ -251,7 +251,7 @@ type S3_File
copy_to : File_Like -> Boolean -> Any ! S3_Error | File_Error
copy_to self (destination : File_Like) (replace_existing : Boolean = False) = Data_Link_Helpers.disallow_links_in_copy self destination <|
if self.is_directory then Error.throw (S3_Error.Error "Copying S3 folders is currently not implemented." self.uri) else
Context.Output.if_enabled disabled_message="Copying an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot copy to a file. Press the Write button ▶ to perform the operation." panic=False <|
destination_writable = Writable_File.from destination
case destination_writable.file of
# Special shortcut for more efficient handling of S3 file copying (no need to move the data to our machine)
@ -292,7 +292,7 @@ type S3_File
move_to self (destination : File_Like) (replace_existing : Boolean = False) =
if self.is_directory then Error.throw (S3_Error.Error "Moving S3 folders is currently not implemented." self.uri) else
Data_Link_Helpers.disallow_links_in_move self destination <|
Context.Output.if_enabled disabled_message="File moving is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot move the file. Press the Write button ▶ to perform the operation." panic=False <|
r = self.copy_to destination replace_existing=replace_existing
r.if_not_error <|
# If source and destination are the same, we do not want to delete the file
@ -341,7 +341,7 @@ type S3_File
will occur.
delete_if_exists : Boolean -> Nothing
delete_if_exists self (recursive : Boolean = False) =
Context.Output.if_enabled disabled_message="Deleting an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot delete the file. Press the Write button ▶ to perform the operation." panic=False <|
case self.is_directory of
True ->
# This is a temporary simplified implementation to ensure cleaning up after tests

View File

@ -196,8 +196,9 @@ list (directory:(Text | File)=enso_project.root) (name_filter:Text="") recursive
Data.fetch URL . body . write file
@uri Text_Input
@format Data_Read_Helpers.format_widget_with_raw_response
@headers Header.default_widget
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any ! Request_Error | HTTP_Error
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) (format = Auto_Detect) =
fetch (uri:(URI | Text)) (method:HTTP_Method=..Get) (headers:(Vector (Header | Pair Text Text))=[]) (format = Auto_Detect) =
Data_Read_Helpers.fetch_following_data_links uri method headers (Data_Read_Helpers.handle_legacy_format "fetch" "format" format)
## ALIAS http post, upload
@ -321,9 +322,10 @@ fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (
form_data = Dictionary.from_vector [["key", "val"], ["a_file", test_file]]
response = Data.post url_post (Request_Body.Form_Data form_data url_encoded=True)
@uri Text_Input
@headers Header.default_widget
@response_format Data_Read_Helpers.format_widget_with_raw_response
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any ! Request_Error | HTTP_Error
post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =
post (uri:(URI | Text)) (body:Request_Body=..Empty) (method:HTTP_Method=..Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =
response = HTTP.post uri body method headers
Data_Read_Helpers.decode_http_response_following_data_links response (Data_Read_Helpers.handle_legacy_format "post" "response_format" response_format)
@ -340,9 +342,10 @@ post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Meth
Defaults to `HTTP_Method.Get`.
- headers: The headers to send with the request. Defaults to an empty vector.
@uri Text_Input
@headers Header.default_widget
download : (URI | Text) -> Writable_File -> HTTP_Method -> Vector (Header | Pair Text Text) -> File ! Request_Error | HTTP_Error
download (uri:(URI | Text)) file:Writable_File (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
Context.Output.if_enabled disabled_message="Downloading to a file is forbidden as the Output context is disabled." panic=False <|
download (uri:(URI | Text)) file:Writable_File (method:HTTP_Method=..Get) (headers:(Vector (Header | Pair Text Text))=[]) =
Context.Output.if_enabled disabled_message="As writing is disabled, cannot download to a file. Press the Write button ▶ to perform the operation." panic=False <|
response = HTTP.fetch uri method headers
case Data_Link.is_data_link response.body.metadata of
True ->

View File

@ -4,12 +4,18 @@ import project.Data.Pair.Pair
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Missing_Argument
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Errors.No_Such_Key.No_Such_Key
import project.Meta
import project.Metadata.Widget
import project.Nothing.Nothing
import project.Panic.Panic
from project.Data.Boolean import Boolean, False, True
from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice, Vector_Editor
from project.Widget_Helpers import make_all_selector
## A key-value store. It is possible to use any type as keys and values and mix
them in one Dictionary. Keys are checked for equality based on their hash
@ -32,14 +38,12 @@ from project.Data.Text.Extensions import all
to pass a foreign map into Enso, where it will be treated as a Dictionary.
@Builtin_Type
type Dictionary key value
## PRIVATE
ADVANCED
## ICON array_new2
Returns an empty dictionary.
empty : Dictionary
empty = @Builtin_Method "Dictionary.empty"
## PRIVATE
ADVANCED
## ICON array_new2
Returns a single-element dictionary with the given key and value.
A Call to `Dictionary.singleton key value` is the same as a call to
`Dictionary.empty.insert key value`.
@ -53,12 +57,14 @@ type Dictionary key value
value 2.
example_singleton = Dictionary.singleton "my_key" 2
@key (make_all_selector ..Always)
@value (make_all_selector ..Always)
singleton : Any -> Any -> Dictionary
singleton key value = Dictionary.empty.insert key value
## ALIAS dictionary, lookup table
GROUP Constants
ICON convert
ICON array_new2
Builds a dictionary from two Vectors. The first vector contains the keys,
and the second vector contains the values. The two vectors must be of the
same length.
@ -71,6 +77,8 @@ type Dictionary key value
meaning that if two entries in the vector share the same key, an
`Illegal_Argument` error is raised. If set to `False`, the last entry
with a given key will be kept.
@keys (Vector_Editor item_editor=make_all_selector display=..Always item_default="Nothing")
@values (Vector_Editor item_editor=make_all_selector display=..Always item_default="Nothing")
from_keys_and_values : Vector Any -> Vector Any -> Boolean -> Dictionary ! Illegal_Argument
from_keys_and_values keys:Vector values:Vector error_on_duplicates:Boolean=True =
if keys.length != values.length then Error.throw (Illegal_Argument.Error "`Dictionary.from_keys_and_values` encountered two vectors of different lengths.") else
@ -80,7 +88,7 @@ type Dictionary key value
## ALIAS dictionary, lookup table
GROUP Constants
ICON convert
ICON array_new2
Builds a dictionary from a vector of key-value pairs, with each key-value
pair represented as a 2 element vector.
@ -96,8 +104,9 @@ type Dictionary key value
Building a dictionary containing two key-value pairs.
example_from_vector = Dictionary.from_vector [["A", 1], ["B", 2]]
@vec key_value_widget
from_vector : Vector Any -> Boolean -> Dictionary ! Illegal_Argument
from_vector vec error_on_duplicates=True =
from_vector vec error_on_duplicates:Boolean=True =
vec.fold Dictionary.empty m-> el-> if el.length != 2 then Error.throw (Illegal_Argument.Error "`Dictionary.from_vector` encountered an invalid value. Each value in the vector has to be a key-value pair - it must have exactly 2 elements.") else
key = el.at 0
value = el.at 1
@ -152,8 +161,11 @@ type Dictionary key value
import Standard.Examples
example_insert = Examples.dictionary.insert 7 "seven"
@key (make_all_selector ..Always)
@value (make_all_selector ..Always)
insert : Any -> Any -> Dictionary
insert self key value = @Builtin_Method "Dictionary.insert"
insert self key=(Missing_Argument.throw "key") value=(Missing_Argument.throw "value") =
self.insert_builtin key value
## GROUP Selections
ICON table_clean
@ -170,8 +182,9 @@ type Dictionary key value
import Standard.Examples
Examples.dictionary.remove "A"
@key key_widget
remove : Any -> Dictionary ! No_Such_Key
remove self key =
remove self key=(Missing_Argument.throw "key") =
Panic.catch Any (self.remove_builtin key) _->
Error.throw (No_Such_Key.Error self key)
@ -191,6 +204,7 @@ type Dictionary key value
import Standard.Examples
example_at = Examples.dictionary.at "A"
@key key_widget
at : Any -> Any ! No_Such_Key
at self key = self.get key (Error.throw (No_Such_Key.Error self key))
@ -211,12 +225,14 @@ type Dictionary key value
import Standard.Examples
example_get = Examples.dictionary.get 2 "zero"
@key key_widget
get : Any -> Any -> Any
get self key ~if_missing=Nothing = self.get_builtin key if_missing
## GROUP Logical
ICON preparation
Returns True iff the Dictionary contains the given `key`.
@key (make_all_selector ..Always)
contains_key : Any -> Boolean
contains_key self key = @Builtin_Method "Dictionary.contains_key"
@ -423,3 +439,19 @@ type Dictionary key value
## PRIVATE
get_builtin : Any -> Any -> Any
get_builtin self key ~if_missing = @Builtin_Method "Dictionary.get_builtin"
## PRIVATE
key_widget dict:Dictionary -> Widget =
values = dict.keys.map k-> Option k.to_text k.pretty
Single_Choice display=..Always values=values
## PRIVATE
key_value_widget -> Widget =
fqn = Meta.get_qualified_type_name Pair . drop (..Last 5)
default = 'pair "key" Nothing'
pair = Option "Pair" fqn+".pair" [["first", make_all_selector], ["second", make_all_selector]]
item_editor = Single_Choice display=..Always values=[pair]
Vector_Editor item_editor=item_editor display=..Always item_default=default
## PRIVATE
Dictionary.from (that:Vector) = Dictionary.from_vector that

View File

@ -29,7 +29,8 @@ from project.Data.Ordering import all
from project.Data.Range.Extensions import all
from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice
from project.Metadata.Widget import Single_Choice, Text_Input, Vector_Editor
from project.Widget_Helpers import make_all_selector
polyglot java import com.fasterxml.jackson.core.JsonLocation
polyglot java import com.fasterxml.jackson.core.JsonProcessingException
@ -161,10 +162,11 @@ type JS_Object
loop name_iterator builder
JS_Object.Value object_node (make_field_names object_node)
## PRIVATE
Creates a Jackson_Object from a list of key-value pairs.
## ICON braces
Creates a JS_Object from a list of key-value pairs.
Keys must be `Text` values.
Values will be recursively converted to JSON serializable as needed.
@pairs key_value_widget
from_pairs : Vector -> JS_Object
from_pairs pairs =
mapper = ObjectMapper.new
@ -348,7 +350,7 @@ type JS_Object
## PRIVATE
Make a field name selector
make_field_name_selector : JS_Object -> Display -> Widget
make_field_name_selector js_object display=Display.Always =
make_field_name_selector js_object display:Display=..Always =
Single_Choice display=display values=(js_object.field_names.map n->(Option n n.pretty))
## PRIVATE
@ -423,3 +425,12 @@ make_enso object =
_ -> js_object
if parsed.is_error then js_object else parsed
## PRIVATE
key_value_widget : Widget
key_value_widget =
fqn = Meta.get_qualified_type_name Pair . drop (..Last 5)
default = 'pair "key" Nothing'
pair = Option "Pair" fqn+".pair" [["first", Text_Input], ["second", make_all_selector]]
item_editor = Single_Choice display=..Always values=[pair]
Vector_Editor item_editor=item_editor display=..Always item_default=default

View File

@ -295,3 +295,12 @@ check_start_valid start function max=3 =
used_start = if start < 0 then start + 2 else start
if used_start < 0 || used_start >= max then Error.throw (Index_Out_Of_Bounds.Error start max) else
function used_start
## ICON array_new
Create a new Pair from two elements.
Arguments:
- first: The first element.
- second: The second element.
pair : Any -> Any -> Pair
pair first second -> Pair = Pair.new first second

View File

@ -826,6 +826,11 @@ type Date
format self format:Date_Time_Formatter=Date_Time_Formatter.iso_date =
format.format_date self
## PRIVATE
Convert to a Enso code representation of this Date.
pretty : Text
pretty self = "(Date.new " + self.year.to_text + " " + self.month.to_text + " " + self.day.to_text + ")"
## PRIVATE
week_days_between start end =
## We split the interval into 3 periods: the first week (containing the

View File

@ -491,6 +491,18 @@ type Time_Of_Day
format self format:Date_Time_Formatter =
format.format_time self
## PRIVATE
Convert to a Enso code representation of this Time_Of_Day.
pretty : Text
pretty self = "(Time_Of_Day.new "
+ (if self.hour == 0 then "" else " hour="+self.hour.to_text)
+ (if self.minute == 0 then "" else " minute="+self.minute.to_text)
+ (if self.second == 0 then "" else " second="+self.second.to_text)
+ (if self.millisecond == 0 then "" else " millisecond="+self.millisecond.to_text)
+ (if self.microsecond == 0 then "" else " microsecond="+self.microsecond.to_text)
+ (if self.nanosecond == 0 then "" else " nanosecond="+self.nanosecond.to_text)
+ ")"
## PRIVATE
Time_Of_Day.from (that:JS_Object) =
## Must have hour and minute but second and nanosecond are optional

View File

@ -211,7 +211,7 @@ type Enso_File
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 : Vector) action =
Context.Output.if_enabled disabled_message="Writing to an Enso_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to a file. Press the Write button ▶ to perform the operation." panic=False <|
is_data_link = Data_Link.is_data_link self
open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && is_data_link
if open_as_data_link then Data_Link_Helpers.write_data_link_as_stream self open_options action else

View File

@ -13,7 +13,6 @@ 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.Network.URI.URI
import project.Nothing.Nothing
import project.Runtime.Context
@ -45,7 +44,7 @@ type Enso_Secret
created in the current working directory.
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
Context.Output.if_enabled disabled_message="Creating a secret is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot create a secret. Press the Write button ▶ to perform the operation." panic=False <|
if name.starts_with "connection-" then Error.throw (Illegal_Argument.Error "Secret name cannot start with 'connection-'") else
parent_dir = parent.if_nothing Enso_File.current_working_directory
path = if name.contains "/" then Error.throw (Illegal_Argument.Error "Secret name cannot contain `/`.") else parent_dir.enso_path.resolve name
@ -65,7 +64,7 @@ type Enso_Secret
Deletes a secret.
delete : Enso_Secret
delete self =
Context.Output.if_enabled disabled_message="Deleting a secret is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot delete secret. Press the Write button ▶ to perform the operation." panic=False <|
uri = URI.from (secret_asset_uri self) . add_query_argument "force" "true"
response = Utils.http_request HTTP_Method.Delete uri
response.if_not_error self
@ -140,7 +139,7 @@ type Enso_Secret
- new_value: The new value of the secret
update_value : Text -> Enso_Secret
update_value self (new_value : Text) =
Context.Output.if_enabled disabled_message="Updating a secret is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot update secret. Press the Write button ▶ to perform the operation." panic=False <|
body = JS_Object.from_pairs [["value", new_value]]
response = Utils.http_request HTTP_Method.Put (secret_resource_uri self) body
response.if_not_error <|

View File

@ -22,7 +22,7 @@ instance =
## PRIVATE
create_dry_run_file file copy_original =
_ = [file, copy_original]
Error.throw (Forbidden_Operation.Error "Currently dry-run is not supported for Enso_File, so writing to an Enso_File is forbidden if the Output context is disabled.")
Error.throw (Forbidden_Operation.Error "As writing is disabled, no action has been performed. Press the Write button ▶ to write the file.")
## PRIVATE

View File

@ -72,7 +72,7 @@ flush_caches = CloudAPI.flushCloudCaches
## PRIVATE
Performs a standard request to the Enso Cloud API,
parsing the result as JSON.
http_request_as_json (method : HTTP_Method) (url : URI) (body : Request_Body = Request_Body.Empty) (additional_headers : Vector = []) (error_handlers : Dictionary Text (Any -> Any) = Dictionary.empty) (retries : Integer = 3) -> Any ! Enso_Cloud_Error =
http_request_as_json (method : HTTP_Method) (url : URI) (body : Request_Body = ..Empty) (additional_headers : Vector = []) (error_handlers : Dictionary Text (Any -> Any) = Dictionary.empty) (retries : Integer = 3) -> Any ! Enso_Cloud_Error =
response = http_request method url body additional_headers error_handlers retries
response.decode_as_json.catch Invalid_JSON error->
Error.throw (Enso_Cloud_Error.Invalid_Response_Payload error)
@ -87,7 +87,7 @@ http_request_as_json (method : HTTP_Method) (url : URI) (body : Request_Body = R
Custom error handlers can be provided as a mapping from error codes
(defined in the cloud project) to functions that take the full JSON payload
and return a custom error.
http_request (method : HTTP_Method) (url : URI) (body : Request_Body = Request_Body.Empty) (additional_headers : Vector = []) (error_handlers : Dictionary Text (Any -> Any) = Dictionary.empty) (retries : Integer = 3) -> Response ! Enso_Cloud_Error = method.if_not_error <| url.if_not_error <| body.if_not_error <| additional_headers.if_not_error <|
http_request (method : HTTP_Method) (url : URI) (body : Request_Body = ..Empty) (additional_headers : Vector = []) (error_handlers : Dictionary Text (Any -> Any) = Dictionary.empty) (retries : Integer = 3) -> Response ! Enso_Cloud_Error = method.if_not_error <| url.if_not_error <| body.if_not_error <| additional_headers.if_not_error <|
all_headers = [authorization_header] + additional_headers
as_connection_error err = Error.throw (Enso_Cloud_Error.Connection_Error err)

View File

@ -431,7 +431,7 @@ type Forbidden_Operation
## PRIVATE
Convert the Forbidden_Operation error to a human-readable format.
to_display_text : Text
to_display_text self = "Forbidden operation: "+self.message
to_display_text self = self.message
type Dry_Run_Operation
## PRIVATE

View File

@ -32,6 +32,7 @@ export project.Data.Numeric.Rounding_Mode.Rounding_Mode
export project.Data.Ordering.Comparable
export project.Data.Ordering.Natural_Order
export project.Data.Ordering.Ordering
export project.Data.Pair.pair
export project.Data.Pair.Pair
export project.Data.Range.Extensions.down_to
export project.Data.Range.Extensions.up_to

View File

@ -44,7 +44,7 @@ Text.to_uri self = URI.parse self
cannot be determined automatically, a raw HTTP `Response` will be returned.
@format Data_Read_Helpers.format_widget_with_raw_response
URI.fetch : HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any
URI.fetch self (method:HTTP_Method=HTTP_Method.Get) headers=[] format=Auto_Detect =
URI.fetch self (method:HTTP_Method=..Get) headers=[] format=Auto_Detect =
Data.fetch self method headers format
## ALIAS http post, upload
@ -85,7 +85,8 @@ URI.fetch self (method:HTTP_Method=HTTP_Method.Get) headers=[] format=Auto_Detec
- 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`.
@headers Header.default_widget
@response_format Data_Read_Helpers.format_widget_with_raw_response
URI.post : Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any
URI.post self (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =
URI.post self (body:Request_Body=..Empty) (method:HTTP_Method=..Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =
Data.post self body method headers response_format

View File

@ -11,6 +11,7 @@ import project.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Error.Error
import project.Errors.Common.Forbidden_Operation
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Function.Function
import project.Meta
import project.Network.HTTP.Header.Header
import project.Network.HTTP.HTTP_Error.HTTP_Error
@ -106,7 +107,7 @@ type HTTP
# Prevent request if the method is a write-like method and output context is disabled.
check_output_context ~action =
if fetch_methods.contains req.method || Context.Output.is_enabled then action else
Error.throw (Forbidden_Operation.Error ("Method " + req.method.to_text + " requests are forbidden as the Output context is disabled."))
Error.throw (Forbidden_Operation.Error ("As writing is disabled, " + req.method.to_text + " request not sent. Press the Write button ▶ to send it."))
handle_request_error =
handler caught_panic =
exception = caught_panic.payload
@ -137,7 +138,7 @@ type HTTP
## PRIVATE
Static helper for get-like methods
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Response ! Request_Error | HTTP_Error
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
fetch (uri:(URI | Text)) (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
@ -187,6 +188,7 @@ parse_headers headers =
headers . map on_problems=No_Wrap h-> case h of
_ : Vector -> Header.new (h.at 0) (h.at 1)
_ : Pair -> Header.new (h.at 0) (h.at 1)
_ : Function -> h:Header
_ : Header -> h
_ -> Error.throw (Illegal_Argument.Error "Invalid header type - all values must be Vector, Pair or Header (got "+(Meta.get_simple_type_name h)+").")

View File

@ -3,11 +3,18 @@ import project.Data.Text.Encoding.Encoding
import project.Data.Text.Text
import project.Enso_Cloud.Enso_Secret.Derived_Secret_Value
import project.Enso_Cloud.Enso_Secret.Enso_Secret
import project.Meta
import project.Metadata.Display
import project.Metadata.Widget
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
from project.Enso_Cloud.Enso_Secret import as_hideable_value
from project.Metadata import make_single_choice
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice, Vector_Editor
from project.Widget_Helpers import make_text_secret_selector
polyglot java import org.graalvm.collections.Pair as Java_Pair
@ -213,6 +220,22 @@ type Header
to_java_pair self =
Java_Pair.create self.name (as_hideable_value self.value)
## PRIVATE
default_widget (display : Display = ..When_Modified) -> Widget =
fqn = "Header"
secret_selector = make_text_secret_selector
custom = [Option "Custom" fqn+".new '' ''"]
accept = [Option "Accept" fqn+".accept '*/*'"]
authorization = [Option "Authorization" fqn+".authorization ''" [["value", secret_selector]]]
basic_auth = [Option "HTTP Basic Auth" fqn+".authorization_basic '' ''" [["user", secret_selector], ["pass", secret_selector]]]
bearer_auth = [Option "Bearer Auth" fqn+".authorization_bearer ''" [["token", secret_selector]]]
content_type = [Option "Content-Type" fqn+".content_type ''" [["value", content_type_widget], ["encoding", Encoding.default_widget]]]
values = custom + accept + authorization + basic_auth + bearer_auth + content_type
item_editor = Single_Choice values=values display=..Always
Vector_Editor item_editor item_default='Header.new "header" "value"' display=display
## PRIVATE
type Header_Comparator
## PRIVATE
@ -229,3 +252,7 @@ type Header_Comparator
## PRIVATE
Comparable.from (that:Header) = Comparable.new that Header_Comparator
## PRIVATE
content_type_widget -> Widget =
make_single_choice [["Custom", "*"], "application/json", "application/octet-stream", "application/x-www-form-urlencoded", "multipart/form-data", "text/csv", "text/html", "text/plain", "text/xml"]

View File

@ -34,7 +34,7 @@ type Request
example_new = Request.new Method.Post (URI.parse "http://example.com")
new : HTTP_Method -> URI -> Vector Header -> Request_Body -> Request
new (method:HTTP_Method) (url:URI) (headers:(Vector Header)=[]) (body:Request_Body=Request_Body.Empty) =
new (method:HTTP_Method) (url:URI) (headers:(Vector Header)=[]) (body:Request_Body=..Empty) =
Request.Value method url headers body
## ICON find

View File

@ -1,11 +1,21 @@
import project.Any.Any
import project.Data.Dictionary.Dictionary
import project.Data.Pair.Pair
import project.Data.Text.Encoding.Encoding
import project.Data.Text.Text
import project.Errors.Common.Missing_Argument
import project.Meta
import project.Metadata.Widget
import project.Network.HTTP.Header.Header
import project.Nothing.Nothing
import project.System.File.File
from project.Data.Boolean import Boolean, False, True
from project.Data.Json import key_value_widget
from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import File_Browse, Single_Choice, Text_Input, Vector_Editor
from project.Network.HTTP.Header import content_type_widget
from project.Widget_Helpers import make_all_selector
## The HTTP POST request body.
type Request_Body
@ -16,19 +26,22 @@ type Request_Body
- text: The plain text in the request body.
- encoding: the text encoding to send as a Content-Encoding header
- content_type: the content_type to send as a Content-Type header
Text (text:Text) (encoding:(Encoding|Nothing)=Nothing) (content_type:(Text|Nothing)=Nothing)
@encoding Encoding.default_widget
@content_type content_type_widget
Text (text:Text="") (encoding:(Encoding|Nothing)=Nothing) (content_type:(Text|Nothing)=Nothing)
## Request body with an object to be sent as JSON.
Arguments:
- x: The object to convert to JSON using `.to_json`.
Json (x:Any)
@x make_all_with_json
Json (x:Any=(Missing_Argument.throw "x"))
## Request body with an object to be sent as a binary file.
Arguments:
- file: The file to send.
Binary (file:File)
Binary (file:File=(Missing_Argument.throw "file"))
## Request body with form data.
@ -36,7 +49,8 @@ type Request_Body
- form_data: the form fields (text or file) to be sent
- url_encoded: if true, use a URL-encoded form; otherwise, use a
multi-part encoding.
Form_Data (form_data:(Dictionary Text (Text | File))) (url_encoded:Boolean=False)
@form_data dictionary_widget
Form_Data (form_data:Dictionary=(Missing_Argument.throw "form_data")) (url_encoded:Boolean=False)
## Empty request body; used for GET
Empty
@ -61,3 +75,18 @@ Request_Body.from (that:File) = Request_Body.Binary that
## PRIVATE
Request_Body.from (that:Any) = Request_Body.Json that
## PRIVATE
make_all_with_json =
base_selector = make_all_selector Request_Body
values = [Option "JSON Object" key_value_widget] + base_selector.values
Single_Choice display=..Always values=values
## PRIVATE
dictionary_widget -> Widget =
fqn = Meta.get_qualified_type_name Pair . drop (..Last 5)
default = 'pair "key" ""'
value_editor = Single_Choice display=..Always values=[Option "Text" '""', Option "File" "File.new ''"]
pair = Option "Pair" fqn+".pair" [["first", Text_Input], ["second", value_editor]]
item_editor = Single_Choice display=..Always values=[pair]
Vector_Editor item_editor=item_editor display=..Always item_default=default

View File

@ -195,7 +195,7 @@ type File
_ -> stream
Output_Stream.new wrapped (File_Error.handle_java_exceptions self)
Context.Output.if_enabled disabled_message="File writing is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to a file. Press the Write button ▶ to perform the operation." panic=False <|
open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && (Data_Link.is_data_link self)
if open_as_data_link then Data_Link_Helpers.write_data_link_as_stream self open_options action else
# We ignore the Data_Link_Access options at this stage:
@ -515,7 +515,7 @@ type File
(Examples.data_dir / "my_directory") . create_directory
create_directory : File ! File_Error
create_directory self =
Context.Output.if_enabled disabled_message="Directory creation is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot create directory. Press the Write button ▶ to perform the operation." panic=False <|
File_Error.handle_java_exceptions self self.create_directory_builtin . if_not_error self
## PRIVATE
@ -665,7 +665,7 @@ type File
file.delete
delete : Boolean -> Nothing ! File_Error
delete self (recursive : Boolean = False) -> Nothing ! File_Error =
Context.Output.if_enabled disabled_message="File deleting is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot delete file. Press the Write button ▶ to perform the operation." panic=False <|
File_Error.handle_java_exceptions self (self.delete_builtin recursive)
## ICON data_output
@ -677,7 +677,7 @@ type File
destination file already exists. Defaults to `False`.
copy_to : File_Like -> Boolean -> Any ! File_Error
copy_to self (destination : File_Like) (replace_existing : Boolean = False) = Data_Link_Helpers.disallow_links_in_copy self destination <|
Context.Output.if_enabled disabled_message="File copying is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot copy file. Press the Write button ▶ to perform the operation." panic=False <|
## We defer the `Writable_File` conversion after the 'disallow links' check,
because the conversion would already start resolving the data link too soon.
destination_writable = Writable_File.from destination
@ -695,7 +695,7 @@ type File
destination file already exists. Defaults to `False`.
move_to : File_Like -> Boolean -> Any ! File_Error
move_to self (destination : File_Like) (replace_existing : Boolean = False) = Data_Link_Helpers.disallow_links_in_move self destination <|
Context.Output.if_enabled disabled_message="File moving is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot move file. Press the Write button ▶ to perform the operation." panic=False <|
## We defer the `Writable_File` conversion after the 'disallow links' check,
because the conversion would already start resolving the data link too soon.
destination_writable = Writable_File.from destination

View File

@ -131,6 +131,12 @@ make_data_cleanse_vector_selector display:Display=Display.Always =
options = patterns.map f-> Option f (".." + f)
Widget.Multiple_Choice values=options display=display
## PRIVATE
Creates a Single_Choice Widget for Any selectors with all types enabled.
make_all_selector : Display -> Widget
make_all_selector display:Display=..Always -> Widget =
make_any_selector display add_text=True add_number=True add_boolean=True add_date=True add_time=True add_date_time=True add_nothing=True
## PRIVATE
Creates a Single_Choice Widget for Any selectors.
make_any_selector : Display -> Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Widget

View File

@ -360,7 +360,7 @@ type Connection
representing the query to execute.
execute_update : Text | SQL_Statement -> Integer
execute_update self query =
Execution_Context.Output.if_enabled disabled_message="Executing update queries is forbidden as the Output context is disabled." panic=False <|
Execution_Context.Output.if_enabled disabled_message="As writing is disabled, cannot execute an update query. Press the Write button ▶ to perform the operation." panic=False <|
statement_setter = self.dialect.get_statement_setter
self.jdbc_connection.with_prepared_statement query statement_setter stmt->
check_statement_is_allowed self stmt
@ -384,7 +384,7 @@ type Connection
representing the query to execute.
execute : Text | SQL_Statement -> Integer
execute self query =
Execution_Context.Output.if_enabled disabled_message="Executing update queries is forbidden as the Output context is disabled." panic=False <|
Execution_Context.Output.if_enabled disabled_message="As writing is disabled, cannot execute an update query. Press the Write button ▶ to perform the operation." panic=False <|
result = self.jdbc_connection.execute query
stmt = result.second
check_statement_is_allowed self stmt

View File

@ -21,7 +21,7 @@ type Postgres_Data_Link_Setup
## PRIVATE
save_as_data_link self destination on_existing_file:Existing_File_Behavior = case self of
Postgres_Data_Link_Setup.Available details -> Context.Output.if_enabled disabled_message="Saving a connection to data link requires the Output context." panic=False <|
Postgres_Data_Link_Setup.Available details -> Context.Output.if_enabled disabled_message="As writing is disabled, cannot save to a Data Link. Press the Write button ▶ to perform the operation." panic=False <|
case destination of
_ : Enso_File ->
replace_existing = case on_existing_file of

View File

@ -101,7 +101,7 @@ type Image
@path (Widget.Text_Input display=Display.Always)
write : Writable_File -> (Write_Flag | Vector) -> Nothing ! File_Error
write self path:Writable_File flags=[] =
Context.Output.if_enabled disabled_message="Writing the image to a file is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to a file. Press the Write button ▶ to perform the operation." panic=False <|
write_flags = case flags of
_ : Vector -> flags
_ -> [flags]

View File

@ -188,7 +188,7 @@ class RuntimeExecutionEnvironmentTest
IF_ENABLED_METH_CALL,
Api.ExpressionUpdate.Payload
.Panic(
"Forbidden operation: The Output context is disabled.",
"The Output context is disabled.",
Seq(idRes)
),
false
@ -292,7 +292,7 @@ class RuntimeExecutionEnvironmentTest
IF_ENABLED_METH_CALL,
Api.ExpressionUpdate.Payload
.Panic(
"Forbidden operation: The Input context is disabled.",
"The Input context is disabled.",
Seq(idRes)
),
false

View File

@ -20,12 +20,11 @@ import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Dictionary",
name = "insert",
name = "insert_builtin",
description =
"""
Returns newly created hash map with the given key value mapping.
""",
autoRegister = false)
""")
@GenerateUncached
public abstract class HashMapInsertNode extends Node {

View File

@ -84,7 +84,7 @@ type Arity_Error
type Forbidden_Operation
Error message
to_display_text self = "Forbidden operation: "+self.message
to_display_text self = self.message
## PRIVATE
A helper that replaces complicated `Text.replace` from main distribution, used for simple error message formatting.