use semver::Version;
use serde::{
de::{Deserializer, Error as DeError, Visitor},
Deserialize, Serialize, Serializer,
};
use serde_json::Value as JsonValue;
use serde_with::skip_serializing_none;
use url::Url;
use std::{
collections::HashMap,
fmt::{self, Display},
fs::read_to_string,
path::PathBuf,
str::FromStr,
};
pub mod parse;
fn default_true() -> bool {
true
}
#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum WindowUrl {
External(Url),
App(PathBuf),
}
impl fmt::Display for WindowUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::External(url) => write!(f, "{url}"),
Self::App(path) => write!(f, "{}", path.display()),
}
}
}
impl Default for WindowUrl {
fn default() -> Self {
Self::App("index.html".into())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
pub enum BundleType {
Deb,
AppImage,
Msi,
Nsis,
App,
Dmg,
Updater,
}
impl Display for BundleType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Deb => "deb",
Self::AppImage => "appimage",
Self::Msi => "msi",
Self::Nsis => "nsis",
Self::App => "app",
Self::Dmg => "dmg",
Self::Updater => "updater",
}
)
}
}
impl Serialize for BundleType {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
impl<'de> Deserialize<'de> for BundleType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
"deb" => Ok(Self::Deb),
"appimage" => Ok(Self::AppImage),
"msi" => Ok(Self::Msi),
"nsis" => Ok(Self::Nsis),
"app" => Ok(Self::App),
"dmg" => Ok(Self::Dmg),
"updater" => Ok(Self::Updater),
_ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub enum BundleTarget {
#[default]
All,
List(Vec<BundleType>),
One(BundleType),
}
#[cfg(feature = "schemars")]
pub(crate) trait Merge: Sized {
fn merge(self, other: Self) -> Self;
}
#[cfg(feature = "schema")]
use schemars::schema::{Metadata, Schema};
#[cfg(feature = "schema")]
impl<T: Merge> Merge for Option<T> {
fn merge(self, other: Self) -> Self {
match (self, other) {
(Some(x), Some(y)) => Some(x.merge(y)),
(None, y) => y,
(x, None) => x,
}
}
}
#[cfg(feature = "schema")]
impl<T: Merge> Merge for Box<T> {
fn merge(mut self, other: Self) -> Self {
*self = (*self).merge(*other);
self
}
}
#[cfg(feature = "schema")]
impl<T> Merge for Vec<T> {
fn merge(mut self, other: Self) -> Self {
self.extend(other);
self
}
}
#[cfg(feature = "schema")]
impl Merge for Metadata {
fn merge(self, other: Self) -> Self {
Metadata {
id: self.id.or(other.id),
title: self.title.or(other.title),
description: self.description.or(other.description),
default: self.default.or(other.default),
deprecated: self.deprecated || other.deprecated,
read_only: self.read_only || other.read_only,
write_only: self.write_only || other.write_only,
examples: self.examples.merge(other.examples),
}
}
}
#[cfg(feature = "schema")]
fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
if metadata == Metadata::default() {
schema
} else {
let mut schema_obj = schema.into_object();
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
Schema::Object(schema_obj)
}
}
#[cfg(feature = "schema")]
impl schemars::JsonSchema for BundleTarget {
fn schema_name() -> std::string::String {
"BundleTarget".to_owned()
}
fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let any_of = vec![
schemars::schema::SchemaObject {
enum_values: Some(vec!["all".into()]),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("Bundle all targets.".to_owned()),
..Default::default()
})),
..Default::default()
}
.into(),
apply_metadata(
generator.subschema_for::<Vec<BundleType>>(),
schemars::schema::Metadata {
description: Some("A list of bundle targets.".to_owned()),
..Default::default()
},
),
apply_metadata(
generator.subschema_for::<BundleType>(),
schemars::schema::Metadata {
description: Some("A single bundle target.".to_owned()),
..Default::default()
},
),
];
schemars::schema::SchemaObject {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
any_of: Some(any_of),
..Default::default()
})),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
..Default::default()
})),
..Default::default()
}
.into()
}
}
impl Serialize for BundleTarget {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::All => serializer.serialize_str("all"),
Self::List(l) => l.serialize(serializer),
Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
}
}
}
impl<'de> Deserialize<'de> for BundleTarget {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize, Serialize)]
#[serde(untagged)]
pub enum BundleTargetInner {
List(Vec<BundleType>),
One(BundleType),
All(String),
}
match BundleTargetInner::deserialize(deserializer)? {
BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
BundleTargetInner::All(t) => Err(DeError::custom(format!("invalid bundle type {t}"))),
BundleTargetInner::List(l) => Ok(Self::List(l)),
BundleTargetInner::One(t) => Ok(Self::One(t)),
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AppImageConfig {
#[serde(default, alias = "bundle-media-framework")]
pub bundle_media_framework: bool,
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DebConfig {
pub depends: Option<Vec<String>>,
#[serde(default)]
pub files: HashMap<PathBuf, PathBuf>,
pub desktop_template: Option<PathBuf>,
pub section: Option<String>,
pub priority: Option<String>,
pub changelog: Option<PathBuf>,
}
fn de_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let version = Option::<String>::deserialize(deserializer)?;
match version {
Some(v) if v.is_empty() => Ok(minimum_system_version()),
e => Ok(e),
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct MacConfig {
pub frameworks: Option<Vec<String>>,
#[serde(
deserialize_with = "de_minimum_system_version",
default = "minimum_system_version",
alias = "minimum-system-version"
)]
pub minimum_system_version: Option<String>,
#[serde(alias = "exception-domain")]
pub exception_domain: Option<String>,
pub license: Option<String>,
#[serde(alias = "signing-identity")]
pub signing_identity: Option<String>,
#[serde(alias = "provider-short-name")]
pub provider_short_name: Option<String>,
pub entitlements: Option<String>,
}
impl Default for MacConfig {
fn default() -> Self {
Self {
frameworks: None,
minimum_system_version: minimum_system_version(),
exception_domain: None,
license: None,
signing_identity: None,
provider_short_name: None,
entitlements: None,
}
}
}
fn minimum_system_version() -> Option<String> {
Some("10.13".into())
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WixLanguageConfig {
#[serde(alias = "locale-path")]
pub locale_path: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum WixLanguage {
One(String),
List(Vec<String>),
Localized(HashMap<String, WixLanguageConfig>),
}
impl Default for WixLanguage {
fn default() -> Self {
Self::One("en-US".into())
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WixConfig {
#[serde(default)]
pub language: WixLanguage,
pub template: Option<PathBuf>,
#[serde(default, alias = "fragment-paths")]
pub fragment_paths: Vec<PathBuf>,
#[serde(default, alias = "component-group-refs")]
pub component_group_refs: Vec<String>,
#[serde(default, alias = "component-refs")]
pub component_refs: Vec<String>,
#[serde(default, alias = "feature-group-refs")]
pub feature_group_refs: Vec<String>,
#[serde(default, alias = "feature-refs")]
pub feature_refs: Vec<String>,
#[serde(default, alias = "merge-refs")]
pub merge_refs: Vec<String>,
#[serde(default, alias = "skip-webview-install")]
pub skip_webview_install: bool,
pub license: Option<PathBuf>,
#[serde(default, alias = "enable-elevated-update-task")]
pub enable_elevated_update_task: bool,
#[serde(alias = "banner-path")]
pub banner_path: Option<PathBuf>,
#[serde(alias = "dialog-image-path")]
pub dialog_image_path: Option<PathBuf>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum NsisCompression {
Zlib,
Bzip2,
Lzma,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct NsisConfig {
pub template: Option<PathBuf>,
pub license: Option<PathBuf>,
#[serde(alias = "header-image")]
pub header_image: Option<PathBuf>,
#[serde(alias = "sidebar-image")]
pub sidebar_image: Option<PathBuf>,
#[serde(alias = "install-icon")]
pub installer_icon: Option<PathBuf>,
#[serde(default, alias = "install-mode")]
pub install_mode: NSISInstallerMode,
pub languages: Option<Vec<String>>,
pub custom_language_files: Option<HashMap<String, PathBuf>>,
#[serde(default, alias = "display-language-selector")]
pub display_language_selector: bool,
pub compression: Option<NsisCompression>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum NSISInstallerMode {
#[default]
CurrentUser,
PerMachine,
Both,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum WebviewInstallMode {
Skip,
DownloadBootstrapper {
#[serde(default = "default_true")]
silent: bool,
},
EmbedBootstrapper {
#[serde(default = "default_true")]
silent: bool,
},
OfflineInstaller {
#[serde(default = "default_true")]
silent: bool,
},
FixedRuntime {
path: PathBuf,
},
}
impl Default for WebviewInstallMode {
fn default() -> Self {
Self::DownloadBootstrapper { silent: true }
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WindowsConfig {
#[serde(alias = "digest-algorithm")]
pub digest_algorithm: Option<String>,
#[serde(alias = "certificate-thumbprint")]
pub certificate_thumbprint: Option<String>,
#[serde(alias = "timestamp-url")]
pub timestamp_url: Option<String>,
#[serde(default)]
pub tsp: bool,
#[serde(default, alias = "webview-install-mode")]
pub webview_install_mode: WebviewInstallMode,
#[serde(alias = "webview-fixed-runtime-path")]
pub webview_fixed_runtime_path: Option<PathBuf>,
#[serde(default = "default_true", alias = "allow-downgrades")]
pub allow_downgrades: bool,
pub wix: Option<WixConfig>,
pub nsis: Option<NsisConfig>,
}
impl Default for WindowsConfig {
fn default() -> Self {
Self {
digest_algorithm: None,
certificate_thumbprint: None,
timestamp_url: None,
tsp: false,
webview_install_mode: Default::default(),
webview_fixed_runtime_path: None,
allow_downgrades: true,
wix: None,
nsis: None,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum BundleResources {
List(Vec<String>),
Map(HashMap<String, String>),
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BundleConfig {
#[serde(default)]
pub active: bool,
#[serde(default)]
pub targets: BundleTarget,
pub identifier: String,
pub publisher: Option<String>,
#[serde(default)]
pub icon: Vec<String>,
pub resources: Option<BundleResources>,
pub copyright: Option<String>,
pub category: Option<String>,
#[serde(alias = "short-description")]
pub short_description: Option<String>,
#[serde(alias = "long-description")]
pub long_description: Option<String>,
#[serde(default)]
pub appimage: AppImageConfig,
#[serde(default)]
pub deb: DebConfig,
#[serde(rename = "macOS", default)]
pub macos: MacConfig,
#[serde(alias = "external-bin")]
pub external_bin: Option<Vec<String>>,
#[serde(default)]
pub windows: WindowsConfig,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CliArg {
pub short: Option<char>,
pub name: String,
pub description: Option<String>,
#[serde(alias = "long-description")]
pub long_description: Option<String>,
#[serde(default, alias = "takes-value")]
pub takes_value: bool,
#[serde(default)]
pub multiple: bool,
#[serde(default, alias = "multiple-occurrences")]
pub multiple_occurrences: bool,
#[serde(alias = "number-of-values")]
pub number_of_values: Option<usize>,
#[serde(alias = "possible-values")]
pub possible_values: Option<Vec<String>>,
#[serde(alias = "min-values")]
pub min_values: Option<usize>,
#[serde(alias = "max-values")]
pub max_values: Option<usize>,
#[serde(default)]
pub required: bool,
#[serde(alias = "required-unless-present")]
pub required_unless_present: Option<String>,
#[serde(alias = "required-unless-present-all")]
pub required_unless_present_all: Option<Vec<String>>,
#[serde(alias = "required-unless-present-any")]
pub required_unless_present_any: Option<Vec<String>>,
#[serde(alias = "conflicts-with")]
pub conflicts_with: Option<String>,
#[serde(alias = "conflicts-with-all")]
pub conflicts_with_all: Option<Vec<String>>,
pub requires: Option<String>,
#[serde(alias = "requires-all")]
pub requires_all: Option<Vec<String>>,
#[serde(alias = "requires-if")]
pub requires_if: Option<Vec<String>>,
#[serde(alias = "requires-if-eq")]
pub required_if_eq: Option<Vec<String>>,
#[serde(alias = "requires-equals")]
pub require_equals: Option<bool>,
#[cfg_attr(feature = "schema", validate(range(min = 1)))]
pub index: Option<usize>,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CliConfig {
pub description: Option<String>,
#[serde(alias = "long-description")]
pub long_description: Option<String>,
#[serde(alias = "before-help")]
pub before_help: Option<String>,
#[serde(alias = "after-help")]
pub after_help: Option<String>,
pub args: Option<Vec<CliArg>>,
pub subcommands: Option<HashMap<String, CliConfig>>,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WindowConfig {
#[serde(default = "default_window_label")]
pub label: String,
#[serde(default)]
pub url: WindowUrl,
#[serde(alias = "user-agent")]
pub user_agent: Option<String>,
#[serde(default = "default_true", alias = "file-drop-enabled")]
pub file_drop_enabled: bool,
#[serde(default)]
pub center: bool,
pub x: Option<f64>,
pub y: Option<f64>,
#[serde(default = "default_width")]
pub width: f64,
#[serde(default = "default_height")]
pub height: f64,
#[serde(alias = "min-width")]
pub min_width: Option<f64>,
#[serde(alias = "min-height")]
pub min_height: Option<f64>,
#[serde(alias = "max-width")]
pub max_width: Option<f64>,
#[serde(alias = "max-height")]
pub max_height: Option<f64>,
#[serde(default = "default_true")]
pub resizable: bool,
#[serde(default = "default_true")]
pub maximizable: bool,
#[serde(default = "default_true")]
pub minimizable: bool,
#[serde(default = "default_true")]
pub closable: bool,
#[serde(default = "default_title")]
pub title: String,
#[serde(default)]
pub fullscreen: bool,
#[serde(default = "default_true")]
pub focus: bool,
#[serde(default)]
pub transparent: bool,
#[serde(default)]
pub maximized: bool,
#[serde(default = "default_true")]
pub visible: bool,
#[serde(default = "default_true")]
pub decorations: bool,
#[serde(default, alias = "always-on-top")]
pub always_on_top: bool,
#[serde(default, alias = "content-protected")]
pub content_protected: bool,
#[serde(default, alias = "skip-taskbar")]
pub skip_taskbar: bool,
pub theme: Option<Theme>,
#[serde(default, alias = "title-bar-style")]
pub title_bar_style: TitleBarStyle,
#[serde(default, alias = "hidden-title")]
pub hidden_title: bool,
#[serde(default, alias = "accept-first-mouse")]
pub accept_first_mouse: bool,
#[serde(default, alias = "tabbing-identifier")]
pub tabbing_identifier: Option<String>,
#[serde(default, alias = "additional-browser-args")]
pub additional_browser_args: Option<String>,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
label: default_window_label(),
url: WindowUrl::default(),
user_agent: None,
file_drop_enabled: true,
center: false,
x: None,
y: None,
width: default_width(),
height: default_height(),
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: true,
maximizable: true,
minimizable: true,
closable: true,
title: default_title(),
fullscreen: false,
focus: false,
transparent: false,
maximized: false,
visible: true,
decorations: true,
always_on_top: false,
content_protected: false,
skip_taskbar: false,
theme: None,
title_bar_style: Default::default(),
hidden_title: false,
accept_first_mouse: false,
tabbing_identifier: None,
additional_browser_args: None,
}
}
}
fn default_window_label() -> String {
"main".to_string()
}
fn default_width() -> f64 {
800f64
}
fn default_height() -> f64 {
600f64
}
fn default_title() -> String {
"Tauri App".to_string()
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", untagged)]
pub enum CspDirectiveSources {
Inline(String),
List(Vec<String>),
}
impl Default for CspDirectiveSources {
fn default() -> Self {
Self::List(Vec::new())
}
}
impl From<CspDirectiveSources> for Vec<String> {
fn from(sources: CspDirectiveSources) -> Self {
match sources {
CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
CspDirectiveSources::List(l) => l,
}
}
}
impl CspDirectiveSources {
pub fn contains(&self, source: &str) -> bool {
match self {
Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
Self::List(l) => l.contains(&source.into()),
}
}
pub fn push<S: AsRef<str>>(&mut self, source: S) {
match self {
Self::Inline(s) => {
s.push(' ');
s.push_str(source.as_ref());
}
Self::List(l) => {
l.push(source.as_ref().to_string());
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", untagged)]
pub enum Csp {
Policy(String),
DirectiveMap(HashMap<String, CspDirectiveSources>),
}
impl From<HashMap<String, CspDirectiveSources>> for Csp {
fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
Self::DirectiveMap(map)
}
}
impl From<Csp> for HashMap<String, CspDirectiveSources> {
fn from(csp: Csp) -> Self {
match csp {
Csp::Policy(policy) => {
let mut map = HashMap::new();
for directive in policy.split(';') {
let mut tokens = directive.trim().split(' ');
if let Some(directive) = tokens.next() {
let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
map.insert(directive.to_string(), CspDirectiveSources::List(sources));
}
}
map
}
Csp::DirectiveMap(m) => m,
}
}
}
impl Display for Csp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Policy(s) => write!(f, "{s}"),
Self::DirectiveMap(m) => {
let len = m.len();
let mut i = 0;
for (directive, sources) in m {
let sources: Vec<String> = sources.clone().into();
write!(f, "{} {}", directive, sources.join(" "))?;
i += 1;
if i != len {
write!(f, "; ")?;
}
}
Ok(())
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum DisabledCspModificationKind {
Flag(bool),
List(Vec<String>),
}
impl Default for DisabledCspModificationKind {
fn default() -> Self {
Self::Flag(false)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RemoteDomainAccessScope {
pub scheme: Option<String>,
pub domain: String,
pub windows: Vec<String>,
#[serde(default)]
pub plugins: Vec<String>,
#[serde(default, rename = "enableTauriAPI", alias = "enable-tauri-api")]
pub enable_tauri_api: bool,
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SecurityConfig {
pub csp: Option<Csp>,
#[serde(alias = "dev-csp")]
pub dev_csp: Option<Csp>,
#[serde(default, alias = "freeze-prototype")]
pub freeze_prototype: bool,
#[serde(default, alias = "dangerous-disable-asset-csp-modification")]
pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
#[serde(default, alias = "dangerous-remote-domain-ipc-access")]
pub dangerous_remote_domain_ipc_access: Vec<RemoteDomainAccessScope>,
#[serde(default, alias = "dangerous-use-http-scheme")]
pub dangerous_use_http_scheme: bool,
}
pub trait Allowlist {
fn all_features() -> Vec<&'static str>;
fn to_features(&self) -> Vec<&'static str>;
}
macro_rules! check_feature {
($self:ident, $features:ident, $flag:ident, $feature_name: expr) => {
if $self.$flag {
$features.push($feature_name)
}
};
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum FsAllowlistScope {
AllowedPaths(Vec<PathBuf>),
#[serde(rename_all = "camelCase")]
Scope {
#[serde(default)]
allow: Vec<PathBuf>,
#[serde(default)]
deny: Vec<PathBuf>,
#[serde(alias = "require-literal-leading-dot")]
require_literal_leading_dot: Option<bool>,
},
}
impl Default for FsAllowlistScope {
fn default() -> Self {
Self::AllowedPaths(Vec::new())
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FsAllowlistConfig {
#[serde(default)]
pub scope: FsAllowlistScope,
#[serde(default)]
pub all: bool,
#[serde(default, alias = "read-file")]
pub read_file: bool,
#[serde(default, alias = "write-file")]
pub write_file: bool,
#[serde(default, alias = "read-dir")]
pub read_dir: bool,
#[serde(default, alias = "copy-file")]
pub copy_file: bool,
#[serde(default, alias = "create-dir")]
pub create_dir: bool,
#[serde(default, alias = "remove-dir")]
pub remove_dir: bool,
#[serde(default, alias = "remove-file")]
pub remove_file: bool,
#[serde(default, alias = "rename-file")]
pub rename_file: bool,
#[serde(default)]
pub exists: bool,
}
impl Allowlist for FsAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
scope: Default::default(),
all: false,
read_file: true,
write_file: true,
read_dir: true,
copy_file: true,
create_dir: true,
remove_dir: true,
remove_file: true,
rename_file: true,
exists: true,
};
let mut features = allowlist.to_features();
features.push("fs-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["fs-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, read_file, "fs-read-file");
check_feature!(self, features, write_file, "fs-write-file");
check_feature!(self, features, read_dir, "fs-read-dir");
check_feature!(self, features, copy_file, "fs-copy-file");
check_feature!(self, features, create_dir, "fs-create-dir");
check_feature!(self, features, remove_dir, "fs-remove-dir");
check_feature!(self, features, remove_file, "fs-remove-file");
check_feature!(self, features, rename_file, "fs-rename-file");
check_feature!(self, features, exists, "fs-exists");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WindowAllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default)]
pub create: bool,
#[serde(default)]
pub center: bool,
#[serde(default, alias = "request-user-attention")]
pub request_user_attention: bool,
#[serde(default, alias = "set-resizable")]
pub set_resizable: bool,
#[serde(default, alias = "set-maximizable")]
pub set_maximizable: bool,
#[serde(default, alias = "set-minimizable")]
pub set_minimizable: bool,
#[serde(default, alias = "set-closable")]
pub set_closable: bool,
#[serde(default, alias = "set-title")]
pub set_title: bool,
#[serde(default)]
pub maximize: bool,
#[serde(default)]
pub unmaximize: bool,
#[serde(default)]
pub minimize: bool,
#[serde(default)]
pub unminimize: bool,
#[serde(default)]
pub show: bool,
#[serde(default)]
pub hide: bool,
#[serde(default)]
pub close: bool,
#[serde(default, alias = "set-decorations")]
pub set_decorations: bool,
#[serde(default, alias = "set-always-on-top")]
pub set_always_on_top: bool,
#[serde(default, alias = "set-content-protected")]
pub set_content_protected: bool,
#[serde(default, alias = "set-size")]
pub set_size: bool,
#[serde(default, alias = "set-min-size")]
pub set_min_size: bool,
#[serde(default, alias = "set-max-size")]
pub set_max_size: bool,
#[serde(default, alias = "set-position")]
pub set_position: bool,
#[serde(default, alias = "set-fullscreen")]
pub set_fullscreen: bool,
#[serde(default, alias = "set-focus")]
pub set_focus: bool,
#[serde(default, alias = "set-icon")]
pub set_icon: bool,
#[serde(default, alias = "set-skip-taskbar")]
pub set_skip_taskbar: bool,
#[serde(default, alias = "set-cursor-grab")]
pub set_cursor_grab: bool,
#[serde(default, alias = "set-cursor-visible")]
pub set_cursor_visible: bool,
#[serde(default, alias = "set-cursor-icon")]
pub set_cursor_icon: bool,
#[serde(default, alias = "set-cursor-position")]
pub set_cursor_position: bool,
#[serde(default, alias = "set-ignore-cursor-events")]
pub set_ignore_cursor_events: bool,
#[serde(default, alias = "start-dragging")]
pub start_dragging: bool,
#[serde(default)]
pub print: bool,
}
impl Allowlist for WindowAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
all: false,
create: true,
center: true,
request_user_attention: true,
set_resizable: true,
set_maximizable: true,
set_minimizable: true,
set_closable: true,
set_title: true,
maximize: true,
unmaximize: true,
minimize: true,
unminimize: true,
show: true,
hide: true,
close: true,
set_decorations: true,
set_always_on_top: true,
set_content_protected: false,
set_size: true,
set_min_size: true,
set_max_size: true,
set_position: true,
set_fullscreen: true,
set_focus: true,
set_icon: true,
set_skip_taskbar: true,
set_cursor_grab: true,
set_cursor_visible: true,
set_cursor_icon: true,
set_cursor_position: true,
set_ignore_cursor_events: true,
start_dragging: true,
print: true,
};
let mut features = allowlist.to_features();
features.push("window-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["window-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, create, "window-create");
check_feature!(self, features, center, "window-center");
check_feature!(
self,
features,
request_user_attention,
"window-request-user-attention"
);
check_feature!(self, features, set_resizable, "window-set-resizable");
check_feature!(self, features, set_maximizable, "window-set-maximizable");
check_feature!(self, features, set_minimizable, "window-set-minimizable");
check_feature!(self, features, set_closable, "window-set-closable");
check_feature!(self, features, set_title, "window-set-title");
check_feature!(self, features, maximize, "window-maximize");
check_feature!(self, features, unmaximize, "window-unmaximize");
check_feature!(self, features, minimize, "window-minimize");
check_feature!(self, features, unminimize, "window-unminimize");
check_feature!(self, features, show, "window-show");
check_feature!(self, features, hide, "window-hide");
check_feature!(self, features, close, "window-close");
check_feature!(self, features, set_decorations, "window-set-decorations");
check_feature!(
self,
features,
set_always_on_top,
"window-set-always-on-top"
);
check_feature!(
self,
features,
set_content_protected,
"window-set-content-protected"
);
check_feature!(self, features, set_size, "window-set-size");
check_feature!(self, features, set_min_size, "window-set-min-size");
check_feature!(self, features, set_max_size, "window-set-max-size");
check_feature!(self, features, set_position, "window-set-position");
check_feature!(self, features, set_fullscreen, "window-set-fullscreen");
check_feature!(self, features, set_focus, "window-set-focus");
check_feature!(self, features, set_icon, "window-set-icon");
check_feature!(self, features, set_skip_taskbar, "window-set-skip-taskbar");
check_feature!(self, features, set_cursor_grab, "window-set-cursor-grab");
check_feature!(
self,
features,
set_cursor_visible,
"window-set-cursor-visible"
);
check_feature!(self, features, set_cursor_icon, "window-set-cursor-icon");
check_feature!(
self,
features,
set_cursor_position,
"window-set-cursor-position"
);
check_feature!(
self,
features,
set_ignore_cursor_events,
"window-set-ignore-cursor-events"
);
check_feature!(self, features, start_dragging, "window-start-dragging");
check_feature!(self, features, print, "window-print");
features
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ShellAllowedCommand {
pub name: String,
#[serde(rename = "cmd", default)] pub command: PathBuf,
#[serde(default)]
pub args: ShellAllowedArgs,
#[serde(default)]
pub sidecar: bool,
}
impl<'de> Deserialize<'de> for ShellAllowedCommand {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerShellAllowedCommand {
name: String,
#[serde(rename = "cmd")]
command: Option<PathBuf>,
#[serde(default)]
args: ShellAllowedArgs,
#[serde(default)]
sidecar: bool,
}
let config = InnerShellAllowedCommand::deserialize(deserializer)?;
if !config.sidecar && config.command.is_none() {
return Err(DeError::custom(
"The shell scope `command` value is required.",
));
}
Ok(ShellAllowedCommand {
name: config.name,
command: config.command.unwrap_or_default(),
args: config.args,
sidecar: config.sidecar,
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellAllowedArgs {
Flag(bool),
List(Vec<ShellAllowedArg>),
}
impl Default for ShellAllowedArgs {
fn default() -> Self {
Self::Flag(false)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellAllowedArg {
Fixed(String),
Var {
validator: String,
},
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ShellAllowlistScope(pub Vec<ShellAllowedCommand>);
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellAllowlistOpen {
Flag(bool),
Validate(String),
}
impl Default for ShellAllowlistOpen {
fn default() -> Self {
Self::Flag(false)
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ShellAllowlistConfig {
#[serde(default)]
pub scope: ShellAllowlistScope,
#[serde(default)]
pub all: bool,
#[serde(default)]
pub execute: bool,
#[serde(default)]
pub sidecar: bool,
#[serde(default)]
pub open: ShellAllowlistOpen,
}
impl Allowlist for ShellAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
scope: Default::default(),
all: false,
execute: true,
sidecar: true,
open: ShellAllowlistOpen::Flag(true),
};
let mut features = allowlist.to_features();
features.push("shell-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["shell-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, execute, "shell-execute");
check_feature!(self, features, sidecar, "shell-sidecar");
if !matches!(self.open, ShellAllowlistOpen::Flag(false)) {
features.push("shell-open")
}
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DialogAllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default)]
pub open: bool,
#[serde(default)]
pub save: bool,
#[serde(default)]
pub message: bool,
#[serde(default)]
pub ask: bool,
#[serde(default)]
pub confirm: bool,
}
impl Allowlist for DialogAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
all: false,
open: true,
save: true,
message: true,
ask: true,
confirm: true,
};
let mut features = allowlist.to_features();
features.push("dialog-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["dialog-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, open, "dialog-open");
check_feature!(self, features, save, "dialog-save");
check_feature!(self, features, message, "dialog-message");
check_feature!(self, features, ask, "dialog-ask");
check_feature!(self, features, confirm, "dialog-confirm");
features
}
}
}
#[allow(rustdoc::bare_urls)]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct HttpAllowlistScope(pub Vec<Url>);
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct HttpAllowlistConfig {
#[serde(default)]
pub scope: HttpAllowlistScope,
#[serde(default)]
pub all: bool,
#[serde(default)]
pub request: bool,
}
impl Allowlist for HttpAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
scope: Default::default(),
all: false,
request: true,
};
let mut features = allowlist.to_features();
features.push("http-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["http-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, request, "http-request");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct NotificationAllowlistConfig {
#[serde(default)]
pub all: bool,
}
impl Allowlist for NotificationAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self { all: false };
let mut features = allowlist.to_features();
features.push("notification-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["notification-all"]
} else {
vec![]
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GlobalShortcutAllowlistConfig {
#[serde(default)]
pub all: bool,
}
impl Allowlist for GlobalShortcutAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self { all: false };
let mut features = allowlist.to_features();
features.push("global-shortcut-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["global-shortcut-all"]
} else {
vec![]
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct OsAllowlistConfig {
#[serde(default)]
pub all: bool,
}
impl Allowlist for OsAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self { all: false };
let mut features = allowlist.to_features();
features.push("os-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["os-all"]
} else {
vec![]
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PathAllowlistConfig {
#[serde(default)]
pub all: bool,
}
impl Allowlist for PathAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self { all: false };
let mut features = allowlist.to_features();
features.push("path-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["path-all"]
} else {
vec![]
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProtocolAllowlistConfig {
#[serde(default, alias = "asset-scope")]
pub asset_scope: FsAllowlistScope,
#[serde(default)]
pub all: bool,
#[serde(default)]
pub asset: bool,
}
impl Allowlist for ProtocolAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
asset_scope: Default::default(),
all: false,
asset: true,
};
let mut features = allowlist.to_features();
features.push("protocol-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["protocol-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, asset, "protocol-asset");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ProcessAllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default)]
pub relaunch: bool,
#[serde(
default,
alias = "relaunchDangerousAllowSymlinkMacOS",
alias = "relaunch-dangerous-allow-symlink-macos"
)]
pub relaunch_dangerous_allow_symlink_macos: bool,
#[serde(default)]
pub exit: bool,
}
impl Allowlist for ProcessAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
all: false,
relaunch: true,
relaunch_dangerous_allow_symlink_macos: false,
exit: true,
};
let mut features = allowlist.to_features();
features.push("process-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["process-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, relaunch, "process-relaunch");
check_feature!(
self,
features,
relaunch_dangerous_allow_symlink_macos,
"process-relaunch-dangerous-allow-symlink-macos"
);
check_feature!(self, features, exit, "process-exit");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ClipboardAllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default, alias = "writeText")]
pub write_text: bool,
#[serde(default, alias = "readText")]
pub read_text: bool,
}
impl Allowlist for ClipboardAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
all: false,
write_text: true,
read_text: true,
};
let mut features = allowlist.to_features();
features.push("clipboard-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["clipboard-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, write_text, "clipboard-write-text");
check_feature!(self, features, read_text, "clipboard-read-text");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AppAllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default)]
pub show: bool,
#[serde(default)]
pub hide: bool,
}
impl Allowlist for AppAllowlistConfig {
fn all_features() -> Vec<&'static str> {
let allowlist = Self {
all: false,
show: true,
hide: true,
};
let mut features = allowlist.to_features();
features.push("app-all");
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["app-all"]
} else {
let mut features = Vec::new();
check_feature!(self, features, show, "app-show");
check_feature!(self, features, hide, "app-hide");
features
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AllowlistConfig {
#[serde(default)]
pub all: bool,
#[serde(default)]
pub fs: FsAllowlistConfig,
#[serde(default)]
pub window: WindowAllowlistConfig,
#[serde(default)]
pub shell: ShellAllowlistConfig,
#[serde(default)]
pub dialog: DialogAllowlistConfig,
#[serde(default)]
pub http: HttpAllowlistConfig,
#[serde(default)]
pub notification: NotificationAllowlistConfig,
#[serde(default, alias = "global-shortcut")]
pub global_shortcut: GlobalShortcutAllowlistConfig,
#[serde(default)]
pub os: OsAllowlistConfig,
#[serde(default)]
pub path: PathAllowlistConfig,
#[serde(default)]
pub protocol: ProtocolAllowlistConfig,
#[serde(default)]
pub process: ProcessAllowlistConfig,
#[serde(default)]
pub clipboard: ClipboardAllowlistConfig,
#[serde(default)]
pub app: AppAllowlistConfig,
}
impl Allowlist for AllowlistConfig {
fn all_features() -> Vec<&'static str> {
let mut features = vec!["api-all"];
features.extend(FsAllowlistConfig::all_features());
features.extend(WindowAllowlistConfig::all_features());
features.extend(ShellAllowlistConfig::all_features());
features.extend(DialogAllowlistConfig::all_features());
features.extend(HttpAllowlistConfig::all_features());
features.extend(NotificationAllowlistConfig::all_features());
features.extend(GlobalShortcutAllowlistConfig::all_features());
features.extend(OsAllowlistConfig::all_features());
features.extend(PathAllowlistConfig::all_features());
features.extend(ProtocolAllowlistConfig::all_features());
features.extend(ProcessAllowlistConfig::all_features());
features.extend(ClipboardAllowlistConfig::all_features());
features.extend(AppAllowlistConfig::all_features());
features
}
fn to_features(&self) -> Vec<&'static str> {
if self.all {
vec!["api-all"]
} else {
let mut features = Vec::new();
features.extend(self.fs.to_features());
features.extend(self.window.to_features());
features.extend(self.shell.to_features());
features.extend(self.dialog.to_features());
features.extend(self.http.to_features());
features.extend(self.notification.to_features());
features.extend(self.global_shortcut.to_features());
features.extend(self.os.to_features());
features.extend(self.path.to_features());
features.extend(self.protocol.to_features());
features.extend(self.process.to_features());
features.extend(self.clipboard.to_features());
features.extend(self.app.to_features());
features
}
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase", tag = "use", content = "options")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum PatternKind {
#[default]
Brownfield,
Isolation {
dir: PathBuf,
},
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TauriConfig {
#[serde(default)]
pub pattern: PatternKind,
#[serde(default)]
pub windows: Vec<WindowConfig>,
pub cli: Option<CliConfig>,
#[serde(default)]
pub bundle: BundleConfig,
#[serde(default)]
pub allowlist: AllowlistConfig,
#[serde(default)]
pub security: SecurityConfig,
#[serde(default)]
pub updater: UpdaterConfig,
#[serde(alias = "system-tray")]
pub system_tray: Option<SystemTrayConfig>,
#[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
pub macos_private_api: bool,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UpdaterEndpoint(pub Url);
impl std::fmt::Display for UpdaterEndpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'de> Deserialize<'de> for UpdaterEndpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let url = Url::deserialize(deserializer)?;
#[cfg(all(not(debug_assertions), not(feature = "schema")))]
{
if url.scheme() != "https" {
return Err(serde::de::Error::custom(
"The configured updater endpoint must use the `https` protocol.",
));
}
}
Ok(Self(url))
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "camelCase"))]
pub enum WindowsUpdateInstallMode {
BasicUi,
Quiet,
#[default]
Passive,
}
impl Display for WindowsUpdateInstallMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::BasicUi => "basicUI",
Self::Quiet => "quiet",
Self::Passive => "passive",
}
)
}
}
impl Serialize for WindowsUpdateInstallMode {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
impl<'de> Deserialize<'de> for WindowsUpdateInstallMode {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
"basicui" => Ok(Self::BasicUi),
"quiet" => Ok(Self::Quiet),
"passive" => Ok(Self::Passive),
_ => Err(DeError::custom(format!(
"unknown update install mode '{s}'"
))),
}
}
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UpdaterWindowsConfig {
#[serde(default, alias = "installer-args")]
pub installer_args: Vec<String>,
#[serde(default, alias = "install-mode")]
pub install_mode: WindowsUpdateInstallMode,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UpdaterConfig {
#[serde(default)]
pub active: bool,
#[serde(default = "default_true")]
pub dialog: bool,
#[allow(rustdoc::bare_urls)]
pub endpoints: Option<Vec<UpdaterEndpoint>>,
#[serde(default)] pub pubkey: String,
#[serde(default)]
pub windows: UpdaterWindowsConfig,
}
impl<'de> Deserialize<'de> for UpdaterConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerUpdaterConfig {
#[serde(default)]
active: bool,
#[serde(default = "default_true")]
dialog: bool,
endpoints: Option<Vec<UpdaterEndpoint>>,
pubkey: Option<String>,
#[serde(default)]
windows: UpdaterWindowsConfig,
}
let config = InnerUpdaterConfig::deserialize(deserializer)?;
if config.active && config.pubkey.is_none() {
return Err(DeError::custom(
"The updater `pubkey` configuration is required.",
));
}
Ok(UpdaterConfig {
active: config.active,
dialog: config.dialog,
endpoints: config.endpoints,
pubkey: config.pubkey.unwrap_or_default(),
windows: config.windows,
})
}
}
impl Default for UpdaterConfig {
fn default() -> Self {
Self {
active: false,
dialog: true,
endpoints: None,
pubkey: "".into(),
windows: Default::default(),
}
}
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SystemTrayConfig {
#[serde(alias = "icon-path")]
pub icon_path: PathBuf,
#[serde(default, alias = "icon-as-template")]
pub icon_as_template: bool,
#[serde(default = "default_true", alias = "menu-on-left-click")]
pub menu_on_left_click: bool,
pub title: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum AppUrl {
Url(WindowUrl),
Files(Vec<PathBuf>),
}
impl std::fmt::Display for AppUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Url(url) => write!(f, "{url}"),
Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", untagged)]
pub enum BeforeDevCommand {
Script(String),
ScriptWithOptions {
script: String,
cwd: Option<String>,
#[serde(default)]
wait: bool,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", untagged)]
pub enum HookCommand {
Script(String),
ScriptWithOptions {
script: String,
cwd: Option<String>,
},
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BuildConfig {
pub runner: Option<String>,
#[serde(default = "default_dev_path", alias = "dev-path")]
pub dev_path: AppUrl,
#[serde(default = "default_dist_dir", alias = "dist-dir")]
pub dist_dir: AppUrl,
#[serde(alias = "before-dev-command")]
pub before_dev_command: Option<BeforeDevCommand>,
#[serde(alias = "before-build-command")]
pub before_build_command: Option<HookCommand>,
#[serde(alias = "before-bundle-command")]
pub before_bundle_command: Option<HookCommand>,
pub features: Option<Vec<String>>,
#[serde(default, alias = "with-global-tauri")]
pub with_global_tauri: bool,
}
impl Default for BuildConfig {
fn default() -> Self {
Self {
runner: None,
dev_path: default_dev_path(),
dist_dir: default_dist_dir(),
before_dev_command: None,
before_build_command: None,
before_bundle_command: None,
features: None,
with_global_tauri: false,
}
}
}
fn default_dev_path() -> AppUrl {
AppUrl::Url(WindowUrl::External(
Url::parse("http://localhost:8080").unwrap(),
))
}
fn default_dist_dir() -> AppUrl {
AppUrl::Url(WindowUrl::App("../dist".into()))
}
#[derive(Debug, PartialEq, Eq)]
struct PackageVersion(String);
impl<'d> serde::Deserialize<'d> for PackageVersion {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<PackageVersion, D::Error> {
struct PackageVersionVisitor;
impl<'d> Visitor<'d> for PackageVersionVisitor {
type Value = PackageVersion;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"a semver string or a path to a package.json file"
)
}
fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
let path = PathBuf::from(value);
if path.exists() {
let json_str = read_to_string(&path)
.map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
let package_json: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
if let Some(obj) = package_json.as_object() {
let version = obj
.get("version")
.ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
.as_str()
.ok_or_else(|| {
DeError::custom(format!("`{} > version` must be a string", path.display()))
})?;
Ok(PackageVersion(
Version::from_str(version)
.map_err(|_| DeError::custom("`package > version` must be a semver string"))?
.to_string(),
))
} else {
Err(DeError::custom(
"`package > version` value is not a path to a JSON object",
))
}
} else {
Ok(PackageVersion(
Version::from_str(value)
.map_err(|_| DeError::custom("`package > version` must be a semver string"))?
.to_string(),
))
}
}
}
deserializer.deserialize_string(PackageVersionVisitor {})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PackageConfig {
#[serde(alias = "product-name")]
#[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
pub product_name: Option<String>,
#[serde(deserialize_with = "version_deserializer", default)]
pub version: Option<String>,
}
fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
}
#[allow(rustdoc::invalid_codeblock_attributes)]
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Config {
#[serde(rename = "$schema")]
pub schema: Option<String>,
#[serde(default)]
pub package: PackageConfig,
#[serde(default)]
pub tauri: TauriConfig,
#[serde(default = "default_build")]
pub build: BuildConfig,
#[serde(default)]
pub plugins: PluginConfig,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct PluginConfig(pub HashMap<String, JsonValue>);
fn default_build() -> BuildConfig {
BuildConfig {
runner: None,
dev_path: default_dev_path(),
dist_dir: default_dist_dir(),
before_dev_command: None,
before_build_command: None,
before_bundle_command: None,
features: None,
with_global_tauri: false,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum TitleBarStyle {
#[default]
Visible,
Transparent,
Overlay,
}
impl Serialize for TitleBarStyle {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
impl<'de> Deserialize<'de> for TitleBarStyle {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.to_lowercase().as_str() {
"transparent" => Self::Transparent,
"overlay" => Self::Overlay,
_ => Self::Visible,
})
}
}
impl Display for TitleBarStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Visible => "Visible",
Self::Transparent => "Transparent",
Self::Overlay => "Overlay",
}
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum Theme {
Light,
Dark,
}
impl Serialize for Theme {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
impl<'de> Deserialize<'de> for Theme {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.to_lowercase().as_str() {
"dark" => Self::Dark,
_ => Self::Light,
})
}
}
impl Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Light => "light",
Self::Dark => "dark",
}
)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_defaults() {
let t_config = TauriConfig::default();
let b_config = BuildConfig::default();
let d_path = default_dev_path();
let d_windows: Vec<WindowConfig> = vec![];
let d_bundle = BundleConfig::default();
let d_updater = UpdaterConfig::default();
let tauri = TauriConfig {
pattern: Default::default(),
windows: vec![],
bundle: BundleConfig {
active: false,
targets: Default::default(),
identifier: String::from(""),
publisher: None,
icon: Vec::new(),
resources: None,
copyright: None,
category: None,
short_description: None,
long_description: None,
appimage: Default::default(),
deb: Default::default(),
macos: Default::default(),
external_bin: None,
windows: Default::default(),
},
cli: None,
updater: UpdaterConfig {
active: false,
dialog: true,
pubkey: "".into(),
endpoints: None,
windows: Default::default(),
},
security: SecurityConfig {
csp: None,
dev_csp: None,
freeze_prototype: false,
dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
dangerous_remote_domain_ipc_access: Vec::new(),
dangerous_use_http_scheme: false,
},
allowlist: AllowlistConfig::default(),
system_tray: None,
macos_private_api: false,
};
let build = BuildConfig {
runner: None,
dev_path: AppUrl::Url(WindowUrl::External(
Url::parse("http://localhost:8080").unwrap(),
)),
dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())),
before_dev_command: None,
before_build_command: None,
before_bundle_command: None,
features: None,
with_global_tauri: false,
};
assert_eq!(t_config, tauri);
assert_eq!(b_config, build);
assert_eq!(d_bundle, tauri.bundle);
assert_eq!(d_updater, tauri.updater);
assert_eq!(
d_path,
AppUrl::Url(WindowUrl::External(
Url::parse("http://localhost:8080").unwrap()
))
);
assert_eq!(d_windows, tauri.windows);
}
}