use http::response::Builder;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use semver::Version;
use serde::{
de::{Deserializer, Error as DeError, Visitor},
Deserialize, Serialize, Serializer,
};
use serde_json::Value as JsonValue;
use serde_untagged::UntaggedEnumVisitor;
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;
use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
pub use self::parse::parse;
fn default_true() -> bool {
true
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum WebviewUrl {
External(Url),
App(PathBuf),
CustomProtocol(Url),
}
impl<'de> Deserialize<'de> for WebviewUrl {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WebviewUrlDeserializer {
Url(Url),
Path(PathBuf),
}
match WebviewUrlDeserializer::deserialize(deserializer)? {
WebviewUrlDeserializer::Url(u) => {
if u.scheme() == "https" || u.scheme() == "http" {
Ok(Self::External(u))
} else {
Ok(Self::CustomProtocol(u))
}
}
WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
}
}
}
impl fmt::Display for WebviewUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
Self::App(path) => write!(f, "{}", path.display()),
}
}
}
impl Default for WebviewUrl {
fn default() -> Self {
Self::App("index.html".into())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
pub enum BundleType {
Deb,
Rpm,
AppImage,
Msi,
Nsis,
App,
Dmg,
}
impl BundleType {
fn all() -> &'static [Self] {
&[
BundleType::Deb,
BundleType::Rpm,
BundleType::AppImage,
BundleType::Msi,
BundleType::Nsis,
BundleType::App,
BundleType::Dmg,
]
}
}
impl Display for BundleType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Deb => "deb",
Self::Rpm => "rpm",
Self::AppImage => "appimage",
Self::Msi => "msi",
Self::Nsis => "nsis",
Self::App => "app",
Self::Dmg => "dmg",
}
)
}
}
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),
"rpm" => Ok(Self::Rpm),
"appimage" => Ok(Self::AppImage),
"msi" => Ok(Self::Msi),
"nsis" => Ok(Self::Nsis),
"app" => Ok(Self::App),
"dmg" => Ok(Self::Dmg),
_ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BundleTarget {
All,
List(Vec<BundleType>),
One(BundleType),
}
#[cfg(feature = "schema")]
impl schemars::JsonSchema for BundleTarget {
fn schema_name() -> std::string::String {
"BundleTarget".to_owned()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let any_of = vec![
schemars::schema::SchemaObject {
const_value: Some("all".into()),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("Bundle all targets.".to_owned()),
..Default::default()
})),
..Default::default()
}
.into(),
schemars::_private::metadata::add_description(
gen.subschema_for::<Vec<BundleType>>(),
"A list of bundle targets.",
),
schemars::_private::metadata::add_description(
gen.subschema_for::<BundleType>(),
"A single bundle target.",
),
];
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 Default for BundleTarget {
fn default() -> Self {
Self::All
}
}
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}, expected one of `all`, {}",
BundleType::all()
.iter()
.map(|b| format!("`{b}`"))
.collect::<Vec<_>>()
.join(", ")
))),
BundleTargetInner::List(l) => Ok(Self::List(l)),
BundleTargetInner::One(t) => Ok(Self::One(t)),
}
}
}
impl BundleTarget {
#[allow(dead_code)]
pub fn to_vec(&self) -> Vec<BundleType> {
match self {
Self::All => BundleType::all().to_vec(),
Self::List(list) => list.clone(),
Self::One(i) => vec![i.clone()],
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AppImageConfig {
#[serde(default, alias = "bundle-media-framework")]
pub bundle_media_framework: bool,
#[serde(default)]
pub files: HashMap<PathBuf, PathBuf>,
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DebConfig {
pub depends: Option<Vec<String>>,
pub recommends: Option<Vec<String>>,
pub provides: Option<Vec<String>>,
pub conflicts: Option<Vec<String>>,
pub replaces: Option<Vec<String>>,
#[serde(default)]
pub files: HashMap<PathBuf, PathBuf>,
pub section: Option<String>,
pub priority: Option<String>,
pub changelog: Option<PathBuf>,
#[serde(alias = "desktop-template")]
pub desktop_template: Option<PathBuf>,
#[serde(alias = "pre-install-script")]
pub pre_install_script: Option<PathBuf>,
#[serde(alias = "post-install-script")]
pub post_install_script: Option<PathBuf>,
#[serde(alias = "pre-remove-script")]
pub pre_remove_script: Option<PathBuf>,
#[serde(alias = "post-remove-script")]
pub post_remove_script: Option<PathBuf>,
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct LinuxConfig {
#[serde(default)]
pub appimage: AppImageConfig,
#[serde(default)]
pub deb: DebConfig,
#[serde(default)]
pub rpm: RpmConfig,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
#[non_exhaustive]
pub enum RpmCompression {
Gzip {
level: u32,
},
Zstd {
level: i32,
},
Xz {
level: u32,
},
Bzip2 {
level: u32,
},
None,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RpmConfig {
pub depends: Option<Vec<String>>,
pub recommends: Option<Vec<String>>,
pub provides: Option<Vec<String>>,
pub conflicts: Option<Vec<String>>,
pub obsoletes: Option<Vec<String>>,
#[serde(default = "default_release")]
pub release: String,
#[serde(default)]
pub epoch: u32,
#[serde(default)]
pub files: HashMap<PathBuf, PathBuf>,
#[serde(alias = "desktop-template")]
pub desktop_template: Option<PathBuf>,
#[serde(alias = "pre-install-script")]
pub pre_install_script: Option<PathBuf>,
#[serde(alias = "post-install-script")]
pub post_install_script: Option<PathBuf>,
#[serde(alias = "pre-remove-script")]
pub pre_remove_script: Option<PathBuf>,
#[serde(alias = "post-remove-script")]
pub post_remove_script: Option<PathBuf>,
pub compression: Option<RpmCompression>,
}
impl Default for RpmConfig {
fn default() -> Self {
Self {
depends: None,
recommends: None,
provides: None,
conflicts: None,
obsoletes: None,
release: default_release(),
epoch: 0,
files: Default::default(),
desktop_template: None,
pre_install_script: None,
post_install_script: None,
pre_remove_script: None,
post_remove_script: None,
compression: None,
}
}
}
fn default_release() -> String {
"1".into()
}
#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Position {
pub x: u32,
pub y: u32,
}
#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Size {
pub width: u32,
pub height: u32,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DmgConfig {
pub background: Option<PathBuf>,
pub window_position: Option<Position>,
#[serde(default = "dmg_window_size", alias = "window-size")]
pub window_size: Size,
#[serde(default = "dmg_app_position", alias = "app-position")]
pub app_position: Position,
#[serde(
default = "dmg_application_folder_position",
alias = "application-folder-position"
)]
pub application_folder_position: Position,
}
impl Default for DmgConfig {
fn default() -> Self {
Self {
background: None,
window_position: None,
window_size: dmg_window_size(),
app_position: dmg_app_position(),
application_folder_position: dmg_application_folder_position(),
}
}
}
fn dmg_window_size() -> Size {
Size {
width: 660,
height: 400,
}
}
fn dmg_app_position() -> Position {
Position { x: 180, y: 170 }
}
fn dmg_application_folder_position() -> Position {
Position { x: 480, y: 170 }
}
fn de_macos_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(macos_minimum_system_version()),
e => Ok(e),
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct MacConfig {
pub frameworks: Option<Vec<String>>,
#[serde(default)]
pub files: HashMap<PathBuf, PathBuf>,
#[serde(
deserialize_with = "de_macos_minimum_system_version",
default = "macos_minimum_system_version",
alias = "minimum-system-version"
)]
pub minimum_system_version: Option<String>,
#[serde(alias = "exception-domain")]
pub exception_domain: Option<String>,
#[serde(alias = "signing-identity")]
pub signing_identity: Option<String>,
#[serde(alias = "hardened-runtime", default = "default_true")]
pub hardened_runtime: bool,
#[serde(alias = "provider-short-name")]
pub provider_short_name: Option<String>,
pub entitlements: Option<String>,
#[serde(default)]
pub dmg: DmgConfig,
}
impl Default for MacConfig {
fn default() -> Self {
Self {
frameworks: None,
files: HashMap::new(),
minimum_system_version: macos_minimum_system_version(),
exception_domain: None,
signing_identity: None,
hardened_runtime: true,
provider_short_name: None,
entitlements: None,
dmg: Default::default(),
}
}
}
fn macos_minimum_system_version() -> Option<String> {
Some("10.13".into())
}
fn ios_minimum_system_version() -> String {
"13.0".into()
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(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(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(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WixConfig {
pub version: Option<String>,
#[serde(alias = "upgrade-code")]
pub upgrade_code: Option<uuid::Uuid>,
#[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 = "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(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum NsisCompression {
Zlib,
Bzip2,
Lzma,
None,
}
impl Default for NsisCompression {
fn default() -> Self {
Self::Lzma
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum NSISInstallerMode {
CurrentUser,
PerMachine,
Both,
}
impl Default for NSISInstallerMode {
fn default() -> Self {
Self::CurrentUser
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct NsisConfig {
pub template: 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,
#[serde(default)]
pub compression: NsisCompression,
#[serde(alias = "start-menu-folder")]
pub start_menu_folder: Option<String>,
#[serde(alias = "installer-hooks")]
pub installer_hooks: Option<PathBuf>,
#[serde(alias = "minimum-webview2-version")]
pub minimum_webview2_version: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(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(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum CustomSignCommandConfig {
Command(String),
CommandWithOptions {
cmd: String,
args: Vec<String>,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(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(default = "default_true", alias = "allow-downgrades")]
pub allow_downgrades: bool,
pub wix: Option<WixConfig>,
pub nsis: Option<NsisConfig>,
#[serde(alias = "sign-command")]
pub sign_command: Option<CustomSignCommandConfig>,
}
impl Default for WindowsConfig {
fn default() -> Self {
Self {
digest_algorithm: None,
certificate_thumbprint: None,
timestamp_url: None,
tsp: false,
webview_install_mode: Default::default(),
allow_downgrades: true,
wix: None,
nsis: None,
sign_command: None,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum BundleTypeRole {
#[default]
Editor,
Viewer,
Shell,
QLGenerator,
None,
}
impl Display for BundleTypeRole {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Editor => write!(f, "Editor"),
Self::Viewer => write!(f, "Viewer"),
Self::Shell => write!(f, "Shell"),
Self::QLGenerator => write!(f, "QLGenerator"),
Self::None => write!(f, "None"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct AssociationExt(pub String);
impl fmt::Display for AssociationExt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'d> serde::Deserialize<'d> for AssociationExt {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
let ext = String::deserialize(deserializer)?;
if let Some(ext) = ext.strip_prefix('.') {
Ok(AssociationExt(ext.into()))
} else {
Ok(AssociationExt(ext))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FileAssociation {
pub ext: Vec<AssociationExt>,
pub name: Option<String>,
pub description: Option<String>,
#[serde(default)]
pub role: BundleTypeRole,
#[serde(alias = "mime-type")]
pub mime_type: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DeepLinkProtocol {
pub schemes: Vec<String>,
pub name: Option<String>,
#[serde(default)]
pub role: BundleTypeRole,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum BundleResources {
List(Vec<String>),
Map(HashMap<String, String>),
}
impl BundleResources {
pub fn push(&mut self, path: impl Into<String>) {
match self {
Self::List(l) => l.push(path.into()),
Self::Map(l) => {
let path = path.into();
l.insert(path.clone(), path);
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum Updater {
String(V1Compatible),
Bool(bool),
}
impl Default for Updater {
fn default() -> Self {
Self::Bool(false)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum V1Compatible {
V1Compatible,
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BundleConfig {
#[serde(default)]
pub active: bool,
#[serde(default)]
pub targets: BundleTarget,
#[serde(default)]
pub create_updater_artifacts: Updater,
pub publisher: Option<String>,
pub homepage: Option<String>,
#[serde(default)]
pub icon: Vec<String>,
pub resources: Option<BundleResources>,
pub copyright: Option<String>,
pub license: Option<String>,
#[serde(alias = "license-file")]
pub license_file: Option<PathBuf>,
pub category: Option<String>,
pub file_associations: Option<Vec<FileAssociation>>,
#[serde(alias = "short-description")]
pub short_description: Option<String>,
#[serde(alias = "long-description")]
pub long_description: Option<String>,
#[serde(default, alias = "use-local-tools-dir")]
pub use_local_tools_dir: bool,
#[serde(alias = "external-bin")]
pub external_bin: Option<Vec<String>>,
#[serde(default)]
pub windows: WindowsConfig,
#[serde(default)]
pub linux: LinuxConfig,
#[serde(rename = "macOS", alias = "macos", default)]
pub macos: MacConfig,
#[serde(rename = "iOS", alias = "ios", default)]
pub ios: IosConfig,
#[serde(default)]
pub android: AndroidConfig,
}
#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Color(pub u8, pub u8, pub u8, pub u8);
impl From<Color> for (u8, u8, u8, u8) {
fn from(value: Color) -> Self {
(value.0, value.1, value.2, value.3)
}
}
impl From<Color> for (u8, u8, u8) {
fn from(value: Color) -> Self {
(value.0, value.1, value.2)
}
}
impl From<(u8, u8, u8, u8)> for Color {
fn from(value: (u8, u8, u8, u8)) -> Self {
Color(value.0, value.1, value.2, value.3)
}
}
impl From<(u8, u8, u8)> for Color {
fn from(value: (u8, u8, u8)) -> Self {
Color(value.0, value.1, value.2, 255)
}
}
impl From<Color> for [u8; 4] {
fn from(value: Color) -> Self {
[value.0, value.1, value.2, value.3]
}
}
impl From<Color> for [u8; 3] {
fn from(value: Color) -> Self {
[value.0, value.1, value.2]
}
}
impl From<[u8; 4]> for Color {
fn from(value: [u8; 4]) -> Self {
Color(value[0], value[1], value[2], value[3])
}
}
impl From<[u8; 3]> for Color {
fn from(value: [u8; 3]) -> Self {
Color(value[0], value[1], value[2], 255)
}
}
impl FromStr for Color {
type Err = String;
fn from_str(mut color: &str) -> Result<Self, Self::Err> {
color = color.trim().strip_prefix('#').unwrap_or(color);
let color = match color.len() {
3 => color.chars()
.flat_map(|c| std::iter::repeat(c).take(2))
.chain(std::iter::repeat('f').take(2))
.collect(),
6 => format!("{color}FF"),
8 => color.to_string(),
_ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
};
let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
Ok(Color(r, g, b, a))
}
}
fn default_alpha() -> u8 {
255
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
enum InnerColor {
String(String),
Rgb((u8, u8, u8)),
Rgba((u8, u8, u8, u8)),
RgbaObject {
red: u8,
green: u8,
blue: u8,
#[serde(default = "default_alpha")]
alpha: u8,
},
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let color = InnerColor::deserialize(deserializer)?;
let color = match color {
InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
InnerColor::Rgba(rgb) => rgb.into(),
InnerColor::RgbaObject {
red,
green,
blue,
alpha,
} => Color(red, green, blue, alpha),
};
Ok(color)
}
}
#[cfg(feature = "schema")]
impl schemars::JsonSchema for Color {
fn schema_name() -> String {
"Color".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let mut schema = schemars::schema_for!(InnerColor).schema;
schema.metadata = None;
let any_of = schema.subschemas().any_of.as_mut().unwrap();
let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
unreachable!()
};
str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
schema.into()
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WindowEffectsConfig {
pub effects: Vec<WindowEffect>,
pub state: Option<WindowEffectState>,
pub radius: Option<f64>,
pub color: Option<Color>,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct WindowConfig {
#[serde(default = "default_window_label")]
pub label: String,
#[serde(default = "default_true")]
pub create: bool,
#[serde(default)]
pub url: WebviewUrl,
#[serde(alias = "user-agent")]
pub user_agent: Option<String>,
#[serde(default = "default_true", alias = "drag-drop-enabled")]
pub drag_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-bottom")]
pub always_on_bottom: bool,
#[serde(default, alias = "always-on-top")]
pub always_on_top: bool,
#[serde(default, alias = "visible-on-all-workspaces")]
pub visible_on_all_workspaces: bool,
#[serde(default, alias = "content-protected")]
pub content_protected: bool,
#[serde(default, alias = "skip-taskbar")]
pub skip_taskbar: bool,
pub window_classname: Option<String>,
pub theme: Option<crate::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>,
#[serde(default = "default_true")]
pub shadow: bool,
#[serde(default, alias = "window-effects")]
pub window_effects: Option<WindowEffectsConfig>,
#[serde(default)]
pub incognito: bool,
pub parent: Option<String>,
#[serde(alias = "proxy-url")]
pub proxy_url: Option<Url>,
#[serde(default, alias = "zoom-hotkeys-enabled")]
pub zoom_hotkeys_enabled: bool,
#[serde(default, alias = "browser-extensions-enabled")]
pub browser_extensions_enabled: bool,
#[serde(default, alias = "use-https-scheme")]
pub use_https_scheme: bool,
pub devtools: Option<bool>,
#[serde(alias = "background-color")]
pub background_color: Option<Color>,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
label: default_window_label(),
url: WebviewUrl::default(),
create: true,
user_agent: None,
drag_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_bottom: false,
always_on_top: false,
visible_on_all_workspaces: false,
content_protected: false,
skip_taskbar: false,
window_classname: None,
theme: None,
title_bar_style: Default::default(),
hidden_title: false,
accept_first_mouse: false,
tabbing_identifier: None,
additional_browser_args: None,
shadow: true,
window_effects: None,
incognito: false,
parent: None,
proxy_url: None,
zoom_hotkeys_enabled: false,
browser_extensions_enabled: false,
use_https_scheme: false,
devtools: None,
background_color: 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(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());
}
}
}
pub fn extend(&mut self, sources: Vec<String>) {
for s in sources {
self.push(s);
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(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(JsonSchema))]
pub enum DisabledCspModificationKind {
Flag(bool),
List(Vec<String>),
}
impl DisabledCspModificationKind {
pub fn can_modify(&self, directive: &str) -> bool {
match self {
Self::Flag(f) => !f,
Self::List(l) => !l.contains(&directive.into()),
}
}
}
impl Default for DisabledCspModificationKind {
fn default() -> Self {
Self::Flag(false)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum FsScope {
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 FsScope {
fn default() -> Self {
Self::AllowedPaths(Vec::new())
}
}
impl FsScope {
pub fn allowed_paths(&self) -> &Vec<PathBuf> {
match self {
Self::AllowedPaths(p) => p,
Self::Scope { allow, .. } => allow,
}
}
pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
match self {
Self::AllowedPaths(_) => None,
Self::Scope { deny, .. } => Some(deny),
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AssetProtocolConfig {
#[serde(default)]
pub scope: FsScope,
#[serde(default)]
pub enable: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", untagged)]
pub enum HeaderSource {
Inline(String),
List(Vec<String>),
Map(HashMap<String, String>),
}
impl Display for HeaderSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Inline(s) => write!(f, "{s}"),
Self::List(l) => write!(f, "{}", l.join(", ")),
Self::Map(m) => {
let len = m.len();
let mut i = 0;
for (key, value) in m {
write!(f, "{} {}", key, value)?;
i += 1;
if i != len {
write!(f, "; ")?;
}
}
Ok(())
}
}
}
}
pub trait HeaderAddition {
fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
}
impl HeaderAddition for Builder {
fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
if let Some(headers) = headers {
if let Some(value) = &headers.access_control_allow_credentials {
self = self.header("Access-Control-Allow-Credentials", value.to_string());
};
if let Some(value) = &headers.access_control_allow_headers {
self = self.header("Access-Control-Allow-Headers", value.to_string());
};
if let Some(value) = &headers.access_control_allow_methods {
self = self.header("Access-Control-Allow-Methods", value.to_string());
};
if let Some(value) = &headers.access_control_expose_headers {
self = self.header("Access-Control-Expose-Headers", value.to_string());
};
if let Some(value) = &headers.access_control_max_age {
self = self.header("Access-Control-Max-Age", value.to_string());
};
if let Some(value) = &headers.cross_origin_embedder_policy {
self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
};
if let Some(value) = &headers.cross_origin_opener_policy {
self = self.header("Cross-Origin-Opener-Policy", value.to_string());
};
if let Some(value) = &headers.cross_origin_resource_policy {
self = self.header("Cross-Origin-Resource-Policy", value.to_string());
};
if let Some(value) = &headers.permissions_policy {
self = self.header("Permission-Policy", value.to_string());
};
if let Some(value) = &headers.timing_allow_origin {
self = self.header("Timing-Allow-Origin", value.to_string());
};
if let Some(value) = &headers.x_content_type_options {
self = self.header("X-Content-Type-Options", value.to_string());
};
if let Some(value) = &headers.tauri_custom_header {
self = self.header("Tauri-Custom-Header", value.to_string());
};
}
self
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct HeaderConfig {
#[serde(rename = "Access-Control-Allow-Credentials")]
pub access_control_allow_credentials: Option<HeaderSource>,
#[serde(rename = "Access-Control-Allow-Headers")]
pub access_control_allow_headers: Option<HeaderSource>,
#[serde(rename = "Access-Control-Allow-Methods")]
pub access_control_allow_methods: Option<HeaderSource>,
#[serde(rename = "Access-Control-Expose-Headers")]
pub access_control_expose_headers: Option<HeaderSource>,
#[serde(rename = "Access-Control-Max-Age")]
pub access_control_max_age: Option<HeaderSource>,
#[serde(rename = "Cross-Origin-Embedder-Policy")]
pub cross_origin_embedder_policy: Option<HeaderSource>,
#[serde(rename = "Cross-Origin-Opener-Policy")]
pub cross_origin_opener_policy: Option<HeaderSource>,
#[serde(rename = "Cross-Origin-Resource-Policy")]
pub cross_origin_resource_policy: Option<HeaderSource>,
#[serde(rename = "Permissions-Policy")]
pub permissions_policy: Option<HeaderSource>,
#[serde(rename = "Timing-Allow-Origin")]
pub timing_allow_origin: Option<HeaderSource>,
#[serde(rename = "X-Content-Type-Options")]
pub x_content_type_options: Option<HeaderSource>,
#[serde(rename = "Tauri-Custom-Header")]
pub tauri_custom_header: Option<HeaderSource>,
}
impl HeaderConfig {
pub fn new() -> Self {
HeaderConfig {
access_control_allow_credentials: None,
access_control_allow_methods: None,
access_control_allow_headers: None,
access_control_expose_headers: None,
access_control_max_age: None,
cross_origin_embedder_policy: None,
cross_origin_opener_policy: None,
cross_origin_resource_policy: None,
permissions_policy: None,
timing_allow_origin: None,
x_content_type_options: None,
tauri_custom_header: None,
}
}
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(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 = "asset-protocol")]
pub asset_protocol: AssetProtocolConfig,
#[serde(default)]
pub pattern: PatternKind,
#[serde(default)]
pub capabilities: Vec<CapabilityEntry>,
#[serde(default)]
pub headers: Option<HeaderConfig>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum CapabilityEntry {
Inlined(Capability),
Reference(String),
}
impl<'de> Deserialize<'de> for CapabilityEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
UntaggedEnumVisitor::new()
.string(|string| Ok(Self::Reference(string.to_owned())))
.map(|map| map.deserialize::<Capability>().map(Self::Inlined))
.deserialize(deserializer)
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", tag = "use", content = "options")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum PatternKind {
Brownfield,
Isolation {
dir: PathBuf,
},
}
impl Default for PatternKind {
fn default() -> Self {
Self::Brownfield
}
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AppConfig {
#[serde(default)]
pub windows: Vec<WindowConfig>,
#[serde(default)]
pub security: SecurityConfig,
#[serde(alias = "tray-icon")]
pub tray_icon: Option<TrayIconConfig>,
#[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
pub macos_private_api: bool,
#[serde(default, alias = "with-global-tauri")]
pub with_global_tauri: bool,
#[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
pub enable_gtk_app_id: bool,
}
impl AppConfig {
pub fn all_features() -> Vec<&'static str> {
vec![
"tray-icon",
"macos-private-api",
"protocol-asset",
"isolation",
]
}
pub fn features(&self) -> Vec<&str> {
let mut features = Vec::new();
if self.tray_icon.is_some() {
features.push("tray-icon");
}
if self.macos_private_api {
features.push("macos-private-api");
}
if self.security.asset_protocol.enable {
features.push("protocol-asset");
}
if let PatternKind::Isolation { .. } = self.security.pattern {
features.push("isolation");
}
features.sort_unstable();
features
}
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TrayIconConfig {
pub id: Option<String>,
#[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>,
pub tooltip: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct IosConfig {
pub template: Option<PathBuf>,
pub frameworks: Option<Vec<String>>,
#[serde(alias = "development-team")]
pub development_team: Option<String>,
#[serde(
alias = "minimum-system-version",
default = "ios_minimum_system_version"
)]
pub minimum_system_version: String,
}
impl Default for IosConfig {
fn default() -> Self {
Self {
template: None,
frameworks: None,
development_team: None,
minimum_system_version: ios_minimum_system_version(),
}
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AndroidConfig {
#[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
pub min_sdk_version: u32,
#[serde(alias = "version-code")]
#[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
pub version_code: Option<u32>,
}
impl Default for AndroidConfig {
fn default() -> Self {
Self {
min_sdk_version: default_min_sdk_version(),
version_code: None,
}
}
}
fn default_min_sdk_version() -> u32 {
24
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum FrontendDist {
Url(Url),
Directory(PathBuf),
Files(Vec<PathBuf>),
}
impl std::fmt::Display for FrontendDist {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Url(url) => write!(f, "{url}"),
Self::Directory(p) => write!(f, "{}", p.display()),
Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(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(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, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BuildConfig {
pub runner: Option<String>,
#[serde(alias = "dev-url")]
pub dev_url: Option<Url>,
#[serde(alias = "frontend-dist")]
pub frontend_dist: Option<FrontendDist>,
#[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>>,
}
#[derive(Debug, PartialEq, Eq)]
struct PackageVersion(String);
impl<'d> serde::Deserialize<'d> for PackageVersion {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, 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 {})
}
}
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))
}
#[skip_serializing_none]
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Config {
#[serde(rename = "$schema")]
pub schema: Option<String>,
#[serde(alias = "product-name")]
#[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
pub product_name: Option<String>,
#[serde(alias = "main-binary-name")]
pub main_binary_name: Option<String>,
#[serde(deserialize_with = "version_deserializer", default)]
pub version: Option<String>,
pub identifier: String,
#[serde(default)]
pub app: AppConfig,
#[serde(default = "default_build")]
pub build: BuildConfig,
#[serde(default)]
pub bundle: BundleConfig,
#[serde(default)]
pub plugins: PluginConfig,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PluginConfig(pub HashMap<String, JsonValue>);
fn default_build() -> BuildConfig {
BuildConfig {
runner: None,
dev_url: None,
frontend_dist: None,
before_dev_command: None,
before_build_command: None,
before_bundle_command: None,
features: None,
}
}
#[cfg(feature = "build")]
mod build {
use super::*;
use crate::{literal_struct, tokens::*};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use std::convert::identity;
impl ToTokens for WebviewUrl {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::WebviewUrl };
tokens.append_all(match self {
Self::App(path) => {
let path = path_buf_lit(path);
quote! { #prefix::App(#path) }
}
Self::External(url) => {
let url = url_lit(url);
quote! { #prefix::External(#url) }
}
Self::CustomProtocol(url) => {
let url = url_lit(url);
quote! { #prefix::CustomProtocol(#url) }
}
})
}
}
impl ToTokens for crate::Theme {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::Theme };
tokens.append_all(match self {
Self::Light => quote! { #prefix::Light },
Self::Dark => quote! { #prefix::Dark },
})
}
}
impl ToTokens for Color {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Color(r, g, b, a) = self;
tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
}
}
impl ToTokens for WindowEffectsConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let effects = vec_lit(self.effects.clone(), |d| d);
let state = opt_lit(self.state.as_ref());
let radius = opt_lit(self.radius.as_ref());
let color = opt_lit(self.color.as_ref());
literal_struct!(
tokens,
::tauri::utils::config::WindowEffectsConfig,
effects,
state,
radius,
color
)
}
}
impl ToTokens for crate::TitleBarStyle {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::TitleBarStyle };
tokens.append_all(match self {
Self::Visible => quote! { #prefix::Visible },
Self::Transparent => quote! { #prefix::Transparent },
Self::Overlay => quote! { #prefix::Overlay },
})
}
}
impl ToTokens for crate::WindowEffect {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::WindowEffect };
#[allow(deprecated)]
tokens.append_all(match self {
WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
WindowEffect::Light => quote! { #prefix::Light},
WindowEffect::Dark => quote! { #prefix::Dark},
WindowEffect::MediumLight => quote! { #prefix::MediumLight},
WindowEffect::UltraDark => quote! { #prefix::UltraDark},
WindowEffect::Titlebar => quote! { #prefix::Titlebar},
WindowEffect::Selection => quote! { #prefix::Selection},
WindowEffect::Menu => quote! { #prefix::Menu},
WindowEffect::Popover => quote! { #prefix::Popover},
WindowEffect::Sidebar => quote! { #prefix::Sidebar},
WindowEffect::HeaderView => quote! { #prefix::HeaderView},
WindowEffect::Sheet => quote! { #prefix::Sheet},
WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
WindowEffect::HudWindow => quote! { #prefix::HudWindow},
WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
WindowEffect::Tooltip => quote! { #prefix::Tooltip},
WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
WindowEffect::Mica => quote! { #prefix::Mica},
WindowEffect::MicaDark => quote! { #prefix::MicaDark},
WindowEffect::MicaLight => quote! { #prefix::MicaLight},
WindowEffect::Blur => quote! { #prefix::Blur},
WindowEffect::Acrylic => quote! { #prefix::Acrylic},
WindowEffect::Tabbed => quote! { #prefix::Tabbed },
WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
})
}
}
impl ToTokens for crate::WindowEffectState {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::WindowEffectState };
#[allow(deprecated)]
tokens.append_all(match self {
WindowEffectState::Active => quote! { #prefix::Active},
WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
WindowEffectState::Inactive => quote! { #prefix::Inactive},
})
}
}
impl ToTokens for WindowConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let label = str_lit(&self.label);
let create = &self.create;
let url = &self.url;
let user_agent = opt_str_lit(self.user_agent.as_ref());
let drag_drop_enabled = self.drag_drop_enabled;
let center = self.center;
let x = opt_lit(self.x.as_ref());
let y = opt_lit(self.y.as_ref());
let width = self.width;
let height = self.height;
let min_width = opt_lit(self.min_width.as_ref());
let min_height = opt_lit(self.min_height.as_ref());
let max_width = opt_lit(self.max_width.as_ref());
let max_height = opt_lit(self.max_height.as_ref());
let resizable = self.resizable;
let maximizable = self.maximizable;
let minimizable = self.minimizable;
let closable = self.closable;
let title = str_lit(&self.title);
let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
let fullscreen = self.fullscreen;
let focus = self.focus;
let transparent = self.transparent;
let maximized = self.maximized;
let visible = self.visible;
let decorations = self.decorations;
let always_on_bottom = self.always_on_bottom;
let always_on_top = self.always_on_top;
let visible_on_all_workspaces = self.visible_on_all_workspaces;
let content_protected = self.content_protected;
let skip_taskbar = self.skip_taskbar;
let window_classname = opt_str_lit(self.window_classname.as_ref());
let theme = opt_lit(self.theme.as_ref());
let title_bar_style = &self.title_bar_style;
let hidden_title = self.hidden_title;
let accept_first_mouse = self.accept_first_mouse;
let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
let shadow = self.shadow;
let window_effects = opt_lit(self.window_effects.as_ref());
let incognito = self.incognito;
let parent = opt_str_lit(self.parent.as_ref());
let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
let browser_extensions_enabled = self.browser_extensions_enabled;
let use_https_scheme = self.use_https_scheme;
let devtools = opt_lit(self.devtools.as_ref());
let background_color = opt_lit(self.background_color.as_ref());
literal_struct!(
tokens,
::tauri::utils::config::WindowConfig,
label,
url,
create,
user_agent,
drag_drop_enabled,
center,
x,
y,
width,
height,
min_width,
min_height,
max_width,
max_height,
resizable,
maximizable,
minimizable,
closable,
title,
proxy_url,
fullscreen,
focus,
transparent,
maximized,
visible,
decorations,
always_on_bottom,
always_on_top,
visible_on_all_workspaces,
content_protected,
skip_taskbar,
window_classname,
theme,
title_bar_style,
hidden_title,
accept_first_mouse,
tabbing_identifier,
additional_browser_args,
shadow,
window_effects,
incognito,
parent,
zoom_hotkeys_enabled,
browser_extensions_enabled,
use_https_scheme,
devtools,
background_color
);
}
}
impl ToTokens for PatternKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::PatternKind };
tokens.append_all(match self {
Self::Brownfield => quote! { #prefix::Brownfield },
#[cfg(not(feature = "isolation"))]
Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
#[cfg(feature = "isolation")]
Self::Isolation { dir } => {
let dir = path_buf_lit(dir);
quote! { #prefix::Isolation { dir: #dir } }
}
})
}
}
impl ToTokens for WebviewInstallMode {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
tokens.append_all(match self {
Self::Skip => quote! { #prefix::Skip },
Self::DownloadBootstrapper { silent } => {
quote! { #prefix::DownloadBootstrapper { silent: #silent } }
}
Self::EmbedBootstrapper { silent } => {
quote! { #prefix::EmbedBootstrapper { silent: #silent } }
}
Self::OfflineInstaller { silent } => {
quote! { #prefix::OfflineInstaller { silent: #silent } }
}
Self::FixedRuntime { path } => {
let path = path_buf_lit(path);
quote! { #prefix::FixedRuntime { path: #path } }
}
})
}
}
impl ToTokens for WindowsConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let webview_install_mode = &self.webview_install_mode;
tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
webview_install_mode: #webview_install_mode,
..Default::default()
}})
}
}
impl ToTokens for BundleConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let publisher = quote!(None);
let homepage = quote!(None);
let icon = vec_lit(&self.icon, str_lit);
let active = self.active;
let targets = quote!(Default::default());
let create_updater_artifacts = quote!(Default::default());
let resources = quote!(None);
let copyright = quote!(None);
let category = quote!(None);
let file_associations = quote!(None);
let short_description = quote!(None);
let long_description = quote!(None);
let use_local_tools_dir = self.use_local_tools_dir;
let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
let windows = &self.windows;
let license = opt_str_lit(self.license.as_ref());
let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
let linux = quote!(Default::default());
let macos = quote!(Default::default());
let ios = quote!(Default::default());
let android = quote!(Default::default());
literal_struct!(
tokens,
::tauri::utils::config::BundleConfig,
active,
publisher,
homepage,
icon,
targets,
create_updater_artifacts,
resources,
copyright,
category,
license,
license_file,
file_associations,
short_description,
long_description,
use_local_tools_dir,
external_bin,
windows,
linux,
macos,
ios,
android
);
}
}
impl ToTokens for FrontendDist {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::FrontendDist };
tokens.append_all(match self {
Self::Url(url) => {
let url = url_lit(url);
quote! { #prefix::Url(#url) }
}
Self::Directory(path) => {
let path = path_buf_lit(path);
quote! { #prefix::Directory(#path) }
}
Self::Files(files) => {
let files = vec_lit(files, path_buf_lit);
quote! { #prefix::Files(#files) }
}
})
}
}
impl ToTokens for BuildConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
let frontend_dist = opt_lit(self.frontend_dist.as_ref());
let runner = quote!(None);
let before_dev_command = quote!(None);
let before_build_command = quote!(None);
let before_bundle_command = quote!(None);
let features = quote!(None);
literal_struct!(
tokens,
::tauri::utils::config::BuildConfig,
runner,
dev_url,
frontend_dist,
before_dev_command,
before_build_command,
before_bundle_command,
features
);
}
}
impl ToTokens for CspDirectiveSources {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
tokens.append_all(match self {
Self::Inline(sources) => {
let sources = sources.as_str();
quote!(#prefix::Inline(#sources.into()))
}
Self::List(list) => {
let list = vec_lit(list, str_lit);
quote!(#prefix::List(#list))
}
})
}
}
impl ToTokens for Csp {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::Csp };
tokens.append_all(match self {
Self::Policy(policy) => {
let policy = policy.as_str();
quote!(#prefix::Policy(#policy.into()))
}
Self::DirectiveMap(list) => {
let map = map_lit(
quote! { ::std::collections::HashMap },
list,
str_lit,
identity,
);
quote!(#prefix::DirectiveMap(#map))
}
})
}
}
impl ToTokens for DisabledCspModificationKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
tokens.append_all(match self {
Self::Flag(flag) => {
quote! { #prefix::Flag(#flag) }
}
Self::List(directives) => {
let directives = vec_lit(directives, str_lit);
quote! { #prefix::List(#directives) }
}
});
}
}
impl ToTokens for CapabilityEntry {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
tokens.append_all(match self {
Self::Inlined(capability) => {
quote! { #prefix::Inlined(#capability) }
}
Self::Reference(id) => {
let id = str_lit(id);
quote! { #prefix::Reference(#id) }
}
});
}
}
impl ToTokens for HeaderSource {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::HeaderSource };
tokens.append_all(match self {
Self::Inline(s) => {
let line = s.as_str();
quote!(#prefix::Inline(#line.into()))
}
Self::List(l) => {
let list = vec_lit(l, str_lit);
quote!(#prefix::List(#list))
}
Self::Map(m) => {
let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
quote!(#prefix::Map(#map))
}
})
}
}
impl ToTokens for HeaderConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let access_control_allow_credentials =
opt_lit(self.access_control_allow_credentials.as_ref());
let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
let permissions_policy = opt_lit(self.permissions_policy.as_ref());
let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
literal_struct!(
tokens,
::tauri::utils::config::HeaderConfig,
access_control_allow_credentials,
access_control_allow_headers,
access_control_allow_methods,
access_control_expose_headers,
access_control_max_age,
cross_origin_embedder_policy,
cross_origin_opener_policy,
cross_origin_resource_policy,
permissions_policy,
timing_allow_origin,
x_content_type_options,
tauri_custom_header
);
}
}
impl ToTokens for SecurityConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let csp = opt_lit(self.csp.as_ref());
let dev_csp = opt_lit(self.dev_csp.as_ref());
let freeze_prototype = self.freeze_prototype;
let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
let asset_protocol = &self.asset_protocol;
let pattern = &self.pattern;
let capabilities = vec_lit(&self.capabilities, identity);
let headers = opt_lit(self.headers.as_ref());
literal_struct!(
tokens,
::tauri::utils::config::SecurityConfig,
csp,
dev_csp,
freeze_prototype,
dangerous_disable_asset_csp_modification,
asset_protocol,
pattern,
capabilities,
headers
);
}
}
impl ToTokens for TrayIconConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let id = opt_str_lit(self.id.as_ref());
let icon_as_template = self.icon_as_template;
let menu_on_left_click = self.menu_on_left_click;
let icon_path = path_buf_lit(&self.icon_path);
let title = opt_str_lit(self.title.as_ref());
let tooltip = opt_str_lit(self.tooltip.as_ref());
literal_struct!(
tokens,
::tauri::utils::config::TrayIconConfig,
id,
icon_path,
icon_as_template,
menu_on_left_click,
title,
tooltip
);
}
}
impl ToTokens for FsScope {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::FsScope };
tokens.append_all(match self {
Self::AllowedPaths(allow) => {
let allowed_paths = vec_lit(allow, path_buf_lit);
quote! { #prefix::AllowedPaths(#allowed_paths) }
}
Self::Scope { allow, deny , require_literal_leading_dot} => {
let allow = vec_lit(allow, path_buf_lit);
let deny = vec_lit(deny, path_buf_lit);
let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
}
});
}
}
impl ToTokens for AssetProtocolConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let scope = &self.scope;
tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
}
}
impl ToTokens for AppConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let windows = vec_lit(&self.windows, identity);
let security = &self.security;
let tray_icon = opt_lit(self.tray_icon.as_ref());
let macos_private_api = self.macos_private_api;
let with_global_tauri = self.with_global_tauri;
let enable_gtk_app_id = self.enable_gtk_app_id;
literal_struct!(
tokens,
::tauri::utils::config::AppConfig,
windows,
security,
tray_icon,
macos_private_api,
with_global_tauri,
enable_gtk_app_id
);
}
}
impl ToTokens for PluginConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let config = map_lit(
quote! { ::std::collections::HashMap },
&self.0,
str_lit,
json_value_lit,
);
tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
}
}
impl ToTokens for Config {
fn to_tokens(&self, tokens: &mut TokenStream) {
let schema = quote!(None);
let product_name = opt_str_lit(self.product_name.as_ref());
let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
let version = opt_str_lit(self.version.as_ref());
let identifier = str_lit(&self.identifier);
let app = &self.app;
let build = &self.build;
let bundle = &self.bundle;
let plugins = &self.plugins;
literal_struct!(
tokens,
::tauri::utils::config::Config,
schema,
product_name,
main_binary_name,
version,
identifier,
app,
build,
bundle,
plugins
);
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_defaults() {
let a_config = AppConfig::default();
let b_config = BuildConfig::default();
let d_windows: Vec<WindowConfig> = vec![];
let d_bundle = BundleConfig::default();
let app = AppConfig {
windows: vec![],
security: SecurityConfig {
csp: None,
dev_csp: None,
freeze_prototype: false,
dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
asset_protocol: AssetProtocolConfig::default(),
pattern: Default::default(),
capabilities: Vec::new(),
headers: None,
},
tray_icon: None,
macos_private_api: false,
with_global_tauri: false,
enable_gtk_app_id: false,
};
let build = BuildConfig {
runner: None,
dev_url: None,
frontend_dist: None,
before_dev_command: None,
before_build_command: None,
before_bundle_command: None,
features: None,
};
let bundle = BundleConfig {
active: false,
targets: Default::default(),
create_updater_artifacts: Default::default(),
publisher: None,
homepage: None,
icon: Vec::new(),
resources: None,
copyright: None,
category: None,
file_associations: None,
short_description: None,
long_description: None,
use_local_tools_dir: false,
license: None,
license_file: None,
linux: Default::default(),
macos: Default::default(),
external_bin: None,
windows: Default::default(),
ios: Default::default(),
android: Default::default(),
};
assert_eq!(a_config, app);
assert_eq!(b_config, build);
assert_eq!(d_bundle, bundle);
assert_eq!(d_windows, app.windows);
}
#[test]
fn parse_hex_color() {
use super::Color;
assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
}
}