mirror of
https://github.com/enso-org/enso.git
synced 2024-12-26 11:14:27 +03:00
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:
parent
ecfe959c29
commit
28bbc34257
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 <|
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)+").")
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user