mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-27 11:56:05 +02:00
fix(shell): fix schema requiring sidecar property even though it is optional (#1839)
* fix(shell): fix schema requiring `sidecar` property even though it is optional * fix clippy * make `cmd` and `sidecar` exclusive * make args optional * cleanup --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"shell": "patch"
|
||||
---
|
||||
|
||||
Fix the plugin schema requiring to set `sidecar` property when it is in fact optional.
|
||||
|
||||
+10
-8
@@ -24,14 +24,16 @@ enum FsScopeEntry {
|
||||
},
|
||||
}
|
||||
|
||||
// Ensure scope entry is kept up to date
|
||||
impl From<FsScopeEntry> for scope::EntryRaw {
|
||||
fn from(value: FsScopeEntry) -> Self {
|
||||
match value {
|
||||
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path),
|
||||
FsScopeEntry::Object { path } => scope::EntryRaw::Object { path },
|
||||
}
|
||||
}
|
||||
// Ensure `FsScopeEntry` and `scope::EntryRaw` is kept in sync
|
||||
fn _f() {
|
||||
match scope::EntryRaw::Value(PathBuf::new()) {
|
||||
scope::EntryRaw::Value(path) => FsScopeEntry::Value(path),
|
||||
scope::EntryRaw::Object { path } => FsScopeEntry::Object { path },
|
||||
};
|
||||
match FsScopeEntry::Value(PathBuf::new()) {
|
||||
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path),
|
||||
FsScopeEntry::Object { path } => scope::EntryRaw::Object { path },
|
||||
};
|
||||
}
|
||||
|
||||
const BASE_DIR_VARS: &[&str] = &[
|
||||
|
||||
@@ -13,10 +13,9 @@ use std::{
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum EntryRaw {
|
||||
pub(crate) enum EntryRaw {
|
||||
Value(PathBuf),
|
||||
Object { path: PathBuf },
|
||||
}
|
||||
|
||||
+10
-17
@@ -47,23 +47,16 @@ enum HttpScopeEntry {
|
||||
},
|
||||
}
|
||||
|
||||
// Ensure scope entry is kept up to date
|
||||
impl From<HttpScopeEntry> for scope::Entry {
|
||||
fn from(value: HttpScopeEntry) -> Self {
|
||||
let url = match value {
|
||||
HttpScopeEntry::Value(url) => url,
|
||||
HttpScopeEntry::Object { url } => url,
|
||||
};
|
||||
|
||||
scope::Entry {
|
||||
url: urlpattern::UrlPattern::parse(
|
||||
urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(&url, None)
|
||||
.unwrap(),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
// Ensure `HttpScopeEntry` and `scope::EntryRaw` is kept in sync
|
||||
fn _f() {
|
||||
match scope::EntryRaw::Value(String::new()) {
|
||||
scope::EntryRaw::Value(url) => HttpScopeEntry::Value(url),
|
||||
scope::EntryRaw::Object { url } => HttpScopeEntry::Object { url },
|
||||
};
|
||||
match HttpScopeEntry::Value(String::new()) {
|
||||
HttpScopeEntry::Value(url) => scope::EntryRaw::Value(url),
|
||||
HttpScopeEntry::Object { url } => scope::EntryRaw::Object { url },
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -33,18 +33,18 @@ fn parse_url_pattern(s: &str) -> Result<UrlPattern, urlpattern::quirks::Error> {
|
||||
UrlPattern::parse(init, Default::default())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum EntryRaw {
|
||||
Value(String),
|
||||
Object { url: String },
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Entry {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EntryRaw {
|
||||
Value(String),
|
||||
Object { url: String },
|
||||
}
|
||||
|
||||
EntryRaw::deserialize(deserializer).and_then(|raw| {
|
||||
let url = match raw {
|
||||
EntryRaw::Value(url) => url,
|
||||
|
||||
@@ -27,7 +27,6 @@ serde = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
|
||||
+159
-1
@@ -2,15 +2,173 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
|
||||
#[path = "src/scope_entry.rs"]
|
||||
mod scope_entry;
|
||||
|
||||
/// A command argument allowed to be executed by the webview API.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, schemars::JsonSchema)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShellScopeEntryAllowedArg {
|
||||
/// A non-configurable argument that is passed to the command in the order it was specified.
|
||||
Fixed(String),
|
||||
|
||||
/// A variable that is set while calling the command from the webview API.
|
||||
///
|
||||
Var {
|
||||
/// [regex] validator to require passed values to conform to an expected input.
|
||||
///
|
||||
/// This will require the argument value passed to this variable to match the `validator` regex
|
||||
/// before it will be executed.
|
||||
///
|
||||
/// The regex string is by default surrounded by `^...$` to match the full string.
|
||||
/// For example the `https?://\w+` regex would be registered as `^https?://\w+$`.
|
||||
///
|
||||
/// [regex]: <https://docs.rs/regex/latest/regex/#syntax>
|
||||
validator: String,
|
||||
|
||||
/// Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.
|
||||
///
|
||||
/// This means the regex will not match on the entire string by default, which might
|
||||
/// be exploited if your regex allow unexpected input to be considered valid.
|
||||
/// When using this option, make sure your regex is correct.
|
||||
#[serde(default)]
|
||||
raw: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// A set of command arguments allowed to be executed by the webview API.
|
||||
///
|
||||
/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all
|
||||
/// arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to
|
||||
/// be passed to the attached command configuration.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, JsonSchema)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShellScopeEntryAllowedArgs {
|
||||
/// Use a simple boolean to allow all or disable all arguments to this command configuration.
|
||||
Flag(bool),
|
||||
|
||||
/// A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.
|
||||
List(Vec<ShellScopeEntryAllowedArg>),
|
||||
}
|
||||
|
||||
impl Default for ShellScopeEntryAllowedArgs {
|
||||
fn default() -> Self {
|
||||
Self::Flag(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shell scope entry.
|
||||
#[derive(JsonSchema)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
#[allow(unused)]
|
||||
pub(crate) enum ShellScopeEntry {
|
||||
Command {
|
||||
/// The name for this allowed shell command configuration.
|
||||
///
|
||||
/// This name will be used inside of the webview API to call this command along with
|
||||
/// any specified arguments.
|
||||
name: String,
|
||||
/// The command name.
|
||||
/// It can start with a variable that resolves to a system base directory.
|
||||
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
|
||||
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
|
||||
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
|
||||
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
|
||||
// use default just so the schema doesn't flag it as required
|
||||
#[serde(rename = "cmd")]
|
||||
command: PathBuf,
|
||||
/// The allowed arguments for the command execution.
|
||||
#[serde(default)]
|
||||
args: ShellScopeEntryAllowedArgs,
|
||||
},
|
||||
Sidecar {
|
||||
/// The name for this allowed shell command configuration.
|
||||
///
|
||||
/// This name will be used inside of the webview API to call this command along with
|
||||
/// any specified arguments.
|
||||
name: String,
|
||||
/// The allowed arguments for the command execution.
|
||||
#[serde(default)]
|
||||
args: ShellScopeEntryAllowedArgs,
|
||||
/// If this command is a sidecar command.
|
||||
sidecar: bool,
|
||||
},
|
||||
}
|
||||
|
||||
// Ensure `ShellScopeEntry` and `scope_entry::EntryRaw`
|
||||
// and `ShellScopeEntryAllowedArg` and `ShellAllowedArg`
|
||||
// and `ShellScopeEntryAllowedArgs` and `ShellAllowedArgs`
|
||||
// are kept in sync
|
||||
#[allow(clippy::unnecessary_operation)]
|
||||
fn _f() {
|
||||
match (ShellScopeEntry::Sidecar {
|
||||
name: String::new(),
|
||||
args: ShellScopeEntryAllowedArgs::Flag(false),
|
||||
sidecar: true,
|
||||
}) {
|
||||
ShellScopeEntry::Command {
|
||||
name,
|
||||
command,
|
||||
args,
|
||||
} => scope_entry::EntryRaw {
|
||||
name,
|
||||
command: Some(command),
|
||||
args: match args {
|
||||
ShellScopeEntryAllowedArgs::Flag(flag) => scope_entry::ShellAllowedArgs::Flag(flag),
|
||||
ShellScopeEntryAllowedArgs::List(vec) => scope_entry::ShellAllowedArgs::List(
|
||||
vec.into_iter()
|
||||
.map(|s| match s {
|
||||
ShellScopeEntryAllowedArg::Fixed(fixed) => {
|
||||
scope_entry::ShellAllowedArg::Fixed(fixed)
|
||||
}
|
||||
ShellScopeEntryAllowedArg::Var { validator, raw } => {
|
||||
scope_entry::ShellAllowedArg::Var { validator, raw }
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
},
|
||||
sidecar: false,
|
||||
},
|
||||
ShellScopeEntry::Sidecar {
|
||||
name,
|
||||
args,
|
||||
sidecar,
|
||||
} => scope_entry::EntryRaw {
|
||||
name,
|
||||
command: None,
|
||||
args: match args {
|
||||
ShellScopeEntryAllowedArgs::Flag(flag) => scope_entry::ShellAllowedArgs::Flag(flag),
|
||||
ShellScopeEntryAllowedArgs::List(vec) => scope_entry::ShellAllowedArgs::List(
|
||||
vec.into_iter()
|
||||
.map(|s| match s {
|
||||
ShellScopeEntryAllowedArg::Fixed(fixed) => {
|
||||
scope_entry::ShellAllowedArg::Fixed(fixed)
|
||||
}
|
||||
ShellScopeEntryAllowedArg::Var { validator, raw } => {
|
||||
scope_entry::ShellAllowedArg::Var { validator, raw }
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
},
|
||||
sidecar,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const COMMANDS: &[&str] = &["execute", "spawn", "stdin_write", "kill", "open"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS)
|
||||
.global_api_script_path("./api-iife.js")
|
||||
.global_scope_schema(schemars::schema_for!(scope_entry::Entry))
|
||||
.global_scope_schema(schemars::schema_for!(ShellScopeEntry))
|
||||
.android_path("android")
|
||||
.ios_path("ios")
|
||||
.build();
|
||||
|
||||
@@ -7,29 +7,23 @@ use serde::{de::Error as DeError, Deserialize, Deserializer};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A command allowed to be executed by the webview API.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, schemars::JsonSchema)]
|
||||
pub struct Entry {
|
||||
/// The name for this allowed shell command configuration.
|
||||
///
|
||||
/// This name will be used inside of the webview API to call this command along with
|
||||
/// any specified arguments.
|
||||
pub name: String,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Entry {
|
||||
pub(crate) name: String,
|
||||
pub(crate) command: PathBuf,
|
||||
pub(crate) args: ShellAllowedArgs,
|
||||
pub(crate) sidecar: bool,
|
||||
}
|
||||
|
||||
/// The command name.
|
||||
/// It can start with a variable that resolves to a system base directory.
|
||||
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
|
||||
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
|
||||
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
|
||||
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
|
||||
// use default just so the schema doesn't flag it as required
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct EntryRaw {
|
||||
pub(crate) name: String,
|
||||
#[serde(rename = "cmd")]
|
||||
pub command: PathBuf,
|
||||
|
||||
/// The allowed arguments for the command execution.
|
||||
pub args: ShellAllowedArgs,
|
||||
|
||||
/// If this command is a sidecar command.
|
||||
pub sidecar: bool,
|
||||
pub(crate) command: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub(crate) args: ShellAllowedArgs,
|
||||
#[serde(default)]
|
||||
pub(crate) sidecar: bool,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Entry {
|
||||
@@ -37,18 +31,7 @@ impl<'de> Deserialize<'de> for Entry {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct InnerEntry {
|
||||
name: String,
|
||||
#[serde(rename = "cmd")]
|
||||
command: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
args: ShellAllowedArgs,
|
||||
#[serde(default)]
|
||||
sidecar: bool,
|
||||
}
|
||||
|
||||
let config = InnerEntry::deserialize(deserializer)?;
|
||||
let config = EntryRaw::deserialize(deserializer)?;
|
||||
|
||||
if !config.sidecar && config.command.is_none() {
|
||||
return Err(DeError::custom(
|
||||
@@ -65,19 +48,11 @@ impl<'de> Deserialize<'de> for Entry {
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of command arguments allowed to be executed by the webview API.
|
||||
///
|
||||
/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all
|
||||
/// arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to
|
||||
/// be passed to the attached command configuration.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, schemars::JsonSchema)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShellAllowedArgs {
|
||||
/// Use a simple boolean to allow all or disable all arguments to this command configuration.
|
||||
Flag(bool),
|
||||
|
||||
/// A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.
|
||||
List(Vec<ShellAllowedArg>),
|
||||
}
|
||||
|
||||
@@ -87,33 +62,13 @@ impl Default for ShellAllowedArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// A command argument allowed to be executed by the webview API.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, schemars::JsonSchema)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShellAllowedArg {
|
||||
/// A non-configurable argument that is passed to the command in the order it was specified.
|
||||
Fixed(String),
|
||||
|
||||
/// A variable that is set while calling the command from the webview API.
|
||||
///
|
||||
Var {
|
||||
/// [regex] validator to require passed values to conform to an expected input.
|
||||
///
|
||||
/// This will require the argument value passed to this variable to match the `validator` regex
|
||||
/// before it will be executed.
|
||||
///
|
||||
/// The regex string is by default surrounded by `^...$` to match the full string.
|
||||
/// For example the `https?://\w+` regex would be registered as `^https?://\w+$`.
|
||||
///
|
||||
/// [regex]: <https://docs.rs/regex/latest/regex/#syntax>
|
||||
validator: String,
|
||||
|
||||
/// Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.
|
||||
///
|
||||
/// This means the regex will not match on the entire string by default, which might
|
||||
/// be exploited if your regex allow unexpected input to be considered valid.
|
||||
/// When using this option, make sure your regex is correct.
|
||||
#[serde(default)]
|
||||
raw: bool,
|
||||
},
|
||||
|
||||
@@ -221,7 +221,7 @@ fn update_app() {
|
||||
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
|
||||
format!("{bundle_updater_ext}.{updater_zip_ext}")
|
||||
} else {
|
||||
format!("{bundle_updater_ext}")
|
||||
bundle_updater_ext
|
||||
};
|
||||
let signature_extension = format!("{updater_extension}.sig");
|
||||
let signature_path = out_bundle_path.with_extension(signature_extension);
|
||||
|
||||
@@ -413,7 +413,7 @@ fn update_app() {
|
||||
),
|
||||
2 => (
|
||||
v2_config.version,
|
||||
Box::new(|| v2::bundle_paths(&root_dir, &v2_config.version))
|
||||
Box::new(|| v2::bundle_paths(&root_dir, v2_config.version))
|
||||
as Box<dyn Fn() -> Vec<(BundleTarget, PathBuf)>>,
|
||||
"-v2",
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user