1use semver::Version;
14use serde::{
15 de::{Deserializer, Error as DeError, Visitor},
16 Deserialize, Serialize, Serializer,
17};
18use serde_json::Value as JsonValue;
19use serde_with::skip_serializing_none;
20use url::Url;
21
22use std::{
23 collections::HashMap,
24 fmt::{self, Display},
25 fs::read_to_string,
26 path::PathBuf,
27 str::FromStr,
28};
29
30pub mod parse;
32
33fn default_true() -> bool {
34 true
35}
36
37#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
39#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
40#[serde(untagged)]
41#[non_exhaustive]
42pub enum WindowUrl {
43 External(Url),
45 App(PathBuf),
49}
50
51impl fmt::Display for WindowUrl {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::External(url) => write!(f, "{url}"),
55 Self::App(path) => write!(f, "{}", path.display()),
56 }
57 }
58}
59
60impl Default for WindowUrl {
61 fn default() -> Self {
62 Self::App("index.html".into())
63 }
64}
65
66#[derive(Debug, PartialEq, Eq, Clone)]
68#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
69#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
70pub enum BundleType {
71 Deb,
73 AppImage,
75 Msi,
77 Nsis,
79 App,
81 Dmg,
83 Updater,
85}
86
87impl Display for BundleType {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(
90 f,
91 "{}",
92 match self {
93 Self::Deb => "deb",
94 Self::AppImage => "appimage",
95 Self::Msi => "msi",
96 Self::Nsis => "nsis",
97 Self::App => "app",
98 Self::Dmg => "dmg",
99 Self::Updater => "updater",
100 }
101 )
102 }
103}
104
105impl Serialize for BundleType {
106 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
107 where
108 S: Serializer,
109 {
110 serializer.serialize_str(self.to_string().as_ref())
111 }
112}
113
114impl<'de> Deserialize<'de> for BundleType {
115 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
116 where
117 D: Deserializer<'de>,
118 {
119 let s = String::deserialize(deserializer)?;
120 match s.to_lowercase().as_str() {
121 "deb" => Ok(Self::Deb),
122 "appimage" => Ok(Self::AppImage),
123 "msi" => Ok(Self::Msi),
124 "nsis" => Ok(Self::Nsis),
125 "app" => Ok(Self::App),
126 "dmg" => Ok(Self::Dmg),
127 "updater" => Ok(Self::Updater),
128 _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
129 }
130 }
131}
132
133#[derive(Debug, PartialEq, Eq, Clone, Default)]
135pub enum BundleTarget {
136 #[default]
138 All,
139 List(Vec<BundleType>),
141 One(BundleType),
143}
144
145#[cfg(feature = "schemars")]
146pub(crate) trait Merge: Sized {
147 fn merge(self, other: Self) -> Self;
148}
149
150#[cfg(feature = "schema")]
151use schemars::schema::{Metadata, Schema};
152
153#[cfg(feature = "schema")]
154impl<T: Merge> Merge for Option<T> {
155 fn merge(self, other: Self) -> Self {
156 match (self, other) {
157 (Some(x), Some(y)) => Some(x.merge(y)),
158 (None, y) => y,
159 (x, None) => x,
160 }
161 }
162}
163
164#[cfg(feature = "schema")]
165impl<T: Merge> Merge for Box<T> {
166 fn merge(mut self, other: Self) -> Self {
167 *self = (*self).merge(*other);
168 self
169 }
170}
171
172#[cfg(feature = "schema")]
173impl<T> Merge for Vec<T> {
174 fn merge(mut self, other: Self) -> Self {
175 self.extend(other);
176 self
177 }
178}
179
180#[cfg(feature = "schema")]
181impl Merge for Metadata {
182 fn merge(self, other: Self) -> Self {
183 Metadata {
184 id: self.id.or(other.id),
185 title: self.title.or(other.title),
186 description: self.description.or(other.description),
187 default: self.default.or(other.default),
188 deprecated: self.deprecated || other.deprecated,
189 read_only: self.read_only || other.read_only,
190 write_only: self.write_only || other.write_only,
191 examples: self.examples.merge(other.examples),
192 }
193 }
194}
195
196#[cfg(feature = "schema")]
197fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
198 if metadata == Metadata::default() {
199 schema
200 } else {
201 let mut schema_obj = schema.into_object();
202 schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
203 Schema::Object(schema_obj)
204 }
205}
206
207#[cfg(feature = "schema")]
208impl schemars::JsonSchema for BundleTarget {
209 fn schema_name() -> std::string::String {
210 "BundleTarget".to_owned()
211 }
212
213 fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
214 let any_of = vec![
215 schemars::schema::SchemaObject {
216 enum_values: Some(vec!["all".into()]),
217 metadata: Some(Box::new(schemars::schema::Metadata {
218 description: Some("Bundle all targets.".to_owned()),
219 ..Default::default()
220 })),
221 ..Default::default()
222 }
223 .into(),
224 apply_metadata(
225 generator.subschema_for::<Vec<BundleType>>(),
226 schemars::schema::Metadata {
227 description: Some("A list of bundle targets.".to_owned()),
228 ..Default::default()
229 },
230 ),
231 apply_metadata(
232 generator.subschema_for::<BundleType>(),
233 schemars::schema::Metadata {
234 description: Some("A single bundle target.".to_owned()),
235 ..Default::default()
236 },
237 ),
238 ];
239
240 schemars::schema::SchemaObject {
241 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
242 any_of: Some(any_of),
243 ..Default::default()
244 })),
245 metadata: Some(Box::new(schemars::schema::Metadata {
246 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
247 ..Default::default()
248 })),
249 ..Default::default()
250 }
251 .into()
252 }
253}
254
255impl Serialize for BundleTarget {
256 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 match self {
261 Self::All => serializer.serialize_str("all"),
262 Self::List(l) => l.serialize(serializer),
263 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
264 }
265 }
266}
267
268impl<'de> Deserialize<'de> for BundleTarget {
269 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
270 where
271 D: Deserializer<'de>,
272 {
273 #[derive(Deserialize, Serialize)]
274 #[serde(untagged)]
275 pub enum BundleTargetInner {
276 List(Vec<BundleType>),
277 One(BundleType),
278 All(String),
279 }
280
281 match BundleTargetInner::deserialize(deserializer)? {
282 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
283 BundleTargetInner::All(t) => Err(DeError::custom(format!("invalid bundle type {t}"))),
284 BundleTargetInner::List(l) => Ok(Self::List(l)),
285 BundleTargetInner::One(t) => Ok(Self::One(t)),
286 }
287 }
288}
289
290#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
294#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
295#[serde(rename_all = "camelCase", deny_unknown_fields)]
296pub struct AppImageConfig {
297 #[serde(default, alias = "bundle-media-framework")]
300 pub bundle_media_framework: bool,
301}
302
303#[skip_serializing_none]
307#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
308#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
309#[serde(rename_all = "camelCase", deny_unknown_fields)]
310pub struct DebConfig {
311 pub depends: Option<Vec<String>>,
313 #[serde(default)]
315 pub files: HashMap<PathBuf, PathBuf>,
316 pub desktop_template: Option<PathBuf>,
320 pub section: Option<String>,
322 pub priority: Option<String>,
325 pub changelog: Option<PathBuf>,
328}
329
330fn de_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
331where
332 D: Deserializer<'de>,
333{
334 let version = Option::<String>::deserialize(deserializer)?;
335 match version {
336 Some(v) if v.is_empty() => Ok(minimum_system_version()),
337 e => Ok(e),
338 }
339}
340
341#[skip_serializing_none]
345#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
346#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
347#[serde(rename_all = "camelCase", deny_unknown_fields)]
348pub struct MacConfig {
349 pub frameworks: Option<Vec<String>>,
353 #[serde(
360 deserialize_with = "de_minimum_system_version",
361 default = "minimum_system_version",
362 alias = "minimum-system-version"
363 )]
364 pub minimum_system_version: Option<String>,
365 #[serde(alias = "exception-domain")]
368 pub exception_domain: Option<String>,
369 pub license: Option<String>,
371 #[serde(alias = "signing-identity")]
373 pub signing_identity: Option<String>,
374 #[serde(alias = "provider-short-name")]
376 pub provider_short_name: Option<String>,
377 pub entitlements: Option<String>,
379}
380
381impl Default for MacConfig {
382 fn default() -> Self {
383 Self {
384 frameworks: None,
385 minimum_system_version: minimum_system_version(),
386 exception_domain: None,
387 license: None,
388 signing_identity: None,
389 provider_short_name: None,
390 entitlements: None,
391 }
392 }
393}
394
395fn minimum_system_version() -> Option<String> {
396 Some("10.13".into())
397}
398
399#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
403#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
404#[serde(rename_all = "camelCase", deny_unknown_fields)]
405pub struct WixLanguageConfig {
406 #[serde(alias = "locale-path")]
408 pub locale_path: Option<String>,
409}
410
411#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
413#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
414#[serde(untagged)]
415pub enum WixLanguage {
416 One(String),
418 List(Vec<String>),
420 Localized(HashMap<String, WixLanguageConfig>),
422}
423
424impl Default for WixLanguage {
425 fn default() -> Self {
426 Self::One("en-US".into())
427 }
428}
429
430#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
434#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
435#[serde(rename_all = "camelCase", deny_unknown_fields)]
436pub struct WixConfig {
437 #[serde(default)]
439 pub language: WixLanguage,
440 pub template: Option<PathBuf>,
442 #[serde(default, alias = "fragment-paths")]
444 pub fragment_paths: Vec<PathBuf>,
445 #[serde(default, alias = "component-group-refs")]
447 pub component_group_refs: Vec<String>,
448 #[serde(default, alias = "component-refs")]
450 pub component_refs: Vec<String>,
451 #[serde(default, alias = "feature-group-refs")]
453 pub feature_group_refs: Vec<String>,
454 #[serde(default, alias = "feature-refs")]
456 pub feature_refs: Vec<String>,
457 #[serde(default, alias = "merge-refs")]
459 pub merge_refs: Vec<String>,
460 #[serde(default, alias = "skip-webview-install")]
464 pub skip_webview_install: bool,
465 pub license: Option<PathBuf>,
469 #[serde(default, alias = "enable-elevated-update-task")]
471 pub enable_elevated_update_task: bool,
472 #[serde(alias = "banner-path")]
477 pub banner_path: Option<PathBuf>,
478 #[serde(alias = "dialog-image-path")]
483 pub dialog_image_path: Option<PathBuf>,
484}
485
486#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
490#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
491#[serde(rename_all = "camelCase", deny_unknown_fields)]
492pub enum NsisCompression {
493 Zlib,
495 Bzip2,
497 Lzma,
499}
500
501#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
503#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
504#[serde(rename_all = "camelCase", deny_unknown_fields)]
505pub struct NsisConfig {
506 pub template: Option<PathBuf>,
508 pub license: Option<PathBuf>,
510 #[serde(alias = "header-image")]
514 pub header_image: Option<PathBuf>,
515 #[serde(alias = "sidebar-image")]
519 pub sidebar_image: Option<PathBuf>,
520 #[serde(alias = "install-icon")]
522 pub installer_icon: Option<PathBuf>,
523 #[serde(default, alias = "install-mode")]
525 pub install_mode: NSISInstallerMode,
526 pub languages: Option<Vec<String>>,
532 pub custom_language_files: Option<HashMap<String, PathBuf>>,
539 #[serde(default, alias = "display-language-selector")]
542 pub display_language_selector: bool,
543 pub compression: Option<NsisCompression>,
547}
548
549#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
551#[serde(rename_all = "camelCase", deny_unknown_fields)]
552#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
553pub enum NSISInstallerMode {
554 #[default]
560 CurrentUser,
561 PerMachine,
566 Both,
572}
573
574#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
579#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
580#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
581pub enum WebviewInstallMode {
582 Skip,
584 DownloadBootstrapper {
588 #[serde(default = "default_true")]
590 silent: bool,
591 },
592 EmbedBootstrapper {
596 #[serde(default = "default_true")]
598 silent: bool,
599 },
600 OfflineInstaller {
604 #[serde(default = "default_true")]
606 silent: bool,
607 },
608 FixedRuntime {
611 path: PathBuf,
616 },
617}
618
619impl Default for WebviewInstallMode {
620 fn default() -> Self {
621 Self::DownloadBootstrapper { silent: true }
622 }
623}
624
625#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
629#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
630#[serde(rename_all = "camelCase", deny_unknown_fields)]
631pub struct WindowsConfig {
632 #[serde(alias = "digest-algorithm")]
635 pub digest_algorithm: Option<String>,
636 #[serde(alias = "certificate-thumbprint")]
638 pub certificate_thumbprint: Option<String>,
639 #[serde(alias = "timestamp-url")]
641 pub timestamp_url: Option<String>,
642 #[serde(default)]
645 pub tsp: bool,
646 #[serde(default, alias = "webview-install-mode")]
648 pub webview_install_mode: WebviewInstallMode,
649 #[serde(alias = "webview-fixed-runtime-path")]
656 pub webview_fixed_runtime_path: Option<PathBuf>,
657 #[serde(default = "default_true", alias = "allow-downgrades")]
663 pub allow_downgrades: bool,
664 pub wix: Option<WixConfig>,
666 pub nsis: Option<NsisConfig>,
668}
669
670impl Default for WindowsConfig {
671 fn default() -> Self {
672 Self {
673 digest_algorithm: None,
674 certificate_thumbprint: None,
675 timestamp_url: None,
676 tsp: false,
677 webview_install_mode: Default::default(),
678 webview_fixed_runtime_path: None,
679 allow_downgrades: true,
680 wix: None,
681 nsis: None,
682 }
683 }
684}
685
686#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
689#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
690#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
691pub enum BundleResources {
692 List(Vec<String>),
694 Map(HashMap<String, String>),
696}
697
698#[skip_serializing_none]
702#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
703#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
704#[serde(rename_all = "camelCase", deny_unknown_fields)]
705pub struct BundleConfig {
706 #[serde(default)]
708 pub active: bool,
709 #[serde(default)]
711 pub targets: BundleTarget,
712 pub identifier: String,
718 pub publisher: Option<String>,
721 #[serde(default)]
723 pub icon: Vec<String>,
724 pub resources: Option<BundleResources>,
728 pub copyright: Option<String>,
730 pub category: Option<String>,
735 #[serde(alias = "short-description")]
737 pub short_description: Option<String>,
738 #[serde(alias = "long-description")]
740 pub long_description: Option<String>,
741 #[serde(default)]
743 pub appimage: AppImageConfig,
744 #[serde(default)]
746 pub deb: DebConfig,
747 #[serde(rename = "macOS", default)]
749 pub macos: MacConfig,
750 #[serde(alias = "external-bin")]
762 pub external_bin: Option<Vec<String>>,
763 #[serde(default)]
765 pub windows: WindowsConfig,
766}
767
768#[skip_serializing_none]
770#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
771#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
772#[serde(rename_all = "camelCase", deny_unknown_fields)]
773pub struct CliArg {
774 pub short: Option<char>,
778 pub name: String,
780 pub description: Option<String>,
783 #[serde(alias = "long-description")]
786 pub long_description: Option<String>,
787 #[serde(default, alias = "takes-value")]
794 pub takes_value: bool,
795 #[serde(default)]
801 pub multiple: bool,
802 #[serde(default, alias = "multiple-occurrences")]
808 pub multiple_occurrences: bool,
809 #[serde(alias = "number-of-values")]
820 pub number_of_values: Option<usize>,
821 #[serde(alias = "possible-values")]
824 pub possible_values: Option<Vec<String>>,
825 #[serde(alias = "min-values")]
829 pub min_values: Option<usize>,
830 #[serde(alias = "max-values")]
834 pub max_values: Option<usize>,
835 #[serde(default)]
840 pub required: bool,
841 #[serde(alias = "required-unless-present")]
844 pub required_unless_present: Option<String>,
845 #[serde(alias = "required-unless-present-all")]
848 pub required_unless_present_all: Option<Vec<String>>,
849 #[serde(alias = "required-unless-present-any")]
852 pub required_unless_present_any: Option<Vec<String>>,
853 #[serde(alias = "conflicts-with")]
856 pub conflicts_with: Option<String>,
857 #[serde(alias = "conflicts-with-all")]
859 pub conflicts_with_all: Option<Vec<String>>,
860 pub requires: Option<String>,
863 #[serde(alias = "requires-all")]
866 pub requires_all: Option<Vec<String>>,
867 #[serde(alias = "requires-if")]
870 pub requires_if: Option<Vec<String>>,
871 #[serde(alias = "requires-if-eq")]
874 pub required_if_eq: Option<Vec<String>>,
875 #[serde(alias = "requires-equals")]
878 pub require_equals: Option<bool>,
879 #[cfg_attr(feature = "schema", validate(range(min = 1)))]
885 pub index: Option<usize>,
886}
887
888#[skip_serializing_none]
892#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
893#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
894#[serde(rename_all = "camelCase", deny_unknown_fields)]
895pub struct CliConfig {
896 pub description: Option<String>,
898 #[serde(alias = "long-description")]
900 pub long_description: Option<String>,
901 #[serde(alias = "before-help")]
905 pub before_help: Option<String>,
906 #[serde(alias = "after-help")]
910 pub after_help: Option<String>,
911 pub args: Option<Vec<CliArg>>,
913 pub subcommands: Option<HashMap<String, CliConfig>>,
915}
916
917#[skip_serializing_none]
921#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
922#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
923#[serde(rename_all = "camelCase", deny_unknown_fields)]
924pub struct WindowConfig {
925 #[serde(default = "default_window_label")]
927 pub label: String,
928 #[serde(default)]
930 pub url: WindowUrl,
931 #[serde(alias = "user-agent")]
933 pub user_agent: Option<String>,
934 #[serde(default = "default_true", alias = "file-drop-enabled")]
938 pub file_drop_enabled: bool,
939 #[serde(default)]
941 pub center: bool,
942 pub x: Option<f64>,
944 pub y: Option<f64>,
946 #[serde(default = "default_width")]
948 pub width: f64,
949 #[serde(default = "default_height")]
951 pub height: f64,
952 #[serde(alias = "min-width")]
954 pub min_width: Option<f64>,
955 #[serde(alias = "min-height")]
957 pub min_height: Option<f64>,
958 #[serde(alias = "max-width")]
960 pub max_width: Option<f64>,
961 #[serde(alias = "max-height")]
963 pub max_height: Option<f64>,
964 #[serde(default = "default_true")]
966 pub resizable: bool,
967 #[serde(default = "default_true")]
975 pub maximizable: bool,
976 #[serde(default = "default_true")]
982 pub minimizable: bool,
983 #[serde(default = "default_true")]
991 pub closable: bool,
992 #[serde(default = "default_title")]
994 pub title: String,
995 #[serde(default)]
997 pub fullscreen: bool,
998 #[serde(default = "default_true")]
1000 pub focus: bool,
1001 #[serde(default)]
1006 pub transparent: bool,
1007 #[serde(default)]
1009 pub maximized: bool,
1010 #[serde(default = "default_true")]
1012 pub visible: bool,
1013 #[serde(default = "default_true")]
1015 pub decorations: bool,
1016 #[serde(default, alias = "always-on-top")]
1018 pub always_on_top: bool,
1019 #[serde(default, alias = "content-protected")]
1021 pub content_protected: bool,
1022 #[serde(default, alias = "skip-taskbar")]
1024 pub skip_taskbar: bool,
1025 pub theme: Option<Theme>,
1027 #[serde(default, alias = "title-bar-style")]
1029 pub title_bar_style: TitleBarStyle,
1030 #[serde(default, alias = "hidden-title")]
1032 pub hidden_title: bool,
1033 #[serde(default, alias = "accept-first-mouse")]
1035 pub accept_first_mouse: bool,
1036 #[serde(default, alias = "tabbing-identifier")]
1043 pub tabbing_identifier: Option<String>,
1044 #[serde(default, alias = "additional-browser-args")]
1047 pub additional_browser_args: Option<String>,
1048}
1049
1050impl Default for WindowConfig {
1051 fn default() -> Self {
1052 Self {
1053 label: default_window_label(),
1054 url: WindowUrl::default(),
1055 user_agent: None,
1056 file_drop_enabled: true,
1057 center: false,
1058 x: None,
1059 y: None,
1060 width: default_width(),
1061 height: default_height(),
1062 min_width: None,
1063 min_height: None,
1064 max_width: None,
1065 max_height: None,
1066 resizable: true,
1067 maximizable: true,
1068 minimizable: true,
1069 closable: true,
1070 title: default_title(),
1071 fullscreen: false,
1072 focus: false,
1073 transparent: false,
1074 maximized: false,
1075 visible: true,
1076 decorations: true,
1077 always_on_top: false,
1078 content_protected: false,
1079 skip_taskbar: false,
1080 theme: None,
1081 title_bar_style: Default::default(),
1082 hidden_title: false,
1083 accept_first_mouse: false,
1084 tabbing_identifier: None,
1085 additional_browser_args: None,
1086 }
1087 }
1088}
1089
1090fn default_window_label() -> String {
1091 "main".to_string()
1092}
1093
1094fn default_width() -> f64 {
1095 800f64
1096}
1097
1098fn default_height() -> f64 {
1099 600f64
1100}
1101
1102fn default_title() -> String {
1103 "Tauri App".to_string()
1104}
1105
1106#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1110#[serde(rename_all = "camelCase", untagged)]
1111pub enum CspDirectiveSources {
1112 Inline(String),
1114 List(Vec<String>),
1116}
1117
1118impl Default for CspDirectiveSources {
1119 fn default() -> Self {
1120 Self::List(Vec::new())
1121 }
1122}
1123
1124impl From<CspDirectiveSources> for Vec<String> {
1125 fn from(sources: CspDirectiveSources) -> Self {
1126 match sources {
1127 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1128 CspDirectiveSources::List(l) => l,
1129 }
1130 }
1131}
1132
1133impl CspDirectiveSources {
1134 pub fn contains(&self, source: &str) -> bool {
1136 match self {
1137 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1138 Self::List(l) => l.contains(&source.into()),
1139 }
1140 }
1141
1142 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1144 match self {
1145 Self::Inline(s) => {
1146 s.push(' ');
1147 s.push_str(source.as_ref());
1148 }
1149 Self::List(l) => {
1150 l.push(source.as_ref().to_string());
1151 }
1152 }
1153 }
1154}
1155
1156#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1159#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1160#[serde(rename_all = "camelCase", untagged)]
1161pub enum Csp {
1162 Policy(String),
1164 DirectiveMap(HashMap<String, CspDirectiveSources>),
1166}
1167
1168impl From<HashMap<String, CspDirectiveSources>> for Csp {
1169 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1170 Self::DirectiveMap(map)
1171 }
1172}
1173
1174impl From<Csp> for HashMap<String, CspDirectiveSources> {
1175 fn from(csp: Csp) -> Self {
1176 match csp {
1177 Csp::Policy(policy) => {
1178 let mut map = HashMap::new();
1179 for directive in policy.split(';') {
1180 let mut tokens = directive.trim().split(' ');
1181 if let Some(directive) = tokens.next() {
1182 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1183 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1184 }
1185 }
1186 map
1187 }
1188 Csp::DirectiveMap(m) => m,
1189 }
1190 }
1191}
1192
1193impl Display for Csp {
1194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1195 match self {
1196 Self::Policy(s) => write!(f, "{s}"),
1197 Self::DirectiveMap(m) => {
1198 let len = m.len();
1199 let mut i = 0;
1200 for (directive, sources) in m {
1201 let sources: Vec<String> = sources.clone().into();
1202 write!(f, "{} {}", directive, sources.join(" "))?;
1203 i += 1;
1204 if i != len {
1205 write!(f, "; ")?;
1206 }
1207 }
1208 Ok(())
1209 }
1210 }
1211 }
1212}
1213
1214#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1216#[serde(untagged)]
1217#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1218pub enum DisabledCspModificationKind {
1219 Flag(bool),
1222 List(Vec<String>),
1224}
1225
1226impl Default for DisabledCspModificationKind {
1227 fn default() -> Self {
1228 Self::Flag(false)
1229 }
1230}
1231
1232#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1235#[serde(rename_all = "camelCase", deny_unknown_fields)]
1236pub struct RemoteDomainAccessScope {
1237 pub scheme: Option<String>,
1239 pub domain: String,
1241 pub windows: Vec<String>,
1243 #[serde(default)]
1246 pub plugins: Vec<String>,
1247 #[serde(default, rename = "enableTauriAPI", alias = "enable-tauri-api")]
1249 pub enable_tauri_api: bool,
1250}
1251
1252#[skip_serializing_none]
1256#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1257#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1258#[serde(rename_all = "camelCase", deny_unknown_fields)]
1259pub struct SecurityConfig {
1260 pub csp: Option<Csp>,
1266 #[serde(alias = "dev-csp")]
1271 pub dev_csp: Option<Csp>,
1272 #[serde(default, alias = "freeze-prototype")]
1274 pub freeze_prototype: bool,
1275 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
1288 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
1289 #[serde(default, alias = "dangerous-remote-domain-ipc-access")]
1302 pub dangerous_remote_domain_ipc_access: Vec<RemoteDomainAccessScope>,
1303 #[serde(default, alias = "dangerous-use-http-scheme")]
1307 pub dangerous_use_http_scheme: bool,
1308}
1309
1310pub trait Allowlist {
1312 fn all_features() -> Vec<&'static str>;
1314 fn to_features(&self) -> Vec<&'static str>;
1316}
1317
1318macro_rules! check_feature {
1319 ($self:ident, $features:ident, $flag:ident, $feature_name: expr) => {
1320 if $self.$flag {
1321 $features.push($feature_name)
1322 }
1323 };
1324}
1325
1326#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1335#[serde(untagged)]
1336#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1337pub enum FsAllowlistScope {
1338 AllowedPaths(Vec<PathBuf>),
1340 #[serde(rename_all = "camelCase")]
1342 Scope {
1343 #[serde(default)]
1345 allow: Vec<PathBuf>,
1346 #[serde(default)]
1349 deny: Vec<PathBuf>,
1350 #[serde(alias = "require-literal-leading-dot")]
1359 require_literal_leading_dot: Option<bool>,
1360 },
1361}
1362
1363impl Default for FsAllowlistScope {
1364 fn default() -> Self {
1365 Self::AllowedPaths(Vec::new())
1366 }
1367}
1368
1369#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1373#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1374#[serde(rename_all = "camelCase", deny_unknown_fields)]
1375pub struct FsAllowlistConfig {
1376 #[serde(default)]
1378 pub scope: FsAllowlistScope,
1379 #[serde(default)]
1381 pub all: bool,
1382 #[serde(default, alias = "read-file")]
1384 pub read_file: bool,
1385 #[serde(default, alias = "write-file")]
1387 pub write_file: bool,
1388 #[serde(default, alias = "read-dir")]
1390 pub read_dir: bool,
1391 #[serde(default, alias = "copy-file")]
1393 pub copy_file: bool,
1394 #[serde(default, alias = "create-dir")]
1396 pub create_dir: bool,
1397 #[serde(default, alias = "remove-dir")]
1399 pub remove_dir: bool,
1400 #[serde(default, alias = "remove-file")]
1402 pub remove_file: bool,
1403 #[serde(default, alias = "rename-file")]
1405 pub rename_file: bool,
1406 #[serde(default)]
1408 pub exists: bool,
1409}
1410
1411impl Allowlist for FsAllowlistConfig {
1412 fn all_features() -> Vec<&'static str> {
1413 let allowlist = Self {
1414 scope: Default::default(),
1415 all: false,
1416 read_file: true,
1417 write_file: true,
1418 read_dir: true,
1419 copy_file: true,
1420 create_dir: true,
1421 remove_dir: true,
1422 remove_file: true,
1423 rename_file: true,
1424 exists: true,
1425 };
1426 let mut features = allowlist.to_features();
1427 features.push("fs-all");
1428 features
1429 }
1430
1431 fn to_features(&self) -> Vec<&'static str> {
1432 if self.all {
1433 vec!["fs-all"]
1434 } else {
1435 let mut features = Vec::new();
1436 check_feature!(self, features, read_file, "fs-read-file");
1437 check_feature!(self, features, write_file, "fs-write-file");
1438 check_feature!(self, features, read_dir, "fs-read-dir");
1439 check_feature!(self, features, copy_file, "fs-copy-file");
1440 check_feature!(self, features, create_dir, "fs-create-dir");
1441 check_feature!(self, features, remove_dir, "fs-remove-dir");
1442 check_feature!(self, features, remove_file, "fs-remove-file");
1443 check_feature!(self, features, rename_file, "fs-rename-file");
1444 check_feature!(self, features, exists, "fs-exists");
1445 features
1446 }
1447 }
1448}
1449
1450#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1454#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1455#[serde(rename_all = "camelCase", deny_unknown_fields)]
1456pub struct WindowAllowlistConfig {
1457 #[serde(default)]
1459 pub all: bool,
1460 #[serde(default)]
1462 pub create: bool,
1463 #[serde(default)]
1465 pub center: bool,
1466 #[serde(default, alias = "request-user-attention")]
1468 pub request_user_attention: bool,
1469 #[serde(default, alias = "set-resizable")]
1471 pub set_resizable: bool,
1472 #[serde(default, alias = "set-maximizable")]
1474 pub set_maximizable: bool,
1475 #[serde(default, alias = "set-minimizable")]
1477 pub set_minimizable: bool,
1478 #[serde(default, alias = "set-closable")]
1480 pub set_closable: bool,
1481 #[serde(default, alias = "set-title")]
1483 pub set_title: bool,
1484 #[serde(default)]
1486 pub maximize: bool,
1487 #[serde(default)]
1489 pub unmaximize: bool,
1490 #[serde(default)]
1492 pub minimize: bool,
1493 #[serde(default)]
1495 pub unminimize: bool,
1496 #[serde(default)]
1498 pub show: bool,
1499 #[serde(default)]
1501 pub hide: bool,
1502 #[serde(default)]
1504 pub close: bool,
1505 #[serde(default, alias = "set-decorations")]
1507 pub set_decorations: bool,
1508 #[serde(default, alias = "set-always-on-top")]
1510 pub set_always_on_top: bool,
1511 #[serde(default, alias = "set-content-protected")]
1513 pub set_content_protected: bool,
1514 #[serde(default, alias = "set-size")]
1516 pub set_size: bool,
1517 #[serde(default, alias = "set-min-size")]
1519 pub set_min_size: bool,
1520 #[serde(default, alias = "set-max-size")]
1522 pub set_max_size: bool,
1523 #[serde(default, alias = "set-position")]
1525 pub set_position: bool,
1526 #[serde(default, alias = "set-fullscreen")]
1528 pub set_fullscreen: bool,
1529 #[serde(default, alias = "set-focus")]
1531 pub set_focus: bool,
1532 #[serde(default, alias = "set-icon")]
1534 pub set_icon: bool,
1535 #[serde(default, alias = "set-skip-taskbar")]
1537 pub set_skip_taskbar: bool,
1538 #[serde(default, alias = "set-cursor-grab")]
1540 pub set_cursor_grab: bool,
1541 #[serde(default, alias = "set-cursor-visible")]
1543 pub set_cursor_visible: bool,
1544 #[serde(default, alias = "set-cursor-icon")]
1546 pub set_cursor_icon: bool,
1547 #[serde(default, alias = "set-cursor-position")]
1549 pub set_cursor_position: bool,
1550 #[serde(default, alias = "set-ignore-cursor-events")]
1552 pub set_ignore_cursor_events: bool,
1553 #[serde(default, alias = "start-dragging")]
1555 pub start_dragging: bool,
1556 #[serde(default)]
1558 pub print: bool,
1559}
1560
1561impl Allowlist for WindowAllowlistConfig {
1562 fn all_features() -> Vec<&'static str> {
1563 let allowlist = Self {
1564 all: false,
1565 create: true,
1566 center: true,
1567 request_user_attention: true,
1568 set_resizable: true,
1569 set_maximizable: true,
1570 set_minimizable: true,
1571 set_closable: true,
1572 set_title: true,
1573 maximize: true,
1574 unmaximize: true,
1575 minimize: true,
1576 unminimize: true,
1577 show: true,
1578 hide: true,
1579 close: true,
1580 set_decorations: true,
1581 set_always_on_top: true,
1582 set_content_protected: false,
1583 set_size: true,
1584 set_min_size: true,
1585 set_max_size: true,
1586 set_position: true,
1587 set_fullscreen: true,
1588 set_focus: true,
1589 set_icon: true,
1590 set_skip_taskbar: true,
1591 set_cursor_grab: true,
1592 set_cursor_visible: true,
1593 set_cursor_icon: true,
1594 set_cursor_position: true,
1595 set_ignore_cursor_events: true,
1596 start_dragging: true,
1597 print: true,
1598 };
1599 let mut features = allowlist.to_features();
1600 features.push("window-all");
1601 features
1602 }
1603
1604 fn to_features(&self) -> Vec<&'static str> {
1605 if self.all {
1606 vec!["window-all"]
1607 } else {
1608 let mut features = Vec::new();
1609 check_feature!(self, features, create, "window-create");
1610 check_feature!(self, features, center, "window-center");
1611 check_feature!(
1612 self,
1613 features,
1614 request_user_attention,
1615 "window-request-user-attention"
1616 );
1617 check_feature!(self, features, set_resizable, "window-set-resizable");
1618 check_feature!(self, features, set_maximizable, "window-set-maximizable");
1619 check_feature!(self, features, set_minimizable, "window-set-minimizable");
1620 check_feature!(self, features, set_closable, "window-set-closable");
1621 check_feature!(self, features, set_title, "window-set-title");
1622 check_feature!(self, features, maximize, "window-maximize");
1623 check_feature!(self, features, unmaximize, "window-unmaximize");
1624 check_feature!(self, features, minimize, "window-minimize");
1625 check_feature!(self, features, unminimize, "window-unminimize");
1626 check_feature!(self, features, show, "window-show");
1627 check_feature!(self, features, hide, "window-hide");
1628 check_feature!(self, features, close, "window-close");
1629 check_feature!(self, features, set_decorations, "window-set-decorations");
1630 check_feature!(
1631 self,
1632 features,
1633 set_always_on_top,
1634 "window-set-always-on-top"
1635 );
1636 check_feature!(
1637 self,
1638 features,
1639 set_content_protected,
1640 "window-set-content-protected"
1641 );
1642 check_feature!(self, features, set_size, "window-set-size");
1643 check_feature!(self, features, set_min_size, "window-set-min-size");
1644 check_feature!(self, features, set_max_size, "window-set-max-size");
1645 check_feature!(self, features, set_position, "window-set-position");
1646 check_feature!(self, features, set_fullscreen, "window-set-fullscreen");
1647 check_feature!(self, features, set_focus, "window-set-focus");
1648 check_feature!(self, features, set_icon, "window-set-icon");
1649 check_feature!(self, features, set_skip_taskbar, "window-set-skip-taskbar");
1650 check_feature!(self, features, set_cursor_grab, "window-set-cursor-grab");
1651 check_feature!(
1652 self,
1653 features,
1654 set_cursor_visible,
1655 "window-set-cursor-visible"
1656 );
1657 check_feature!(self, features, set_cursor_icon, "window-set-cursor-icon");
1658 check_feature!(
1659 self,
1660 features,
1661 set_cursor_position,
1662 "window-set-cursor-position"
1663 );
1664 check_feature!(
1665 self,
1666 features,
1667 set_ignore_cursor_events,
1668 "window-set-ignore-cursor-events"
1669 );
1670 check_feature!(self, features, start_dragging, "window-start-dragging");
1671 check_feature!(self, features, print, "window-print");
1672 features
1673 }
1674 }
1675}
1676
1677#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1679#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1680pub struct ShellAllowedCommand {
1681 pub name: String,
1686
1687 #[serde(rename = "cmd", default)] pub command: PathBuf,
1695
1696 #[serde(default)]
1698 pub args: ShellAllowedArgs,
1699
1700 #[serde(default)]
1702 pub sidecar: bool,
1703}
1704
1705impl<'de> Deserialize<'de> for ShellAllowedCommand {
1706 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1707 where
1708 D: Deserializer<'de>,
1709 {
1710 #[derive(Deserialize)]
1711 struct InnerShellAllowedCommand {
1712 name: String,
1713 #[serde(rename = "cmd")]
1714 command: Option<PathBuf>,
1715 #[serde(default)]
1716 args: ShellAllowedArgs,
1717 #[serde(default)]
1718 sidecar: bool,
1719 }
1720
1721 let config = InnerShellAllowedCommand::deserialize(deserializer)?;
1722
1723 if !config.sidecar && config.command.is_none() {
1724 return Err(DeError::custom(
1725 "The shell scope `command` value is required.",
1726 ));
1727 }
1728
1729 Ok(ShellAllowedCommand {
1730 name: config.name,
1731 command: config.command.unwrap_or_default(),
1732 args: config.args,
1733 sidecar: config.sidecar,
1734 })
1735 }
1736}
1737
1738#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1744#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1745#[serde(untagged, deny_unknown_fields)]
1746#[non_exhaustive]
1747pub enum ShellAllowedArgs {
1748 Flag(bool),
1750
1751 List(Vec<ShellAllowedArg>),
1753}
1754
1755impl Default for ShellAllowedArgs {
1756 fn default() -> Self {
1757 Self::Flag(false)
1758 }
1759}
1760
1761#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1763#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1764#[serde(untagged, deny_unknown_fields)]
1765#[non_exhaustive]
1766pub enum ShellAllowedArg {
1767 Fixed(String),
1769
1770 Var {
1773 validator: String,
1780 },
1781}
1782
1783#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1786#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1787pub struct ShellAllowlistScope(pub Vec<ShellAllowedCommand>);
1788
1789#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1791#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1792#[serde(untagged, deny_unknown_fields)]
1793#[non_exhaustive]
1794pub enum ShellAllowlistOpen {
1795 Flag(bool),
1799
1800 Validate(String),
1805}
1806
1807impl Default for ShellAllowlistOpen {
1808 fn default() -> Self {
1809 Self::Flag(false)
1810 }
1811}
1812
1813#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1817#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1818#[serde(rename_all = "camelCase", deny_unknown_fields)]
1819pub struct ShellAllowlistConfig {
1820 #[serde(default)]
1823 pub scope: ShellAllowlistScope,
1824 #[serde(default)]
1826 pub all: bool,
1827 #[serde(default)]
1829 pub execute: bool,
1830 #[serde(default)]
1834 pub sidecar: bool,
1835 #[serde(default)]
1837 pub open: ShellAllowlistOpen,
1838}
1839
1840impl Allowlist for ShellAllowlistConfig {
1841 fn all_features() -> Vec<&'static str> {
1842 let allowlist = Self {
1843 scope: Default::default(),
1844 all: false,
1845 execute: true,
1846 sidecar: true,
1847 open: ShellAllowlistOpen::Flag(true),
1848 };
1849 let mut features = allowlist.to_features();
1850 features.push("shell-all");
1851 features
1852 }
1853
1854 fn to_features(&self) -> Vec<&'static str> {
1855 if self.all {
1856 vec!["shell-all"]
1857 } else {
1858 let mut features = Vec::new();
1859 check_feature!(self, features, execute, "shell-execute");
1860 check_feature!(self, features, sidecar, "shell-sidecar");
1861
1862 if !matches!(self.open, ShellAllowlistOpen::Flag(false)) {
1863 features.push("shell-open")
1864 }
1865
1866 features
1867 }
1868 }
1869}
1870
1871#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1875#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1876#[serde(rename_all = "camelCase", deny_unknown_fields)]
1877pub struct DialogAllowlistConfig {
1878 #[serde(default)]
1880 pub all: bool,
1881 #[serde(default)]
1883 pub open: bool,
1884 #[serde(default)]
1886 pub save: bool,
1887 #[serde(default)]
1889 pub message: bool,
1890 #[serde(default)]
1892 pub ask: bool,
1893 #[serde(default)]
1895 pub confirm: bool,
1896}
1897
1898impl Allowlist for DialogAllowlistConfig {
1899 fn all_features() -> Vec<&'static str> {
1900 let allowlist = Self {
1901 all: false,
1902 open: true,
1903 save: true,
1904 message: true,
1905 ask: true,
1906 confirm: true,
1907 };
1908 let mut features = allowlist.to_features();
1909 features.push("dialog-all");
1910 features
1911 }
1912
1913 fn to_features(&self) -> Vec<&'static str> {
1914 if self.all {
1915 vec!["dialog-all"]
1916 } else {
1917 let mut features = Vec::new();
1918 check_feature!(self, features, open, "dialog-open");
1919 check_feature!(self, features, save, "dialog-save");
1920 check_feature!(self, features, message, "dialog-message");
1921 check_feature!(self, features, ask, "dialog-ask");
1922 check_feature!(self, features, confirm, "dialog-confirm");
1923 features
1924 }
1925 }
1926}
1927
1928#[allow(rustdoc::bare_urls)]
1937#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1938#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1941pub struct HttpAllowlistScope(pub Vec<Url>);
1942
1943#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1947#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1948#[serde(rename_all = "camelCase", deny_unknown_fields)]
1949pub struct HttpAllowlistConfig {
1950 #[serde(default)]
1952 pub scope: HttpAllowlistScope,
1953 #[serde(default)]
1955 pub all: bool,
1956 #[serde(default)]
1958 pub request: bool,
1959}
1960
1961impl Allowlist for HttpAllowlistConfig {
1962 fn all_features() -> Vec<&'static str> {
1963 let allowlist = Self {
1964 scope: Default::default(),
1965 all: false,
1966 request: true,
1967 };
1968 let mut features = allowlist.to_features();
1969 features.push("http-all");
1970 features
1971 }
1972
1973 fn to_features(&self) -> Vec<&'static str> {
1974 if self.all {
1975 vec!["http-all"]
1976 } else {
1977 let mut features = Vec::new();
1978 check_feature!(self, features, request, "http-request");
1979 features
1980 }
1981 }
1982}
1983
1984#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1988#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1989#[serde(rename_all = "camelCase", deny_unknown_fields)]
1990pub struct NotificationAllowlistConfig {
1991 #[serde(default)]
1993 pub all: bool,
1994}
1995
1996impl Allowlist for NotificationAllowlistConfig {
1997 fn all_features() -> Vec<&'static str> {
1998 let allowlist = Self { all: false };
1999 let mut features = allowlist.to_features();
2000 features.push("notification-all");
2001 features
2002 }
2003
2004 fn to_features(&self) -> Vec<&'static str> {
2005 if self.all {
2006 vec!["notification-all"]
2007 } else {
2008 vec![]
2009 }
2010 }
2011}
2012
2013#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2017#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2018#[serde(rename_all = "camelCase", deny_unknown_fields)]
2019pub struct GlobalShortcutAllowlistConfig {
2020 #[serde(default)]
2022 pub all: bool,
2023}
2024
2025impl Allowlist for GlobalShortcutAllowlistConfig {
2026 fn all_features() -> Vec<&'static str> {
2027 let allowlist = Self { all: false };
2028 let mut features = allowlist.to_features();
2029 features.push("global-shortcut-all");
2030 features
2031 }
2032
2033 fn to_features(&self) -> Vec<&'static str> {
2034 if self.all {
2035 vec!["global-shortcut-all"]
2036 } else {
2037 vec![]
2038 }
2039 }
2040}
2041
2042#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2046#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2047#[serde(rename_all = "camelCase", deny_unknown_fields)]
2048pub struct OsAllowlistConfig {
2049 #[serde(default)]
2051 pub all: bool,
2052}
2053
2054impl Allowlist for OsAllowlistConfig {
2055 fn all_features() -> Vec<&'static str> {
2056 let allowlist = Self { all: false };
2057 let mut features = allowlist.to_features();
2058 features.push("os-all");
2059 features
2060 }
2061
2062 fn to_features(&self) -> Vec<&'static str> {
2063 if self.all {
2064 vec!["os-all"]
2065 } else {
2066 vec![]
2067 }
2068 }
2069}
2070
2071#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2075#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2076#[serde(rename_all = "camelCase", deny_unknown_fields)]
2077pub struct PathAllowlistConfig {
2078 #[serde(default)]
2080 pub all: bool,
2081}
2082
2083impl Allowlist for PathAllowlistConfig {
2084 fn all_features() -> Vec<&'static str> {
2085 let allowlist = Self { all: false };
2086 let mut features = allowlist.to_features();
2087 features.push("path-all");
2088 features
2089 }
2090
2091 fn to_features(&self) -> Vec<&'static str> {
2092 if self.all {
2093 vec!["path-all"]
2094 } else {
2095 vec![]
2096 }
2097 }
2098}
2099
2100#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2104#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2105#[serde(rename_all = "camelCase", deny_unknown_fields)]
2106pub struct ProtocolAllowlistConfig {
2107 #[serde(default, alias = "asset-scope")]
2109 pub asset_scope: FsAllowlistScope,
2110 #[serde(default)]
2112 pub all: bool,
2113 #[serde(default)]
2115 pub asset: bool,
2116}
2117
2118impl Allowlist for ProtocolAllowlistConfig {
2119 fn all_features() -> Vec<&'static str> {
2120 let allowlist = Self {
2121 asset_scope: Default::default(),
2122 all: false,
2123 asset: true,
2124 };
2125 let mut features = allowlist.to_features();
2126 features.push("protocol-all");
2127 features
2128 }
2129
2130 fn to_features(&self) -> Vec<&'static str> {
2131 if self.all {
2132 vec!["protocol-all"]
2133 } else {
2134 let mut features = Vec::new();
2135 check_feature!(self, features, asset, "protocol-asset");
2136 features
2137 }
2138 }
2139}
2140
2141#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2146#[serde(rename_all = "camelCase", deny_unknown_fields)]
2147pub struct ProcessAllowlistConfig {
2148 #[serde(default)]
2150 pub all: bool,
2151 #[serde(default)]
2153 pub relaunch: bool,
2154 #[serde(
2159 default,
2160 alias = "relaunchDangerousAllowSymlinkMacOS",
2161 alias = "relaunch-dangerous-allow-symlink-macos"
2162 )]
2163 pub relaunch_dangerous_allow_symlink_macos: bool,
2164 #[serde(default)]
2166 pub exit: bool,
2167}
2168
2169impl Allowlist for ProcessAllowlistConfig {
2170 fn all_features() -> Vec<&'static str> {
2171 let allowlist = Self {
2172 all: false,
2173 relaunch: true,
2174 relaunch_dangerous_allow_symlink_macos: false,
2175 exit: true,
2176 };
2177 let mut features = allowlist.to_features();
2178 features.push("process-all");
2179 features
2180 }
2181
2182 fn to_features(&self) -> Vec<&'static str> {
2183 if self.all {
2184 vec!["process-all"]
2185 } else {
2186 let mut features = Vec::new();
2187 check_feature!(self, features, relaunch, "process-relaunch");
2188 check_feature!(
2189 self,
2190 features,
2191 relaunch_dangerous_allow_symlink_macos,
2192 "process-relaunch-dangerous-allow-symlink-macos"
2193 );
2194 check_feature!(self, features, exit, "process-exit");
2195 features
2196 }
2197 }
2198}
2199
2200#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2204#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2205#[serde(rename_all = "camelCase", deny_unknown_fields)]
2206pub struct ClipboardAllowlistConfig {
2207 #[serde(default)]
2209 pub all: bool,
2210 #[serde(default, alias = "writeText")]
2212 pub write_text: bool,
2213 #[serde(default, alias = "readText")]
2215 pub read_text: bool,
2216}
2217
2218impl Allowlist for ClipboardAllowlistConfig {
2219 fn all_features() -> Vec<&'static str> {
2220 let allowlist = Self {
2221 all: false,
2222 write_text: true,
2223 read_text: true,
2224 };
2225 let mut features = allowlist.to_features();
2226 features.push("clipboard-all");
2227 features
2228 }
2229
2230 fn to_features(&self) -> Vec<&'static str> {
2231 if self.all {
2232 vec!["clipboard-all"]
2233 } else {
2234 let mut features = Vec::new();
2235 check_feature!(self, features, write_text, "clipboard-write-text");
2236 check_feature!(self, features, read_text, "clipboard-read-text");
2237 features
2238 }
2239 }
2240}
2241
2242#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2246#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2247#[serde(rename_all = "camelCase", deny_unknown_fields)]
2248pub struct AppAllowlistConfig {
2249 #[serde(default)]
2251 pub all: bool,
2252 #[serde(default)]
2254 pub show: bool,
2255 #[serde(default)]
2257 pub hide: bool,
2258}
2259
2260impl Allowlist for AppAllowlistConfig {
2261 fn all_features() -> Vec<&'static str> {
2262 let allowlist = Self {
2263 all: false,
2264 show: true,
2265 hide: true,
2266 };
2267 let mut features = allowlist.to_features();
2268 features.push("app-all");
2269 features
2270 }
2271
2272 fn to_features(&self) -> Vec<&'static str> {
2273 if self.all {
2274 vec!["app-all"]
2275 } else {
2276 let mut features = Vec::new();
2277 check_feature!(self, features, show, "app-show");
2278 check_feature!(self, features, hide, "app-hide");
2279 features
2280 }
2281 }
2282}
2283
2284#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2295#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2296#[serde(rename_all = "camelCase", deny_unknown_fields)]
2297pub struct AllowlistConfig {
2298 #[serde(default)]
2300 pub all: bool,
2301 #[serde(default)]
2303 pub fs: FsAllowlistConfig,
2304 #[serde(default)]
2306 pub window: WindowAllowlistConfig,
2307 #[serde(default)]
2309 pub shell: ShellAllowlistConfig,
2310 #[serde(default)]
2312 pub dialog: DialogAllowlistConfig,
2313 #[serde(default)]
2315 pub http: HttpAllowlistConfig,
2316 #[serde(default)]
2318 pub notification: NotificationAllowlistConfig,
2319 #[serde(default, alias = "global-shortcut")]
2321 pub global_shortcut: GlobalShortcutAllowlistConfig,
2322 #[serde(default)]
2324 pub os: OsAllowlistConfig,
2325 #[serde(default)]
2327 pub path: PathAllowlistConfig,
2328 #[serde(default)]
2330 pub protocol: ProtocolAllowlistConfig,
2331 #[serde(default)]
2333 pub process: ProcessAllowlistConfig,
2334 #[serde(default)]
2336 pub clipboard: ClipboardAllowlistConfig,
2337 #[serde(default)]
2339 pub app: AppAllowlistConfig,
2340}
2341
2342impl Allowlist for AllowlistConfig {
2343 fn all_features() -> Vec<&'static str> {
2344 let mut features = vec!["api-all"];
2345 features.extend(FsAllowlistConfig::all_features());
2346 features.extend(WindowAllowlistConfig::all_features());
2347 features.extend(ShellAllowlistConfig::all_features());
2348 features.extend(DialogAllowlistConfig::all_features());
2349 features.extend(HttpAllowlistConfig::all_features());
2350 features.extend(NotificationAllowlistConfig::all_features());
2351 features.extend(GlobalShortcutAllowlistConfig::all_features());
2352 features.extend(OsAllowlistConfig::all_features());
2353 features.extend(PathAllowlistConfig::all_features());
2354 features.extend(ProtocolAllowlistConfig::all_features());
2355 features.extend(ProcessAllowlistConfig::all_features());
2356 features.extend(ClipboardAllowlistConfig::all_features());
2357 features.extend(AppAllowlistConfig::all_features());
2358 features
2359 }
2360
2361 fn to_features(&self) -> Vec<&'static str> {
2362 if self.all {
2363 vec!["api-all"]
2364 } else {
2365 let mut features = Vec::new();
2366 features.extend(self.fs.to_features());
2367 features.extend(self.window.to_features());
2368 features.extend(self.shell.to_features());
2369 features.extend(self.dialog.to_features());
2370 features.extend(self.http.to_features());
2371 features.extend(self.notification.to_features());
2372 features.extend(self.global_shortcut.to_features());
2373 features.extend(self.os.to_features());
2374 features.extend(self.path.to_features());
2375 features.extend(self.protocol.to_features());
2376 features.extend(self.process.to_features());
2377 features.extend(self.clipboard.to_features());
2378 features.extend(self.app.to_features());
2379 features
2380 }
2381 }
2382}
2383
2384#[skip_serializing_none]
2386#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
2387#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2388#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2389pub enum PatternKind {
2390 #[default]
2392 Brownfield,
2393 Isolation {
2395 dir: PathBuf,
2397 },
2398}
2399
2400#[skip_serializing_none]
2404#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2405#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2406#[serde(rename_all = "camelCase", deny_unknown_fields)]
2407pub struct TauriConfig {
2408 #[serde(default)]
2410 pub pattern: PatternKind,
2411 #[serde(default)]
2413 pub windows: Vec<WindowConfig>,
2414 pub cli: Option<CliConfig>,
2416 #[serde(default)]
2418 pub bundle: BundleConfig,
2419 #[serde(default)]
2421 pub allowlist: AllowlistConfig,
2422 #[serde(default)]
2424 pub security: SecurityConfig,
2425 #[serde(default)]
2427 pub updater: UpdaterConfig,
2428 #[serde(alias = "system-tray")]
2430 pub system_tray: Option<SystemTrayConfig>,
2431 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2433 pub macos_private_api: bool,
2434}
2435
2436#[skip_serializing_none]
2440#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
2441#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2442pub struct UpdaterEndpoint(pub Url);
2443
2444impl std::fmt::Display for UpdaterEndpoint {
2445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2446 write!(f, "{}", self.0)
2447 }
2448}
2449
2450impl<'de> Deserialize<'de> for UpdaterEndpoint {
2451 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2452 where
2453 D: Deserializer<'de>,
2454 {
2455 let url = Url::deserialize(deserializer)?;
2456 #[cfg(all(not(debug_assertions), not(feature = "schema")))]
2457 {
2458 if url.scheme() != "https" {
2459 return Err(serde::de::Error::custom(
2460 "The configured updater endpoint must use the `https` protocol.",
2461 ));
2462 }
2463 }
2464 Ok(Self(url))
2465 }
2466}
2467
2468#[derive(Debug, PartialEq, Eq, Clone, Default)]
2470#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2471#[cfg_attr(feature = "schema", schemars(rename_all = "camelCase"))]
2472pub enum WindowsUpdateInstallMode {
2473 BasicUi,
2475 Quiet,
2478 #[default]
2480 Passive,
2481 }
2484
2485impl Display for WindowsUpdateInstallMode {
2486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2487 write!(
2488 f,
2489 "{}",
2490 match self {
2491 Self::BasicUi => "basicUI",
2492 Self::Quiet => "quiet",
2493 Self::Passive => "passive",
2494 }
2495 )
2496 }
2497}
2498
2499impl Serialize for WindowsUpdateInstallMode {
2500 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2501 where
2502 S: Serializer,
2503 {
2504 serializer.serialize_str(self.to_string().as_ref())
2505 }
2506}
2507
2508impl<'de> Deserialize<'de> for WindowsUpdateInstallMode {
2509 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
2510 where
2511 D: Deserializer<'de>,
2512 {
2513 let s = String::deserialize(deserializer)?;
2514 match s.to_lowercase().as_str() {
2515 "basicui" => Ok(Self::BasicUi),
2516 "quiet" => Ok(Self::Quiet),
2517 "passive" => Ok(Self::Passive),
2518 _ => Err(DeError::custom(format!(
2519 "unknown update install mode '{s}'"
2520 ))),
2521 }
2522 }
2523}
2524
2525#[skip_serializing_none]
2529#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
2530#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2531#[serde(rename_all = "camelCase", deny_unknown_fields)]
2532pub struct UpdaterWindowsConfig {
2533 #[serde(default, alias = "installer-args")]
2535 pub installer_args: Vec<String>,
2536 #[serde(default, alias = "install-mode")]
2538 pub install_mode: WindowsUpdateInstallMode,
2539}
2540
2541#[skip_serializing_none]
2545#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
2546#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2547#[serde(rename_all = "camelCase", deny_unknown_fields)]
2548pub struct UpdaterConfig {
2549 #[serde(default)]
2551 pub active: bool,
2552 #[serde(default = "default_true")]
2554 pub dialog: bool,
2555 #[allow(rustdoc::bare_urls)]
2566 pub endpoints: Option<Vec<UpdaterEndpoint>>,
2567 #[serde(default)] pub pubkey: String,
2570 #[serde(default)]
2572 pub windows: UpdaterWindowsConfig,
2573}
2574
2575impl<'de> Deserialize<'de> for UpdaterConfig {
2576 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2577 where
2578 D: Deserializer<'de>,
2579 {
2580 #[derive(Deserialize)]
2581 struct InnerUpdaterConfig {
2582 #[serde(default)]
2583 active: bool,
2584 #[serde(default = "default_true")]
2585 dialog: bool,
2586 endpoints: Option<Vec<UpdaterEndpoint>>,
2587 pubkey: Option<String>,
2588 #[serde(default)]
2589 windows: UpdaterWindowsConfig,
2590 }
2591
2592 let config = InnerUpdaterConfig::deserialize(deserializer)?;
2593
2594 if config.active && config.pubkey.is_none() {
2595 return Err(DeError::custom(
2596 "The updater `pubkey` configuration is required.",
2597 ));
2598 }
2599
2600 Ok(UpdaterConfig {
2601 active: config.active,
2602 dialog: config.dialog,
2603 endpoints: config.endpoints,
2604 pubkey: config.pubkey.unwrap_or_default(),
2605 windows: config.windows,
2606 })
2607 }
2608}
2609
2610impl Default for UpdaterConfig {
2611 fn default() -> Self {
2612 Self {
2613 active: false,
2614 dialog: true,
2615 endpoints: None,
2616 pubkey: "".into(),
2617 windows: Default::default(),
2618 }
2619 }
2620}
2621
2622#[skip_serializing_none]
2626#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2627#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2628#[serde(rename_all = "camelCase", deny_unknown_fields)]
2629pub struct SystemTrayConfig {
2630 #[serde(alias = "icon-path")]
2632 pub icon_path: PathBuf,
2633 #[serde(default, alias = "icon-as-template")]
2635 pub icon_as_template: bool,
2636 #[serde(default = "default_true", alias = "menu-on-left-click")]
2638 pub menu_on_left_click: bool,
2639 pub title: Option<String>,
2641}
2642
2643#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2645#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2646#[serde(untagged, deny_unknown_fields)]
2647#[non_exhaustive]
2648pub enum AppUrl {
2649 Url(WindowUrl),
2651 Files(Vec<PathBuf>),
2653}
2654
2655impl std::fmt::Display for AppUrl {
2656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2657 match self {
2658 Self::Url(url) => write!(f, "{url}"),
2659 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2660 }
2661 }
2662}
2663
2664#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2666#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2667#[serde(rename_all = "camelCase", untagged)]
2668pub enum BeforeDevCommand {
2669 Script(String),
2671 ScriptWithOptions {
2673 script: String,
2675 cwd: Option<String>,
2677 #[serde(default)]
2679 wait: bool,
2680 },
2681}
2682
2683#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2685#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2686#[serde(rename_all = "camelCase", untagged)]
2687pub enum HookCommand {
2688 Script(String),
2690 ScriptWithOptions {
2692 script: String,
2694 cwd: Option<String>,
2696 },
2697}
2698
2699#[skip_serializing_none]
2703#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2704#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2705#[serde(rename_all = "camelCase", deny_unknown_fields)]
2706pub struct BuildConfig {
2707 pub runner: Option<String>,
2709 #[serde(default = "default_dev_path", alias = "dev-path")]
2717 pub dev_path: AppUrl,
2718 #[serde(default = "default_dist_dir", alias = "dist-dir")]
2730 pub dist_dir: AppUrl,
2731 #[serde(alias = "before-dev-command")]
2735 pub before_dev_command: Option<BeforeDevCommand>,
2736 #[serde(alias = "before-build-command")]
2740 pub before_build_command: Option<HookCommand>,
2741 #[serde(alias = "before-bundle-command")]
2745 pub before_bundle_command: Option<HookCommand>,
2746 pub features: Option<Vec<String>>,
2748 #[serde(default, alias = "with-global-tauri")]
2750 pub with_global_tauri: bool,
2751}
2752
2753impl Default for BuildConfig {
2754 fn default() -> Self {
2755 Self {
2756 runner: None,
2757 dev_path: default_dev_path(),
2758 dist_dir: default_dist_dir(),
2759 before_dev_command: None,
2760 before_build_command: None,
2761 before_bundle_command: None,
2762 features: None,
2763 with_global_tauri: false,
2764 }
2765 }
2766}
2767
2768fn default_dev_path() -> AppUrl {
2769 AppUrl::Url(WindowUrl::External(
2770 Url::parse("http://localhost:8080").unwrap(),
2771 ))
2772}
2773
2774fn default_dist_dir() -> AppUrl {
2775 AppUrl::Url(WindowUrl::App("../dist".into()))
2776}
2777
2778#[derive(Debug, PartialEq, Eq)]
2779struct PackageVersion(String);
2780
2781impl<'d> serde::Deserialize<'d> for PackageVersion {
2782 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<PackageVersion, D::Error> {
2783 struct PackageVersionVisitor;
2784
2785 impl<'d> Visitor<'d> for PackageVersionVisitor {
2786 type Value = PackageVersion;
2787
2788 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2789 write!(
2790 formatter,
2791 "a semver string or a path to a package.json file"
2792 )
2793 }
2794
2795 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2796 let path = PathBuf::from(value);
2797 if path.exists() {
2798 let json_str = read_to_string(&path)
2799 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2800 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2801 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2802 if let Some(obj) = package_json.as_object() {
2803 let version = obj
2804 .get("version")
2805 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2806 .as_str()
2807 .ok_or_else(|| {
2808 DeError::custom(format!("`{} > version` must be a string", path.display()))
2809 })?;
2810 Ok(PackageVersion(
2811 Version::from_str(version)
2812 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2813 .to_string(),
2814 ))
2815 } else {
2816 Err(DeError::custom(
2817 "`package > version` value is not a path to a JSON object",
2818 ))
2819 }
2820 } else {
2821 Ok(PackageVersion(
2822 Version::from_str(value)
2823 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2824 .to_string(),
2825 ))
2826 }
2827 }
2828 }
2829
2830 deserializer.deserialize_string(PackageVersionVisitor {})
2831 }
2832}
2833
2834#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2838#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2839#[serde(rename_all = "camelCase", deny_unknown_fields)]
2840pub struct PackageConfig {
2841 #[serde(alias = "product-name")]
2843 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2844 pub product_name: Option<String>,
2845 #[serde(deserialize_with = "version_deserializer", default)]
2847 pub version: Option<String>,
2848}
2849
2850fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2851where
2852 D: Deserializer<'de>,
2853{
2854 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2855}
2856
2857#[allow(rustdoc::invalid_codeblock_attributes)]
2929#[skip_serializing_none]
2930#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2931#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2932#[serde(rename_all = "camelCase", deny_unknown_fields)]
2933pub struct Config {
2934 #[serde(rename = "$schema")]
2936 pub schema: Option<String>,
2937 #[serde(default)]
2939 pub package: PackageConfig,
2940 #[serde(default)]
2942 pub tauri: TauriConfig,
2943 #[serde(default = "default_build")]
2945 pub build: BuildConfig,
2946 #[serde(default)]
2948 pub plugins: PluginConfig,
2949}
2950
2951#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2955#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2956pub struct PluginConfig(pub HashMap<String, JsonValue>);
2957
2958fn default_build() -> BuildConfig {
2959 BuildConfig {
2960 runner: None,
2961 dev_path: default_dev_path(),
2962 dist_dir: default_dist_dir(),
2963 before_dev_command: None,
2964 before_build_command: None,
2965 before_bundle_command: None,
2966 features: None,
2967 with_global_tauri: false,
2968 }
2969}
2970
2971#[derive(Debug, Clone, PartialEq, Eq, Default)]
2973#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2974pub enum TitleBarStyle {
2975 #[default]
2977 Visible,
2978 Transparent,
2982 Overlay,
2989}
2990
2991impl Serialize for TitleBarStyle {
2992 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2993 where
2994 S: Serializer,
2995 {
2996 serializer.serialize_str(self.to_string().as_ref())
2997 }
2998}
2999
3000impl<'de> Deserialize<'de> for TitleBarStyle {
3001 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
3002 where
3003 D: Deserializer<'de>,
3004 {
3005 let s = String::deserialize(deserializer)?;
3006 Ok(match s.to_lowercase().as_str() {
3007 "transparent" => Self::Transparent,
3008 "overlay" => Self::Overlay,
3009 _ => Self::Visible,
3010 })
3011 }
3012}
3013
3014impl Display for TitleBarStyle {
3015 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3016 write!(
3017 f,
3018 "{}",
3019 match self {
3020 Self::Visible => "Visible",
3021 Self::Transparent => "Transparent",
3022 Self::Overlay => "Overlay",
3023 }
3024 )
3025 }
3026}
3027
3028#[derive(Debug, Copy, Clone, PartialEq, Eq)]
3030#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
3031#[non_exhaustive]
3032pub enum Theme {
3033 Light,
3035 Dark,
3037}
3038
3039impl Serialize for Theme {
3040 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3041 where
3042 S: Serializer,
3043 {
3044 serializer.serialize_str(self.to_string().as_ref())
3045 }
3046}
3047
3048impl<'de> Deserialize<'de> for Theme {
3049 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
3050 where
3051 D: Deserializer<'de>,
3052 {
3053 let s = String::deserialize(deserializer)?;
3054 Ok(match s.to_lowercase().as_str() {
3055 "dark" => Self::Dark,
3056 _ => Self::Light,
3057 })
3058 }
3059}
3060
3061impl Display for Theme {
3062 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3063 write!(
3064 f,
3065 "{}",
3066 match self {
3067 Self::Light => "light",
3068 Self::Dark => "dark",
3069 }
3070 )
3071 }
3072}
3073
3074#[cfg(test)]
3075mod test {
3076 use super::*;
3077
3078 #[test]
3081 fn test_defaults() {
3083 let t_config = TauriConfig::default();
3085 let b_config = BuildConfig::default();
3087 let d_path = default_dev_path();
3089 let d_windows: Vec<WindowConfig> = vec![];
3091 let d_bundle = BundleConfig::default();
3093 let d_updater = UpdaterConfig::default();
3095
3096 let tauri = TauriConfig {
3098 pattern: Default::default(),
3099 windows: vec![],
3100 bundle: BundleConfig {
3101 active: false,
3102 targets: Default::default(),
3103 identifier: String::from(""),
3104 publisher: None,
3105 icon: Vec::new(),
3106 resources: None,
3107 copyright: None,
3108 category: None,
3109 short_description: None,
3110 long_description: None,
3111 appimage: Default::default(),
3112 deb: Default::default(),
3113 macos: Default::default(),
3114 external_bin: None,
3115 windows: Default::default(),
3116 },
3117 cli: None,
3118 updater: UpdaterConfig {
3119 active: false,
3120 dialog: true,
3121 pubkey: "".into(),
3122 endpoints: None,
3123 windows: Default::default(),
3124 },
3125 security: SecurityConfig {
3126 csp: None,
3127 dev_csp: None,
3128 freeze_prototype: false,
3129 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3130 dangerous_remote_domain_ipc_access: Vec::new(),
3131 dangerous_use_http_scheme: false,
3132 },
3133 allowlist: AllowlistConfig::default(),
3134 system_tray: None,
3135 macos_private_api: false,
3136 };
3137
3138 let build = BuildConfig {
3140 runner: None,
3141 dev_path: AppUrl::Url(WindowUrl::External(
3142 Url::parse("http://localhost:8080").unwrap(),
3143 )),
3144 dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())),
3145 before_dev_command: None,
3146 before_build_command: None,
3147 before_bundle_command: None,
3148 features: None,
3149 with_global_tauri: false,
3150 };
3151
3152 assert_eq!(t_config, tauri);
3154 assert_eq!(b_config, build);
3155 assert_eq!(d_bundle, tauri.bundle);
3156 assert_eq!(d_updater, tauri.updater);
3157 assert_eq!(
3158 d_path,
3159 AppUrl::Url(WindowUrl::External(
3160 Url::parse("http://localhost:8080").unwrap()
3161 ))
3162 );
3163 assert_eq!(d_windows, tauri.windows);
3164 }
3165}