feat(plugins): web requests api (#2879)

* feat(plugins): web requests api

* fix e2e tests

* fix e2e tests again
This commit is contained in:
Aram Drevekenin 2023-10-20 14:20:00 +02:00 committed by GitHub
parent 41e953f177
commit b59b29a534
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1124 additions and 108 deletions

694
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,7 @@ impl ZellijPlugin for State {
PermissionType::RunCommands,
PermissionType::OpenTerminalsOrPlugins,
PermissionType::WriteToStdin,
PermissionType::WebAccess,
]);
self.configuration = configuration;
subscribe(&[
@ -257,6 +258,22 @@ impl ZellijPlugin for State {
context,
);
},
Key::Ctrl('4') => {
let mut headers = BTreeMap::new();
let mut context = BTreeMap::new();
let body = vec![1, 2, 3];
headers.insert("header1".to_owned(), "value1".to_owned());
headers.insert("header2".to_owned(), "value2".to_owned());
context.insert("user_key_1".to_owned(), "user_value1".to_owned());
context.insert("user_key_2".to_owned(), "user_value2".to_owned());
web_request(
"https://example.com/foo?arg1=val1&arg2=val2",
HttpVerb::Post,
headers,
body,
context,
);
},
_ => {},
},
Event::CustomMessage(message, payload) => {

View file

@ -3,8 +3,13 @@ use zellij_utils::consts::{
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
ZELLIJ_SOCK_DIR,
};
use zellij_utils::data::{Event, SessionInfo};
use zellij_utils::data::{Event, HttpVerb, SessionInfo};
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
use zellij_utils::surf::{
self,
http::{Method, Url},
RequestBuilder,
};
use std::collections::{BTreeMap, HashMap};
use std::fs;
@ -40,6 +45,15 @@ pub enum BackgroundJob {
PathBuf,
BTreeMap<String, String>,
), // command, args, env_variables, cwd, context
WebRequest(
PluginId,
ClientId,
String, // url
HttpVerb,
BTreeMap<String, String>, // headers
Vec<u8>, // body
BTreeMap<String, String>, // context
),
Exit,
}
@ -57,6 +71,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
@ -285,6 +300,69 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
}
});
},
BackgroundJob::WebRequest(plugin_id, client_id, url, verb, headers, body, context) => {
task::spawn({
let senders = bus.senders.clone();
async move {
async fn web_request(
url: String,
verb: HttpVerb,
headers: BTreeMap<String, String>,
body: Vec<u8>,
) -> Result<
(u16, BTreeMap<String, String>, Vec<u8>), // status_code, headers, body
zellij_utils::surf::Error,
> {
let url = Url::parse(&url)?;
let http_method = match verb {
HttpVerb::Get => Method::Get,
HttpVerb::Post => Method::Post,
HttpVerb::Put => Method::Put,
HttpVerb::Delete => Method::Delete,
};
let mut req = RequestBuilder::new(http_method, url);
if !body.is_empty() {
req = req.body(body);
}
for (header, value) in headers {
req = req.header(header.as_str(), value);
}
let mut res = req.await?;
let status_code = res.status();
let headers: BTreeMap<String, String> = res
.iter()
.map(|(name, value)| (name.to_string(), value.to_string()))
.collect();
let body = res.take_body().into_bytes().await?;
Ok((status_code as u16, headers, body))
}
match web_request(url, verb, headers, body).await {
Ok((status, headers, body)) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(plugin_id),
Some(client_id),
Event::WebRequestResult(status, headers, body, context),
)]));
},
Err(e) => {
log::error!("Failed to send web request: {}", e);
let error_body = e.to_string().as_bytes().to_vec();
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(plugin_id),
Some(client_id),
Event::WebRequestResult(
400,
BTreeMap::new(),
error_body,
context,
),
)]));
},
}
}
});
},
BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);

View file

@ -546,6 +546,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
let _ = to_screen.send(ScreenInstruction::Exit);
let _ = to_server.send(ServerInstruction::KillSession);
let _ = to_plugin.send(PluginInstruction::Exit);
let _ = to_background_jobs.send(BackgroundJob::Exit);
let _ = plugin_thread.join();
}
};
@ -5418,3 +5419,78 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}
#[test]
#[ignore]
pub fn web_request_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, background_jobs_receiver, screen_receiver, teardown) =
create_plugin_thread_with_background_jobs_receiver(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
};
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_background_jobs_instructions = Arc::new(Mutex::new(vec![]));
let background_jobs_thread = log_actions_in_thread!(
received_background_jobs_instructions,
BackgroundJob::WebRequest,
background_jobs_receiver,
1
);
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
received_screen_instructions,
ScreenInstruction::Exit,
screen_receiver,
1,
&PermissionType::WebAccess,
cache_path,
plugin_thread_sender,
client_id
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
tab_index,
None,
client_id,
size,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(Key::Ctrl('4')), // this triggers the enent in the fixture plugin
)]));
background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let new_tab_event = received_background_jobs_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let BackgroundJob::WebRequest(..) = i {
Some(i.clone())
} else {
None
}
})
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}

View file

@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4864
assertion_line: 5189
expression: "format!(\"{:#?}\", permissions)"
---
Some(
@ -11,5 +11,6 @@ Some(
RunCommands,
OpenTerminalsOrPlugins,
WriteToStdin,
WebAccess,
],
)

View file

@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4767
assertion_line: 5101
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
@ -13,5 +13,6 @@ Some(
RunCommands,
OpenTerminalsOrPlugins,
WriteToStdin,
WebAccess,
],
)

View file

@ -0,0 +1,26 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 5494
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
WebRequest(
0,
1,
"https://example.com/foo?arg1=val1&arg2=val2",
Post,
{
"header1": "value1",
"header2": "value2",
},
[
1,
2,
3,
],
{
"user_key_1": "user_value1",
"user_key_2": "user_value2",
},
),
)

View file

@ -18,7 +18,7 @@ use std::{
use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports};
use wasmer_wasi::WasiEnv;
use zellij_utils::data::{
CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission,
CommandType, ConnectToSession, HttpVerb, PermissionStatus, PermissionType, PluginPermission,
};
use zellij_utils::input::permission::PermissionCache;
@ -130,6 +130,9 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
run_command(env, command_line, env_variables, cwd, context)
},
PluginCommand::WebRequest(url, verb, headers, body, context) => {
web_request(env, url, verb, headers, body, context)
},
PluginCommand::PostMessageTo(plugin_message) => {
post_message_to(env, plugin_message)?
},
@ -607,12 +610,6 @@ fn run_command(
cwd: PathBuf,
context: BTreeMap<String, String>,
) {
let err_context = || {
format!(
"failed to execute command on host for plugin '{}'",
env.plugin_env.name()
)
};
if command_line.is_empty() {
log::error!("Command cannot be empty");
} else {
@ -632,6 +629,28 @@ fn run_command(
}
}
fn web_request(
env: &ForeignFunctionEnv,
url: String,
verb: HttpVerb,
headers: BTreeMap<String, String>,
body: Vec<u8>,
context: BTreeMap<String, String>,
) {
let _ = env
.plugin_env
.senders
.send_to_background_jobs(BackgroundJob::WebRequest(
env.plugin_env.plugin_id,
env.plugin_env.client_id,
url,
verb,
headers,
body,
context,
));
}
fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
let worker_name = plugin_message
.worker_name
@ -1198,6 +1217,7 @@ fn check_command_permission(
| PluginCommand::OpenCommandPaneInPlace(..)
| PluginCommand::RunCommand(..)
| PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
PluginCommand::WebRequest(..) => PermissionType::WebAccess,
PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin,
PluginCommand::SwitchTabTo(..)
| PluginCommand::SwitchToMode(..)

View file

@ -207,6 +207,21 @@ pub fn run_command_with_env_variables_and_cwd(
unsafe { host_run_plugin_command() };
}
pub fn web_request<S: AsRef<str>>(
url: S,
verb: HttpVerb,
headers: BTreeMap<String, String>,
body: Vec<u8>,
context: BTreeMap<String, String>,
) where
S: ToString,
{
let plugin_command = PluginCommand::WebRequest(url.to_string(), verb, headers, body, context);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
/// Hide the plugin pane (suppress it) from the UI
pub fn hide_self() {
let plugin_command = PluginCommand::HideSelf;

View file

@ -54,6 +54,7 @@ interprocess = "1.2.1"
async-std = { version = "1.3.0", features = ["unstable"] }
notify-debouncer-full = "0.1.0"
humantime = "2.1.0"
surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls"] }
[dev-dependencies]
insta = { version = "1.6.0", features = ["backtrace"] }

View file

@ -11,7 +11,7 @@ pub struct Event {
pub name: i32,
#[prost(
oneof = "event::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14"
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"
)]
pub payload: ::core::option::Option<event::Payload>,
}
@ -46,6 +46,8 @@ pub mod event {
SessionUpdatePayload(super::SessionUpdatePayload),
#[prost(message, tag = "14")]
RunCommandResultPayload(super::RunCommandResultPayload),
#[prost(message, tag = "15")]
WebRequestResultPayload(super::WebRequestResultPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@ -68,6 +70,18 @@ pub struct RunCommandResultPayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WebRequestResultPayload {
#[prost(int32, tag = "1")]
pub status: i32,
#[prost(message, repeated, tag = "2")]
pub headers: ::prost::alloc::vec::Vec<Header>,
#[prost(bytes = "vec", tag = "3")]
pub body: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "4")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContextItem {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
@ -76,6 +90,14 @@ pub struct ContextItem {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Header {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PermissionRequestResultPayload {
#[prost(bool, tag = "1")]
pub granted: bool,
@ -287,6 +309,7 @@ pub enum EventType {
PermissionRequestResult = 15,
SessionUpdate = 16,
RunCommandResult = 17,
WebRequestResult = 18,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@ -313,6 +336,7 @@ impl EventType {
EventType::PermissionRequestResult => "PermissionRequestResult",
EventType::SessionUpdate => "SessionUpdate",
EventType::RunCommandResult => "RunCommandResult",
EventType::WebRequestResult => "WebRequestResult",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -336,6 +360,7 @@ impl EventType {
"PermissionRequestResult" => Some(Self::PermissionRequestResult),
"SessionUpdate" => Some(Self::SessionUpdate),
"RunCommandResult" => Some(Self::RunCommandResult),
"WebRequestResult" => Some(Self::WebRequestResult),
_ => None,
}
}

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32,
#[prost(
oneof = "plugin_command::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43"
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44"
)]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
@ -98,6 +98,8 @@ pub mod plugin_command {
OpenCommandPaneInPlacePayload(super::OpenCommandPanePayload),
#[prost(message, tag = "43")]
RunCommandPayload(super::RunCommandPayload),
#[prost(message, tag = "44")]
WebRequestPayload(super::WebRequestPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@ -178,6 +180,20 @@ pub struct RunCommandPayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WebRequestPayload {
#[prost(string, tag = "1")]
pub url: ::prost::alloc::string::String,
#[prost(enumeration = "HttpVerb", tag = "2")]
pub verb: i32,
#[prost(message, repeated, tag = "3")]
pub headers: ::prost::alloc::vec::Vec<super::event::Header>,
#[prost(bytes = "vec", tag = "4")]
pub body: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "5")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EnvVariable {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
@ -294,6 +310,7 @@ pub enum CommandName {
OpenCommandInPlace = 69,
OpenFileInPlace = 70,
RunCommand = 71,
WebRequest = 72,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -374,6 +391,7 @@ impl CommandName {
CommandName::OpenCommandInPlace => "OpenCommandInPlace",
CommandName::OpenFileInPlace => "OpenFileInPlace",
CommandName::RunCommand => "RunCommand",
CommandName::WebRequest => "WebRequest",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -451,6 +469,39 @@ impl CommandName {
"OpenCommandInPlace" => Some(Self::OpenCommandInPlace),
"OpenFileInPlace" => Some(Self::OpenFileInPlace),
"RunCommand" => Some(Self::RunCommand),
"WebRequest" => Some(Self::WebRequest),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum HttpVerb {
Get = 0,
Post = 1,
Put = 2,
Delete = 3,
}
impl HttpVerb {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
HttpVerb::Get => "Get",
HttpVerb::Post => "Post",
HttpVerb::Put => "Put",
HttpVerb::Delete => "Delete",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"Get" => Some(Self::Get),
"Post" => Some(Self::Post),
"Put" => Some(Self::Put),
"Delete" => Some(Self::Delete),
_ => None,
}
}

View file

@ -7,6 +7,7 @@ pub enum PermissionType {
RunCommands = 3,
OpenTerminalsOrPlugins = 4,
WriteToStdin = 5,
WebAccess = 6,
}
impl PermissionType {
/// String value of the enum field names used in the ProtoBuf definition.
@ -21,6 +22,7 @@ impl PermissionType {
PermissionType::RunCommands => "RunCommands",
PermissionType::OpenTerminalsOrPlugins => "OpenTerminalsOrPlugins",
PermissionType::WriteToStdin => "WriteToStdin",
PermissionType::WebAccess => "WebAccess",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -32,6 +34,7 @@ impl PermissionType {
"RunCommands" => Some(Self::RunCommands),
"OpenTerminalsOrPlugins" => Some(Self::OpenTerminalsOrPlugins),
"WriteToStdin" => Some(Self::WriteToStdin),
"WebAccess" => Some(Self::WebAccess),
_ => None,
}
}

View file

@ -498,6 +498,15 @@ pub enum Event {
SessionUpdate(Vec<SessionInfo>),
RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
// context
WebRequestResult(
u16,
BTreeMap<String, String>,
Vec<u8>,
BTreeMap<String, String>,
), // status,
// headers,
// body,
// context
}
#[derive(
@ -524,6 +533,7 @@ pub enum Permission {
RunCommands,
OpenTerminalsOrPlugins,
WriteToStdin,
WebAccess,
}
impl PermissionType {
@ -539,6 +549,7 @@ impl PermissionType {
PermissionType::RunCommands => "Run commands".to_owned(),
PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(),
PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(),
PermissionType::WebAccess => "Make web requests".to_owned(),
}
}
}
@ -991,6 +1002,14 @@ impl PluginMessage {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HttpVerb {
Get,
Post,
Put,
Delete,
}
#[derive(Debug, Clone, EnumDiscriminants, ToString)]
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
#[strum_discriminants(name(CommandType))]
@ -1067,12 +1086,16 @@ pub enum PluginCommand {
OpenFileInPlace(FileToOpen),
OpenCommandPaneInPlace(CommandToRun),
RunCommand(
Vec<String>,
BTreeMap<String, String>,
PathBuf,
BTreeMap<String, String>,
), // command,
// env_Variables,
// cwd,
// context
Vec<String>, // command
BTreeMap<String, String>, // env_variables
PathBuf, // cwd
BTreeMap<String, String>, // context
),
WebRequest(
String, // url
HttpVerb,
BTreeMap<String, String>, // headers
Vec<u8>, // body
BTreeMap<String, String>, // context
),
}

View file

@ -447,6 +447,7 @@ pub enum BackgroundJobContext {
ReportSessionInfo,
ReportLayoutInfo,
RunCommand,
WebRequest,
Exit,
}

View file

@ -24,7 +24,8 @@ pub mod logging; // Requires log4rs
#[cfg(not(target_family = "wasm"))]
pub use ::{
anyhow, async_channel, async_std, clap, common_path, humantime, interprocess, lazy_static,
libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte,
libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, surf, tempfile, termwiz,
vte,
};
pub use ::prost;

View file

@ -41,6 +41,7 @@ enum EventType {
PermissionRequestResult = 15;
SessionUpdate = 16;
RunCommandResult = 17;
WebRequestResult = 18;
}
message EventNameList {
@ -63,6 +64,7 @@ message Event {
PermissionRequestResultPayload permission_request_result_payload = 12;
SessionUpdatePayload session_update_payload = 13;
RunCommandResultPayload run_command_result_payload = 14;
WebRequestResultPayload web_request_result_payload = 15;
}
}
@ -77,11 +79,23 @@ message RunCommandResultPayload {
repeated ContextItem context = 4;
}
message WebRequestResultPayload {
int32 status = 1;
repeated Header headers = 2;
bytes body = 3;
repeated ContextItem context = 4;
}
message ContextItem {
string name = 1;
string value = 2;
}
message Header {
string name = 1;
string value = 2;
}
message PermissionRequestResultPayload {
bool granted = 1;
}

View file

@ -198,6 +198,25 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for the RunCommandResult Event"),
},
Some(ProtobufEventType::WebRequestResult) => match protobuf_event.payload {
Some(ProtobufEventPayload::WebRequestResultPayload(web_request_result_payload)) => {
Ok(Event::WebRequestResult(
web_request_result_payload.status as u16,
web_request_result_payload
.headers
.into_iter()
.map(|h| (h.name, h.value))
.collect(),
web_request_result_payload.body,
web_request_result_payload
.context
.into_iter()
.map(|c_i| (c_i.name, c_i.value))
.collect(),
))
},
_ => Err("Malformed payload for the WebRequestResult Event"),
},
None => Err("Unknown Protobuf Event"),
}
}
@ -370,6 +389,26 @@ impl TryFrom<Event> for ProtobufEvent {
)),
})
},
Event::WebRequestResult(status, headers, body, context) => {
let web_request_result_payload = WebRequestResultPayload {
status: status as i32,
headers: headers
.into_iter()
.map(|(name, value)| Header { name, value })
.collect(),
body,
context: context
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect(),
};
Ok(ProtobufEvent {
name: ProtobufEventType::WebRequestResult as i32,
payload: Some(event::Payload::WebRequestResultPayload(
web_request_result_payload,
)),
})
},
}
}
}
@ -816,6 +855,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
ProtobufEventType::SessionUpdate => EventType::SessionUpdate,
ProtobufEventType::RunCommandResult => EventType::RunCommandResult,
ProtobufEventType::WebRequestResult => EventType::WebRequestResult,
})
}
}
@ -842,6 +882,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
EventType::SessionUpdate => ProtobufEventType::SessionUpdate,
EventType::RunCommandResult => ProtobufEventType::RunCommandResult,
EventType::WebRequestResult => ProtobufEventType::WebRequestResult,
})
}
}

View file

@ -83,6 +83,7 @@ enum CommandName {
OpenCommandInPlace = 69;
OpenFileInPlace = 70;
RunCommand = 71;
WebRequest = 72;
}
message PluginCommand {
@ -130,6 +131,7 @@ message PluginCommand {
OpenFilePayload open_terminal_in_place_payload = 41;
OpenCommandPanePayload open_command_pane_in_place_payload = 42;
RunCommandPayload run_command_payload = 43;
WebRequestPayload web_request_payload = 44;
}
}
@ -179,6 +181,21 @@ message RunCommandPayload {
repeated ContextItem context = 4;
}
message WebRequestPayload {
string url = 1;
HttpVerb verb = 2;
repeated event.Header headers = 3;
bytes body = 4;
repeated ContextItem context = 5;
}
enum HttpVerb {
Get = 0;
Post = 1;
Put = 2;
Delete = 3;
}
message EnvVariable {
string name = 1;
string value = 2;

View file

@ -1,24 +1,47 @@
pub use super::generated_api::api::{
action::{PaneIdAndShouldFloat, SwitchToModePayload},
event::EventNameList as ProtobufEventNameList,
event::{EventNameList as ProtobufEventNameList, Header},
input_mode::InputMode as ProtobufInputMode,
plugin_command::{
plugin_command::Payload, CommandName, ContextItem, EnvVariable, ExecCmdPayload,
IdAndNewName, MovePayload, OpenCommandPanePayload, OpenFilePayload,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, MovePayload, OpenCommandPanePayload,
OpenFilePayload, PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload,
SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload,
WebRequestPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
};
use crate::data::{ConnectToSession, PermissionType, PluginCommand};
use crate::data::{ConnectToSession, HttpVerb, PermissionType, PluginCommand};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::path::PathBuf;
impl Into<HttpVerb> for ProtobufHttpVerb {
fn into(self) -> HttpVerb {
match self {
ProtobufHttpVerb::Get => HttpVerb::Get,
ProtobufHttpVerb::Post => HttpVerb::Post,
ProtobufHttpVerb::Put => HttpVerb::Put,
ProtobufHttpVerb::Delete => HttpVerb::Delete,
}
}
}
impl Into<ProtobufHttpVerb> for HttpVerb {
fn into(self) -> ProtobufHttpVerb {
match self {
HttpVerb::Get => ProtobufHttpVerb::Get,
HttpVerb::Post => ProtobufHttpVerb::Post,
HttpVerb::Put => ProtobufHttpVerb::Put,
HttpVerb::Delete => ProtobufHttpVerb::Delete,
}
}
}
impl TryFrom<ProtobufPluginCommand> for PluginCommand {
type Error = &'static str;
fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> {
@ -577,6 +600,34 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for RunCommand"),
},
Some(CommandName::WebRequest) => match protobuf_plugin_command.payload {
Some(Payload::WebRequestPayload(web_request_payload)) => {
let context: BTreeMap<String, String> = web_request_payload
.context
.into_iter()
.map(|e| (e.name, e.value))
.collect();
let headers: BTreeMap<String, String> = web_request_payload
.headers
.into_iter()
.map(|e| (e.name, e.value))
.collect();
let verb = match ProtobufHttpVerb::from_i32(web_request_payload.verb) {
Some(verb) => verb.into(),
None => {
return Err("Unrecognized http verb");
},
};
Ok(PluginCommand::WebRequest(
web_request_payload.url,
verb,
headers,
web_request_payload.body,
context,
))
},
_ => Err("Mismatched payload for WebRequest"),
},
None => Err("Unrecognized plugin command"),
}
}
@ -972,6 +1023,27 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
})),
})
},
PluginCommand::WebRequest(url, verb, headers, body, context) => {
let context: Vec<_> = context
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect();
let headers: Vec<_> = headers
.into_iter()
.map(|(name, value)| Header { name, value })
.collect();
let verb: ProtobufHttpVerb = verb.into();
Ok(ProtobufPluginCommand {
name: CommandName::WebRequest as i32,
payload: Some(Payload::WebRequestPayload(WebRequestPayload {
url,
verb: verb as i32,
body,
headers,
context,
})),
})
},
}
}
}

View file

@ -9,4 +9,5 @@ enum PermissionType {
RunCommands = 3;
OpenTerminalsOrPlugins = 4;
WriteToStdin = 5;
WebAccess = 6;
}

View file

@ -19,6 +19,7 @@ impl TryFrom<ProtobufPermissionType> for PermissionType {
Ok(PermissionType::OpenTerminalsOrPlugins)
},
ProtobufPermissionType::WriteToStdin => Ok(PermissionType::WriteToStdin),
ProtobufPermissionType::WebAccess => Ok(PermissionType::WebAccess),
}
}
}
@ -39,6 +40,7 @@ impl TryFrom<PermissionType> for ProtobufPermissionType {
Ok(ProtobufPermissionType::OpenTerminalsOrPlugins)
},
PermissionType::WriteToStdin => Ok(ProtobufPermissionType::WriteToStdin),
PermissionType::WebAccess => Ok(ProtobufPermissionType::WebAccess),
}
}
}