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)]
135pub enum BundleTarget {
136 All,
138 List(Vec<BundleType>),
140 One(BundleType),
142}
143
144#[cfg(feature = "schemars")]
145pub(crate) trait Merge: Sized {
146 fn merge(self, other: Self) -> Self;
147}
148
149#[cfg(feature = "schema")]
150use schemars::schema::{Metadata, Schema};
151
152#[cfg(feature = "schema")]
153impl<T: Merge> Merge for Option<T> {
154 fn merge(self, other: Self) -> Self {
155 match (self, other) {
156 (Some(x), Some(y)) => Some(x.merge(y)),
157 (None, y) => y,
158 (x, None) => x,
159 }
160 }
161}
162
163#[cfg(feature = "schema")]
164impl<T: Merge> Merge for Box<T> {
165 fn merge(mut self, other: Self) -> Self {
166 *self = (*self).merge(*other);
167 self
168 }
169}
170
171#[cfg(feature = "schema")]
172impl<T> Merge for Vec<T> {
173 fn merge(mut self, other: Self) -> Self {
174 self.extend(other);
175 self
176 }
177}
178
179#[cfg(feature = "schema")]
180impl Merge for Metadata {
181 fn merge(self, other: Self) -> Self {
182 Metadata {
183 id: self.id.or(other.id),
184 title: self.title.or(other.title),
185 description: self.description.or(other.description),
186 default: self.default.or(other.default),
187 deprecated: self.deprecated || other.deprecated,
188 read_only: self.read_only || other.read_only,
189 write_only: self.write_only || other.write_only,
190 examples: self.examples.merge(other.examples),
191 }
192 }
193}
194
195#[cfg(feature = "schema")]
196fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {
197 if metadata == Metadata::default() {
198 schema
199 } else {
200 let mut schema_obj = schema.into_object();
201 schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
202 Schema::Object(schema_obj)
203 }
204}
205
206#[cfg(feature = "schema")]
207impl schemars::JsonSchema for BundleTarget {
208 fn schema_name() -> std::string::String {
209 "BundleTarget".to_owned()
210 }
211
212 fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
213 let any_of = vec![
214 schemars::schema::SchemaObject {
215 enum_values: Some(vec!["all".into()]),
216 metadata: Some(Box::new(schemars::schema::Metadata {
217 description: Some("Bundle all targets.".to_owned()),
218 ..Default::default()
219 })),
220 ..Default::default()
221 }
222 .into(),
223 apply_metadata(
224 generator.subschema_for::<Vec<BundleType>>(),
225 schemars::schema::Metadata {
226 description: Some("A list of bundle targets.".to_owned()),
227 ..Default::default()
228 },
229 ),
230 apply_metadata(
231 generator.subschema_for::<BundleType>(),
232 schemars::schema::Metadata {
233 description: Some("A single bundle target.".to_owned()),
234 ..Default::default()
235 },
236 ),
237 ];
238
239 schemars::schema::SchemaObject {
240 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
241 any_of: Some(any_of),
242 ..Default::default()
243 })),
244 metadata: Some(Box::new(schemars::schema::Metadata {
245 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
246 ..Default::default()
247 })),
248 ..Default::default()
249 }
250 .into()
251 }
252}
253
254impl Default for BundleTarget {
255 fn default() -> Self {
256 Self::All
257 }
258}
259
260impl Serialize for BundleTarget {
261 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
262 where
263 S: Serializer,
264 {
265 match self {
266 Self::All => serializer.serialize_str("all"),
267 Self::List(l) => l.serialize(serializer),
268 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
269 }
270 }
271}
272
273impl<'de> Deserialize<'de> for BundleTarget {
274 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
275 where
276 D: Deserializer<'de>,
277 {
278 #[derive(Deserialize, Serialize)]
279 #[serde(untagged)]
280 pub enum BundleTargetInner {
281 List(Vec<BundleType>),
282 One(BundleType),
283 All(String),
284 }
285
286 match BundleTargetInner::deserialize(deserializer)? {
287 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
288 BundleTargetInner::All(t) => Err(DeError::custom(format!("invalid bundle type {t}"))),
289 BundleTargetInner::List(l) => Ok(Self::List(l)),
290 BundleTargetInner::One(t) => Ok(Self::One(t)),
291 }
292 }
293}
294
295#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
299#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
300#[serde(rename_all = "camelCase", deny_unknown_fields)]
301pub struct AppImageConfig {
302 #[serde(default, alias = "bundle-media-framework")]
305 pub bundle_media_framework: bool,
306}
307
308#[skip_serializing_none]
312#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
313#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
314#[serde(rename_all = "camelCase", deny_unknown_fields)]
315pub struct DebConfig {
316 pub depends: Option<Vec<String>>,
318 #[serde(default)]
320 pub files: HashMap<PathBuf, PathBuf>,
321 pub desktop_template: Option<PathBuf>,
325 pub section: Option<String>,
327 pub priority: Option<String>,
330 pub changelog: Option<PathBuf>,
333}
334
335fn de_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
336where
337 D: Deserializer<'de>,
338{
339 let version = Option::<String>::deserialize(deserializer)?;
340 match version {
341 Some(v) if v.is_empty() => Ok(minimum_system_version()),
342 e => Ok(e),
343 }
344}
345
346#[skip_serializing_none]
350#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
352#[serde(rename_all = "camelCase", deny_unknown_fields)]
353pub struct MacConfig {
354 pub frameworks: Option<Vec<String>>,
358 #[serde(
365 deserialize_with = "de_minimum_system_version",
366 default = "minimum_system_version",
367 alias = "minimum-system-version"
368 )]
369 pub minimum_system_version: Option<String>,
370 #[serde(alias = "exception-domain")]
373 pub exception_domain: Option<String>,
374 pub license: Option<String>,
376 #[serde(alias = "signing-identity")]
378 pub signing_identity: Option<String>,
379 #[serde(alias = "provider-short-name")]
381 pub provider_short_name: Option<String>,
382 pub entitlements: Option<String>,
384}
385
386impl Default for MacConfig {
387 fn default() -> Self {
388 Self {
389 frameworks: None,
390 minimum_system_version: minimum_system_version(),
391 exception_domain: None,
392 license: None,
393 signing_identity: None,
394 provider_short_name: None,
395 entitlements: None,
396 }
397 }
398}
399
400fn minimum_system_version() -> Option<String> {
401 Some("10.13".into())
402}
403
404#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
408#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
409#[serde(rename_all = "camelCase", deny_unknown_fields)]
410pub struct WixLanguageConfig {
411 #[serde(alias = "locale-path")]
413 pub locale_path: Option<String>,
414}
415
416#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
418#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
419#[serde(untagged)]
420pub enum WixLanguage {
421 One(String),
423 List(Vec<String>),
425 Localized(HashMap<String, WixLanguageConfig>),
427}
428
429impl Default for WixLanguage {
430 fn default() -> Self {
431 Self::One("en-US".into())
432 }
433}
434
435#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
439#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
440#[serde(rename_all = "camelCase", deny_unknown_fields)]
441pub struct WixConfig {
442 #[serde(default)]
444 pub language: WixLanguage,
445 pub template: Option<PathBuf>,
447 #[serde(default, alias = "fragment-paths")]
449 pub fragment_paths: Vec<PathBuf>,
450 #[serde(default, alias = "component-group-refs")]
452 pub component_group_refs: Vec<String>,
453 #[serde(default, alias = "component-refs")]
455 pub component_refs: Vec<String>,
456 #[serde(default, alias = "feature-group-refs")]
458 pub feature_group_refs: Vec<String>,
459 #[serde(default, alias = "feature-refs")]
461 pub feature_refs: Vec<String>,
462 #[serde(default, alias = "merge-refs")]
464 pub merge_refs: Vec<String>,
465 #[serde(default, alias = "skip-webview-install")]
469 pub skip_webview_install: bool,
470 pub license: Option<PathBuf>,
474 #[serde(default, alias = "enable-elevated-update-task")]
476 pub enable_elevated_update_task: bool,
477 #[serde(alias = "banner-path")]
482 pub banner_path: Option<PathBuf>,
483 #[serde(alias = "dialog-image-path")]
488 pub dialog_image_path: Option<PathBuf>,
489}
490
491#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
495#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
496#[serde(rename_all = "camelCase", deny_unknown_fields)]
497pub enum NsisCompression {
498 Zlib,
500 Bzip2,
502 Lzma,
504}
505
506#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
508#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
509#[serde(rename_all = "camelCase", deny_unknown_fields)]
510pub struct NsisConfig {
511 pub template: Option<PathBuf>,
513 pub license: Option<PathBuf>,
515 #[serde(alias = "header-image")]
519 pub header_image: Option<PathBuf>,
520 #[serde(alias = "sidebar-image")]
524 pub sidebar_image: Option<PathBuf>,
525 #[serde(alias = "install-icon")]
527 pub installer_icon: Option<PathBuf>,
528 #[serde(default, alias = "install-mode")]
530 pub install_mode: NSISInstallerMode,
531 pub languages: Option<Vec<String>>,
537 pub custom_language_files: Option<HashMap<String, PathBuf>>,
544 #[serde(default, alias = "display-language-selector")]
547 pub display_language_selector: bool,
548 pub compression: Option<NsisCompression>,
552}
553
554#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
556#[serde(rename_all = "camelCase", deny_unknown_fields)]
557#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
558pub enum NSISInstallerMode {
559 CurrentUser,
565 PerMachine,
570 Both,
576}
577
578impl Default for NSISInstallerMode {
579 fn default() -> Self {
580 Self::CurrentUser
581 }
582}
583
584#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
589#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
590#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
591pub enum WebviewInstallMode {
592 Skip,
594 DownloadBootstrapper {
598 #[serde(default = "default_true")]
600 silent: bool,
601 },
602 EmbedBootstrapper {
606 #[serde(default = "default_true")]
608 silent: bool,
609 },
610 OfflineInstaller {
614 #[serde(default = "default_true")]
616 silent: bool,
617 },
618 FixedRuntime {
621 path: PathBuf,
626 },
627}
628
629impl Default for WebviewInstallMode {
630 fn default() -> Self {
631 Self::DownloadBootstrapper { silent: true }
632 }
633}
634
635#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
639#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
640#[serde(rename_all = "camelCase", deny_unknown_fields)]
641pub struct WindowsConfig {
642 #[serde(alias = "digest-algorithm")]
645 pub digest_algorithm: Option<String>,
646 #[serde(alias = "certificate-thumbprint")]
648 pub certificate_thumbprint: Option<String>,
649 #[serde(alias = "timestamp-url")]
651 pub timestamp_url: Option<String>,
652 #[serde(default)]
655 pub tsp: bool,
656 #[serde(default, alias = "webview-install-mode")]
658 pub webview_install_mode: WebviewInstallMode,
659 #[serde(alias = "webview-fixed-runtime-path")]
666 pub webview_fixed_runtime_path: Option<PathBuf>,
667 #[serde(default = "default_true", alias = "allow-downgrades")]
673 pub allow_downgrades: bool,
674 pub wix: Option<WixConfig>,
676 pub nsis: Option<NsisConfig>,
678}
679
680impl Default for WindowsConfig {
681 fn default() -> Self {
682 Self {
683 digest_algorithm: None,
684 certificate_thumbprint: None,
685 timestamp_url: None,
686 tsp: false,
687 webview_install_mode: Default::default(),
688 webview_fixed_runtime_path: None,
689 allow_downgrades: true,
690 wix: None,
691 nsis: None,
692 }
693 }
694}
695
696#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
699#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
700#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
701pub enum BundleResources {
702 List(Vec<String>),
704 Map(HashMap<String, String>),
706}
707
708#[skip_serializing_none]
712#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
713#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
714#[serde(rename_all = "camelCase", deny_unknown_fields)]
715pub struct BundleConfig {
716 #[serde(default)]
718 pub active: bool,
719 #[serde(default)]
721 pub targets: BundleTarget,
722 pub identifier: String,
728 pub publisher: Option<String>,
731 #[serde(default)]
733 pub icon: Vec<String>,
734 pub resources: Option<BundleResources>,
738 pub copyright: Option<String>,
740 pub category: Option<String>,
745 #[serde(alias = "short-description")]
747 pub short_description: Option<String>,
748 #[serde(alias = "long-description")]
750 pub long_description: Option<String>,
751 #[serde(default)]
753 pub appimage: AppImageConfig,
754 #[serde(default)]
756 pub deb: DebConfig,
757 #[serde(rename = "macOS", default)]
759 pub macos: MacConfig,
760 #[serde(alias = "external-bin")]
772 pub external_bin: Option<Vec<String>>,
773 #[serde(default)]
775 pub windows: WindowsConfig,
776}
777
778#[skip_serializing_none]
780#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
781#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
782#[serde(rename_all = "camelCase", deny_unknown_fields)]
783pub struct CliArg {
784 pub short: Option<char>,
788 pub name: String,
790 pub description: Option<String>,
793 #[serde(alias = "long-description")]
796 pub long_description: Option<String>,
797 #[serde(default, alias = "takes-value")]
804 pub takes_value: bool,
805 #[serde(default)]
811 pub multiple: bool,
812 #[serde(default, alias = "multiple-occurrences")]
818 pub multiple_occurrences: bool,
819 #[serde(alias = "number-of-values")]
830 pub number_of_values: Option<usize>,
831 #[serde(alias = "possible-values")]
834 pub possible_values: Option<Vec<String>>,
835 #[serde(alias = "min-values")]
839 pub min_values: Option<usize>,
840 #[serde(alias = "max-values")]
844 pub max_values: Option<usize>,
845 #[serde(default)]
850 pub required: bool,
851 #[serde(alias = "required-unless-present")]
854 pub required_unless_present: Option<String>,
855 #[serde(alias = "required-unless-present-all")]
858 pub required_unless_present_all: Option<Vec<String>>,
859 #[serde(alias = "required-unless-present-any")]
862 pub required_unless_present_any: Option<Vec<String>>,
863 #[serde(alias = "conflicts-with")]
866 pub conflicts_with: Option<String>,
867 #[serde(alias = "conflicts-with-all")]
869 pub conflicts_with_all: Option<Vec<String>>,
870 pub requires: Option<String>,
873 #[serde(alias = "requires-all")]
876 pub requires_all: Option<Vec<String>>,
877 #[serde(alias = "requires-if")]
880 pub requires_if: Option<Vec<String>>,
881 #[serde(alias = "requires-if-eq")]
884 pub required_if_eq: Option<Vec<String>>,
885 #[serde(alias = "requires-equals")]
888 pub require_equals: Option<bool>,
889 #[cfg_attr(feature = "schema", validate(range(min = 1)))]
895 pub index: Option<usize>,
896}
897
898#[skip_serializing_none]
902#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
903#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
904#[serde(rename_all = "camelCase", deny_unknown_fields)]
905pub struct CliConfig {
906 pub description: Option<String>,
908 #[serde(alias = "long-description")]
910 pub long_description: Option<String>,
911 #[serde(alias = "before-help")]
915 pub before_help: Option<String>,
916 #[serde(alias = "after-help")]
920 pub after_help: Option<String>,
921 pub args: Option<Vec<CliArg>>,
923 pub subcommands: Option<HashMap<String, CliConfig>>,
925}
926
927#[skip_serializing_none]
931#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
932#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
933#[serde(rename_all = "camelCase", deny_unknown_fields)]
934pub struct WindowConfig {
935 #[serde(default = "default_window_label")]
937 pub label: String,
938 #[serde(default)]
940 pub url: WindowUrl,
941 #[serde(alias = "user-agent")]
943 pub user_agent: Option<String>,
944 #[serde(default = "default_true", alias = "file-drop-enabled")]
948 pub file_drop_enabled: bool,
949 #[serde(default)]
951 pub center: bool,
952 pub x: Option<f64>,
954 pub y: Option<f64>,
956 #[serde(default = "default_width")]
958 pub width: f64,
959 #[serde(default = "default_height")]
961 pub height: f64,
962 #[serde(alias = "min-width")]
964 pub min_width: Option<f64>,
965 #[serde(alias = "min-height")]
967 pub min_height: Option<f64>,
968 #[serde(alias = "max-width")]
970 pub max_width: Option<f64>,
971 #[serde(alias = "max-height")]
973 pub max_height: Option<f64>,
974 #[serde(default = "default_true")]
976 pub resizable: bool,
977 #[serde(default = "default_true")]
985 pub maximizable: bool,
986 #[serde(default = "default_true")]
992 pub minimizable: bool,
993 #[serde(default = "default_true")]
1001 pub closable: bool,
1002 #[serde(default = "default_title")]
1004 pub title: String,
1005 #[serde(default)]
1007 pub fullscreen: bool,
1008 #[serde(default = "default_true")]
1010 pub focus: bool,
1011 #[serde(default)]
1016 pub transparent: bool,
1017 #[serde(default)]
1019 pub maximized: bool,
1020 #[serde(default = "default_true")]
1022 pub visible: bool,
1023 #[serde(default = "default_true")]
1025 pub decorations: bool,
1026 #[serde(default, alias = "always-on-top")]
1028 pub always_on_top: bool,
1029 #[serde(default, alias = "content-protected")]
1031 pub content_protected: bool,
1032 #[serde(default, alias = "skip-taskbar")]
1034 pub skip_taskbar: bool,
1035 pub theme: Option<Theme>,
1037 #[serde(default, alias = "title-bar-style")]
1039 pub title_bar_style: TitleBarStyle,
1040 #[serde(default, alias = "hidden-title")]
1042 pub hidden_title: bool,
1043 #[serde(default, alias = "accept-first-mouse")]
1045 pub accept_first_mouse: bool,
1046 #[serde(default, alias = "tabbing-identifier")]
1053 pub tabbing_identifier: Option<String>,
1054 #[serde(default, alias = "additional-browser-args")]
1057 pub additional_browser_args: Option<String>,
1058}
1059
1060impl Default for WindowConfig {
1061 fn default() -> Self {
1062 Self {
1063 label: default_window_label(),
1064 url: WindowUrl::default(),
1065 user_agent: None,
1066 file_drop_enabled: true,
1067 center: false,
1068 x: None,
1069 y: None,
1070 width: default_width(),
1071 height: default_height(),
1072 min_width: None,
1073 min_height: None,
1074 max_width: None,
1075 max_height: None,
1076 resizable: true,
1077 maximizable: true,
1078 minimizable: true,
1079 closable: true,
1080 title: default_title(),
1081 fullscreen: false,
1082 focus: false,
1083 transparent: false,
1084 maximized: false,
1085 visible: true,
1086 decorations: true,
1087 always_on_top: false,
1088 content_protected: false,
1089 skip_taskbar: false,
1090 theme: None,
1091 title_bar_style: Default::default(),
1092 hidden_title: false,
1093 accept_first_mouse: false,
1094 tabbing_identifier: None,
1095 additional_browser_args: None,
1096 }
1097 }
1098}
1099
1100fn default_window_label() -> String {
1101 "main".to_string()
1102}
1103
1104fn default_width() -> f64 {
1105 800f64
1106}
1107
1108fn default_height() -> f64 {
1109 600f64
1110}
1111
1112fn default_title() -> String {
1113 "Tauri App".to_string()
1114}
1115
1116#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1120#[serde(rename_all = "camelCase", untagged)]
1121pub enum CspDirectiveSources {
1122 Inline(String),
1124 List(Vec<String>),
1126}
1127
1128impl Default for CspDirectiveSources {
1129 fn default() -> Self {
1130 Self::List(Vec::new())
1131 }
1132}
1133
1134impl From<CspDirectiveSources> for Vec<String> {
1135 fn from(sources: CspDirectiveSources) -> Self {
1136 match sources {
1137 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1138 CspDirectiveSources::List(l) => l,
1139 }
1140 }
1141}
1142
1143impl CspDirectiveSources {
1144 pub fn contains(&self, source: &str) -> bool {
1146 match self {
1147 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1148 Self::List(l) => l.contains(&source.into()),
1149 }
1150 }
1151
1152 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1154 match self {
1155 Self::Inline(s) => {
1156 s.push(' ');
1157 s.push_str(source.as_ref());
1158 }
1159 Self::List(l) => {
1160 l.push(source.as_ref().to_string());
1161 }
1162 }
1163 }
1164}
1165
1166#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1169#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1170#[serde(rename_all = "camelCase", untagged)]
1171pub enum Csp {
1172 Policy(String),
1174 DirectiveMap(HashMap<String, CspDirectiveSources>),
1176}
1177
1178impl From<HashMap<String, CspDirectiveSources>> for Csp {
1179 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1180 Self::DirectiveMap(map)
1181 }
1182}
1183
1184impl From<Csp> for HashMap<String, CspDirectiveSources> {
1185 fn from(csp: Csp) -> Self {
1186 match csp {
1187 Csp::Policy(policy) => {
1188 let mut map = HashMap::new();
1189 for directive in policy.split(';') {
1190 let mut tokens = directive.trim().split(' ');
1191 if let Some(directive) = tokens.next() {
1192 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1193 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1194 }
1195 }
1196 map
1197 }
1198 Csp::DirectiveMap(m) => m,
1199 }
1200 }
1201}
1202
1203impl Display for Csp {
1204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1205 match self {
1206 Self::Policy(s) => write!(f, "{s}"),
1207 Self::DirectiveMap(m) => {
1208 let len = m.len();
1209 let mut i = 0;
1210 for (directive, sources) in m {
1211 let sources: Vec<String> = sources.clone().into();
1212 write!(f, "{} {}", directive, sources.join(" "))?;
1213 i += 1;
1214 if i != len {
1215 write!(f, "; ")?;
1216 }
1217 }
1218 Ok(())
1219 }
1220 }
1221 }
1222}
1223
1224#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1226#[serde(untagged)]
1227#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1228pub enum DisabledCspModificationKind {
1229 Flag(bool),
1232 List(Vec<String>),
1234}
1235
1236impl Default for DisabledCspModificationKind {
1237 fn default() -> Self {
1238 Self::Flag(false)
1239 }
1240}
1241
1242#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1244#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1245#[serde(rename_all = "camelCase", deny_unknown_fields)]
1246pub struct RemoteDomainAccessScope {
1247 pub scheme: Option<String>,
1249 pub domain: String,
1251 pub windows: Vec<String>,
1253 #[serde(default)]
1256 pub plugins: Vec<String>,
1257 #[serde(default, rename = "enableTauriAPI", alias = "enable-tauri-api")]
1259 pub enable_tauri_api: bool,
1260}
1261
1262#[skip_serializing_none]
1266#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1267#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1268#[serde(rename_all = "camelCase", deny_unknown_fields)]
1269pub struct SecurityConfig {
1270 pub csp: Option<Csp>,
1276 #[serde(alias = "dev-csp")]
1281 pub dev_csp: Option<Csp>,
1282 #[serde(default, alias = "freeze-prototype")]
1284 pub freeze_prototype: bool,
1285 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
1298 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
1299 #[serde(default, alias = "dangerous-remote-domain-ipc-access")]
1312 pub dangerous_remote_domain_ipc_access: Vec<RemoteDomainAccessScope>,
1313 #[serde(default, alias = "dangerous-use-http-scheme")]
1317 pub dangerous_use_http_scheme: bool,
1318}
1319
1320pub trait Allowlist {
1322 fn all_features() -> Vec<&'static str>;
1324 fn to_features(&self) -> Vec<&'static str>;
1326}
1327
1328macro_rules! check_feature {
1329 ($self:ident, $features:ident, $flag:ident, $feature_name: expr) => {
1330 if $self.$flag {
1331 $features.push($feature_name)
1332 }
1333 };
1334}
1335
1336#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1345#[serde(untagged)]
1346#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1347pub enum FsAllowlistScope {
1348 AllowedPaths(Vec<PathBuf>),
1350 #[serde(rename_all = "camelCase")]
1352 Scope {
1353 #[serde(default)]
1355 allow: Vec<PathBuf>,
1356 #[serde(default)]
1359 deny: Vec<PathBuf>,
1360 #[serde(alias = "require-literal-leading-dot")]
1369 require_literal_leading_dot: Option<bool>,
1370 },
1371}
1372
1373impl Default for FsAllowlistScope {
1374 fn default() -> Self {
1375 Self::AllowedPaths(Vec::new())
1376 }
1377}
1378
1379#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1384#[serde(rename_all = "camelCase", deny_unknown_fields)]
1385pub struct FsAllowlistConfig {
1386 #[serde(default)]
1388 pub scope: FsAllowlistScope,
1389 #[serde(default)]
1391 pub all: bool,
1392 #[serde(default, alias = "read-file")]
1394 pub read_file: bool,
1395 #[serde(default, alias = "write-file")]
1397 pub write_file: bool,
1398 #[serde(default, alias = "read-dir")]
1400 pub read_dir: bool,
1401 #[serde(default, alias = "copy-file")]
1403 pub copy_file: bool,
1404 #[serde(default, alias = "create-dir")]
1406 pub create_dir: bool,
1407 #[serde(default, alias = "remove-dir")]
1409 pub remove_dir: bool,
1410 #[serde(default, alias = "remove-file")]
1412 pub remove_file: bool,
1413 #[serde(default, alias = "rename-file")]
1415 pub rename_file: bool,
1416 #[serde(default)]
1418 pub exists: bool,
1419}
1420
1421impl Allowlist for FsAllowlistConfig {
1422 fn all_features() -> Vec<&'static str> {
1423 let allowlist = Self {
1424 scope: Default::default(),
1425 all: false,
1426 read_file: true,
1427 write_file: true,
1428 read_dir: true,
1429 copy_file: true,
1430 create_dir: true,
1431 remove_dir: true,
1432 remove_file: true,
1433 rename_file: true,
1434 exists: true,
1435 };
1436 let mut features = allowlist.to_features();
1437 features.push("fs-all");
1438 features
1439 }
1440
1441 fn to_features(&self) -> Vec<&'static str> {
1442 if self.all {
1443 vec!["fs-all"]
1444 } else {
1445 let mut features = Vec::new();
1446 check_feature!(self, features, read_file, "fs-read-file");
1447 check_feature!(self, features, write_file, "fs-write-file");
1448 check_feature!(self, features, read_dir, "fs-read-dir");
1449 check_feature!(self, features, copy_file, "fs-copy-file");
1450 check_feature!(self, features, create_dir, "fs-create-dir");
1451 check_feature!(self, features, remove_dir, "fs-remove-dir");
1452 check_feature!(self, features, remove_file, "fs-remove-file");
1453 check_feature!(self, features, rename_file, "fs-rename-file");
1454 check_feature!(self, features, exists, "fs-exists");
1455 features
1456 }
1457 }
1458}
1459
1460#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1464#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1465#[serde(rename_all = "camelCase", deny_unknown_fields)]
1466pub struct WindowAllowlistConfig {
1467 #[serde(default)]
1469 pub all: bool,
1470 #[serde(default)]
1472 pub create: bool,
1473 #[serde(default)]
1475 pub center: bool,
1476 #[serde(default, alias = "request-user-attention")]
1478 pub request_user_attention: bool,
1479 #[serde(default, alias = "set-resizable")]
1481 pub set_resizable: bool,
1482 #[serde(default, alias = "set-maximizable")]
1484 pub set_maximizable: bool,
1485 #[serde(default, alias = "set-minimizable")]
1487 pub set_minimizable: bool,
1488 #[serde(default, alias = "set-closable")]
1490 pub set_closable: bool,
1491 #[serde(default, alias = "set-title")]
1493 pub set_title: bool,
1494 #[serde(default)]
1496 pub maximize: bool,
1497 #[serde(default)]
1499 pub unmaximize: bool,
1500 #[serde(default)]
1502 pub minimize: bool,
1503 #[serde(default)]
1505 pub unminimize: bool,
1506 #[serde(default)]
1508 pub show: bool,
1509 #[serde(default)]
1511 pub hide: bool,
1512 #[serde(default)]
1514 pub close: bool,
1515 #[serde(default, alias = "set-decorations")]
1517 pub set_decorations: bool,
1518 #[serde(default, alias = "set-always-on-top")]
1520 pub set_always_on_top: bool,
1521 #[serde(default, alias = "set-content-protected")]
1523 pub set_content_protected: bool,
1524 #[serde(default, alias = "set-size")]
1526 pub set_size: bool,
1527 #[serde(default, alias = "set-min-size")]
1529 pub set_min_size: bool,
1530 #[serde(default, alias = "set-max-size")]
1532 pub set_max_size: bool,
1533 #[serde(default, alias = "set-position")]
1535 pub set_position: bool,
1536 #[serde(default, alias = "set-fullscreen")]
1538 pub set_fullscreen: bool,
1539 #[serde(default, alias = "set-focus")]
1541 pub set_focus: bool,
1542 #[serde(default, alias = "set-icon")]
1544 pub set_icon: bool,
1545 #[serde(default, alias = "set-skip-taskbar")]
1547 pub set_skip_taskbar: bool,
1548 #[serde(default, alias = "set-cursor-grab")]
1550 pub set_cursor_grab: bool,
1551 #[serde(default, alias = "set-cursor-visible")]
1553 pub set_cursor_visible: bool,
1554 #[serde(default, alias = "set-cursor-icon")]
1556 pub set_cursor_icon: bool,
1557 #[serde(default, alias = "set-cursor-position")]
1559 pub set_cursor_position: bool,
1560 #[serde(default, alias = "set-ignore-cursor-events")]
1562 pub set_ignore_cursor_events: bool,
1563 #[serde(default, alias = "start-dragging")]
1565 pub start_dragging: bool,
1566 #[serde(default)]
1568 pub print: bool,
1569}
1570
1571impl Allowlist for WindowAllowlistConfig {
1572 fn all_features() -> Vec<&'static str> {
1573 let allowlist = Self {
1574 all: false,
1575 create: true,
1576 center: true,
1577 request_user_attention: true,
1578 set_resizable: true,
1579 set_maximizable: true,
1580 set_minimizable: true,
1581 set_closable: true,
1582 set_title: true,
1583 maximize: true,
1584 unmaximize: true,
1585 minimize: true,
1586 unminimize: true,
1587 show: true,
1588 hide: true,
1589 close: true,
1590 set_decorations: true,
1591 set_always_on_top: true,
1592 set_content_protected: false,
1593 set_size: true,
1594 set_min_size: true,
1595 set_max_size: true,
1596 set_position: true,
1597 set_fullscreen: true,
1598 set_focus: true,
1599 set_icon: true,
1600 set_skip_taskbar: true,
1601 set_cursor_grab: true,
1602 set_cursor_visible: true,
1603 set_cursor_icon: true,
1604 set_cursor_position: true,
1605 set_ignore_cursor_events: true,
1606 start_dragging: true,
1607 print: true,
1608 };
1609 let mut features = allowlist.to_features();
1610 features.push("window-all");
1611 features
1612 }
1613
1614 fn to_features(&self) -> Vec<&'static str> {
1615 if self.all {
1616 vec!["window-all"]
1617 } else {
1618 let mut features = Vec::new();
1619 check_feature!(self, features, create, "window-create");
1620 check_feature!(self, features, center, "window-center");
1621 check_feature!(
1622 self,
1623 features,
1624 request_user_attention,
1625 "window-request-user-attention"
1626 );
1627 check_feature!(self, features, set_resizable, "window-set-resizable");
1628 check_feature!(self, features, set_maximizable, "window-set-maximizable");
1629 check_feature!(self, features, set_minimizable, "window-set-minimizable");
1630 check_feature!(self, features, set_closable, "window-set-closable");
1631 check_feature!(self, features, set_title, "window-set-title");
1632 check_feature!(self, features, maximize, "window-maximize");
1633 check_feature!(self, features, unmaximize, "window-unmaximize");
1634 check_feature!(self, features, minimize, "window-minimize");
1635 check_feature!(self, features, unminimize, "window-unminimize");
1636 check_feature!(self, features, show, "window-show");
1637 check_feature!(self, features, hide, "window-hide");
1638 check_feature!(self, features, close, "window-close");
1639 check_feature!(self, features, set_decorations, "window-set-decorations");
1640 check_feature!(
1641 self,
1642 features,
1643 set_always_on_top,
1644 "window-set-always-on-top"
1645 );
1646 check_feature!(
1647 self,
1648 features,
1649 set_content_protected,
1650 "window-set-content-protected"
1651 );
1652 check_feature!(self, features, set_size, "window-set-size");
1653 check_feature!(self, features, set_min_size, "window-set-min-size");
1654 check_feature!(self, features, set_max_size, "window-set-max-size");
1655 check_feature!(self, features, set_position, "window-set-position");
1656 check_feature!(self, features, set_fullscreen, "window-set-fullscreen");
1657 check_feature!(self, features, set_focus, "window-set-focus");
1658 check_feature!(self, features, set_icon, "window-set-icon");
1659 check_feature!(self, features, set_skip_taskbar, "window-set-skip-taskbar");
1660 check_feature!(self, features, set_cursor_grab, "window-set-cursor-grab");
1661 check_feature!(
1662 self,
1663 features,
1664 set_cursor_visible,
1665 "window-set-cursor-visible"
1666 );
1667 check_feature!(self, features, set_cursor_icon, "window-set-cursor-icon");
1668 check_feature!(
1669 self,
1670 features,
1671 set_cursor_position,
1672 "window-set-cursor-position"
1673 );
1674 check_feature!(
1675 self,
1676 features,
1677 set_ignore_cursor_events,
1678 "window-set-ignore-cursor-events"
1679 );
1680 check_feature!(self, features, start_dragging, "window-start-dragging");
1681 check_feature!(self, features, print, "window-print");
1682 features
1683 }
1684 }
1685}
1686
1687#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1689#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1690pub struct ShellAllowedCommand {
1691 pub name: String,
1696
1697 #[serde(rename = "cmd", default)] pub command: PathBuf,
1705
1706 #[serde(default)]
1708 pub args: ShellAllowedArgs,
1709
1710 #[serde(default)]
1712 pub sidecar: bool,
1713}
1714
1715impl<'de> Deserialize<'de> for ShellAllowedCommand {
1716 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1717 where
1718 D: Deserializer<'de>,
1719 {
1720 #[derive(Deserialize)]
1721 struct InnerShellAllowedCommand {
1722 name: String,
1723 #[serde(rename = "cmd")]
1724 command: Option<PathBuf>,
1725 #[serde(default)]
1726 args: ShellAllowedArgs,
1727 #[serde(default)]
1728 sidecar: bool,
1729 }
1730
1731 let config = InnerShellAllowedCommand::deserialize(deserializer)?;
1732
1733 if !config.sidecar && config.command.is_none() {
1734 return Err(DeError::custom(
1735 "The shell scope `command` value is required.",
1736 ));
1737 }
1738
1739 Ok(ShellAllowedCommand {
1740 name: config.name,
1741 command: config.command.unwrap_or_default(),
1742 args: config.args,
1743 sidecar: config.sidecar,
1744 })
1745 }
1746}
1747
1748#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1754#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1755#[serde(untagged, deny_unknown_fields)]
1756#[non_exhaustive]
1757pub enum ShellAllowedArgs {
1758 Flag(bool),
1760
1761 List(Vec<ShellAllowedArg>),
1763}
1764
1765impl Default for ShellAllowedArgs {
1766 fn default() -> Self {
1767 Self::Flag(false)
1768 }
1769}
1770
1771#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1773#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1774#[serde(untagged, deny_unknown_fields)]
1775#[non_exhaustive]
1776pub enum ShellAllowedArg {
1777 Fixed(String),
1779
1780 Var {
1783 validator: String,
1790 },
1791}
1792
1793#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1796#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1797pub struct ShellAllowlistScope(pub Vec<ShellAllowedCommand>);
1798
1799#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1801#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1802#[serde(untagged, deny_unknown_fields)]
1803#[non_exhaustive]
1804pub enum ShellAllowlistOpen {
1805 Flag(bool),
1809
1810 Validate(String),
1815}
1816
1817impl Default for ShellAllowlistOpen {
1818 fn default() -> Self {
1819 Self::Flag(false)
1820 }
1821}
1822
1823#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1827#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1828#[serde(rename_all = "camelCase", deny_unknown_fields)]
1829pub struct ShellAllowlistConfig {
1830 #[serde(default)]
1833 pub scope: ShellAllowlistScope,
1834 #[serde(default)]
1836 pub all: bool,
1837 #[serde(default)]
1839 pub execute: bool,
1840 #[serde(default)]
1844 pub sidecar: bool,
1845 #[serde(default)]
1847 pub open: ShellAllowlistOpen,
1848}
1849
1850impl Allowlist for ShellAllowlistConfig {
1851 fn all_features() -> Vec<&'static str> {
1852 let allowlist = Self {
1853 scope: Default::default(),
1854 all: false,
1855 execute: true,
1856 sidecar: true,
1857 open: ShellAllowlistOpen::Flag(true),
1858 };
1859 let mut features = allowlist.to_features();
1860 features.push("shell-all");
1861 features
1862 }
1863
1864 fn to_features(&self) -> Vec<&'static str> {
1865 if self.all {
1866 vec!["shell-all"]
1867 } else {
1868 let mut features = Vec::new();
1869 check_feature!(self, features, execute, "shell-execute");
1870 check_feature!(self, features, sidecar, "shell-sidecar");
1871
1872 if !matches!(self.open, ShellAllowlistOpen::Flag(false)) {
1873 features.push("shell-open")
1874 }
1875
1876 features
1877 }
1878 }
1879}
1880
1881#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1885#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1886#[serde(rename_all = "camelCase", deny_unknown_fields)]
1887pub struct DialogAllowlistConfig {
1888 #[serde(default)]
1890 pub all: bool,
1891 #[serde(default)]
1893 pub open: bool,
1894 #[serde(default)]
1896 pub save: bool,
1897 #[serde(default)]
1899 pub message: bool,
1900 #[serde(default)]
1902 pub ask: bool,
1903 #[serde(default)]
1905 pub confirm: bool,
1906}
1907
1908impl Allowlist for DialogAllowlistConfig {
1909 fn all_features() -> Vec<&'static str> {
1910 let allowlist = Self {
1911 all: false,
1912 open: true,
1913 save: true,
1914 message: true,
1915 ask: true,
1916 confirm: true,
1917 };
1918 let mut features = allowlist.to_features();
1919 features.push("dialog-all");
1920 features
1921 }
1922
1923 fn to_features(&self) -> Vec<&'static str> {
1924 if self.all {
1925 vec!["dialog-all"]
1926 } else {
1927 let mut features = Vec::new();
1928 check_feature!(self, features, open, "dialog-open");
1929 check_feature!(self, features, save, "dialog-save");
1930 check_feature!(self, features, message, "dialog-message");
1931 check_feature!(self, features, ask, "dialog-ask");
1932 check_feature!(self, features, confirm, "dialog-confirm");
1933 features
1934 }
1935 }
1936}
1937
1938#[allow(rustdoc::bare_urls)]
1947#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1948#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1951pub struct HttpAllowlistScope(pub Vec<Url>);
1952
1953#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1957#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1958#[serde(rename_all = "camelCase", deny_unknown_fields)]
1959pub struct HttpAllowlistConfig {
1960 #[serde(default)]
1962 pub scope: HttpAllowlistScope,
1963 #[serde(default)]
1965 pub all: bool,
1966 #[serde(default)]
1968 pub request: bool,
1969}
1970
1971impl Allowlist for HttpAllowlistConfig {
1972 fn all_features() -> Vec<&'static str> {
1973 let allowlist = Self {
1974 scope: Default::default(),
1975 all: false,
1976 request: true,
1977 };
1978 let mut features = allowlist.to_features();
1979 features.push("http-all");
1980 features
1981 }
1982
1983 fn to_features(&self) -> Vec<&'static str> {
1984 if self.all {
1985 vec!["http-all"]
1986 } else {
1987 let mut features = Vec::new();
1988 check_feature!(self, features, request, "http-request");
1989 features
1990 }
1991 }
1992}
1993
1994#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1998#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1999#[serde(rename_all = "camelCase", deny_unknown_fields)]
2000pub struct NotificationAllowlistConfig {
2001 #[serde(default)]
2003 pub all: bool,
2004}
2005
2006impl Allowlist for NotificationAllowlistConfig {
2007 fn all_features() -> Vec<&'static str> {
2008 let allowlist = Self { all: false };
2009 let mut features = allowlist.to_features();
2010 features.push("notification-all");
2011 features
2012 }
2013
2014 fn to_features(&self) -> Vec<&'static str> {
2015 if self.all {
2016 vec!["notification-all"]
2017 } else {
2018 vec![]
2019 }
2020 }
2021}
2022
2023#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2027#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2028#[serde(rename_all = "camelCase", deny_unknown_fields)]
2029pub struct GlobalShortcutAllowlistConfig {
2030 #[serde(default)]
2032 pub all: bool,
2033}
2034
2035impl Allowlist for GlobalShortcutAllowlistConfig {
2036 fn all_features() -> Vec<&'static str> {
2037 let allowlist = Self { all: false };
2038 let mut features = allowlist.to_features();
2039 features.push("global-shortcut-all");
2040 features
2041 }
2042
2043 fn to_features(&self) -> Vec<&'static str> {
2044 if self.all {
2045 vec!["global-shortcut-all"]
2046 } else {
2047 vec![]
2048 }
2049 }
2050}
2051
2052#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2056#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2057#[serde(rename_all = "camelCase", deny_unknown_fields)]
2058pub struct OsAllowlistConfig {
2059 #[serde(default)]
2061 pub all: bool,
2062}
2063
2064impl Allowlist for OsAllowlistConfig {
2065 fn all_features() -> Vec<&'static str> {
2066 let allowlist = Self { all: false };
2067 let mut features = allowlist.to_features();
2068 features.push("os-all");
2069 features
2070 }
2071
2072 fn to_features(&self) -> Vec<&'static str> {
2073 if self.all {
2074 vec!["os-all"]
2075 } else {
2076 vec![]
2077 }
2078 }
2079}
2080
2081#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2085#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2086#[serde(rename_all = "camelCase", deny_unknown_fields)]
2087pub struct PathAllowlistConfig {
2088 #[serde(default)]
2090 pub all: bool,
2091}
2092
2093impl Allowlist for PathAllowlistConfig {
2094 fn all_features() -> Vec<&'static str> {
2095 let allowlist = Self { all: false };
2096 let mut features = allowlist.to_features();
2097 features.push("path-all");
2098 features
2099 }
2100
2101 fn to_features(&self) -> Vec<&'static str> {
2102 if self.all {
2103 vec!["path-all"]
2104 } else {
2105 vec![]
2106 }
2107 }
2108}
2109
2110#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2114#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2115#[serde(rename_all = "camelCase", deny_unknown_fields)]
2116pub struct ProtocolAllowlistConfig {
2117 #[serde(default, alias = "asset-scope")]
2119 pub asset_scope: FsAllowlistScope,
2120 #[serde(default)]
2122 pub all: bool,
2123 #[serde(default)]
2125 pub asset: bool,
2126}
2127
2128impl Allowlist for ProtocolAllowlistConfig {
2129 fn all_features() -> Vec<&'static str> {
2130 let allowlist = Self {
2131 asset_scope: Default::default(),
2132 all: false,
2133 asset: true,
2134 };
2135 let mut features = allowlist.to_features();
2136 features.push("protocol-all");
2137 features
2138 }
2139
2140 fn to_features(&self) -> Vec<&'static str> {
2141 if self.all {
2142 vec!["protocol-all"]
2143 } else {
2144 let mut features = Vec::new();
2145 check_feature!(self, features, asset, "protocol-asset");
2146 features
2147 }
2148 }
2149}
2150
2151#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2156#[serde(rename_all = "camelCase", deny_unknown_fields)]
2157pub struct ProcessAllowlistConfig {
2158 #[serde(default)]
2160 pub all: bool,
2161 #[serde(default)]
2163 pub relaunch: bool,
2164 #[serde(
2169 default,
2170 alias = "relaunchDangerousAllowSymlinkMacOS",
2171 alias = "relaunch-dangerous-allow-symlink-macos"
2172 )]
2173 pub relaunch_dangerous_allow_symlink_macos: bool,
2174 #[serde(default)]
2176 pub exit: bool,
2177}
2178
2179impl Allowlist for ProcessAllowlistConfig {
2180 fn all_features() -> Vec<&'static str> {
2181 let allowlist = Self {
2182 all: false,
2183 relaunch: true,
2184 relaunch_dangerous_allow_symlink_macos: false,
2185 exit: true,
2186 };
2187 let mut features = allowlist.to_features();
2188 features.push("process-all");
2189 features
2190 }
2191
2192 fn to_features(&self) -> Vec<&'static str> {
2193 if self.all {
2194 vec!["process-all"]
2195 } else {
2196 let mut features = Vec::new();
2197 check_feature!(self, features, relaunch, "process-relaunch");
2198 check_feature!(
2199 self,
2200 features,
2201 relaunch_dangerous_allow_symlink_macos,
2202 "process-relaunch-dangerous-allow-symlink-macos"
2203 );
2204 check_feature!(self, features, exit, "process-exit");
2205 features
2206 }
2207 }
2208}
2209
2210#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2214#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2215#[serde(rename_all = "camelCase", deny_unknown_fields)]
2216pub struct ClipboardAllowlistConfig {
2217 #[serde(default)]
2219 pub all: bool,
2220 #[serde(default, alias = "writeText")]
2222 pub write_text: bool,
2223 #[serde(default, alias = "readText")]
2225 pub read_text: bool,
2226}
2227
2228impl Allowlist for ClipboardAllowlistConfig {
2229 fn all_features() -> Vec<&'static str> {
2230 let allowlist = Self {
2231 all: false,
2232 write_text: true,
2233 read_text: true,
2234 };
2235 let mut features = allowlist.to_features();
2236 features.push("clipboard-all");
2237 features
2238 }
2239
2240 fn to_features(&self) -> Vec<&'static str> {
2241 if self.all {
2242 vec!["clipboard-all"]
2243 } else {
2244 let mut features = Vec::new();
2245 check_feature!(self, features, write_text, "clipboard-write-text");
2246 check_feature!(self, features, read_text, "clipboard-read-text");
2247 features
2248 }
2249 }
2250}
2251
2252#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2256#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2257#[serde(rename_all = "camelCase", deny_unknown_fields)]
2258pub struct AppAllowlistConfig {
2259 #[serde(default)]
2261 pub all: bool,
2262 #[serde(default)]
2264 pub show: bool,
2265 #[serde(default)]
2267 pub hide: bool,
2268}
2269
2270impl Allowlist for AppAllowlistConfig {
2271 fn all_features() -> Vec<&'static str> {
2272 let allowlist = Self {
2273 all: false,
2274 show: true,
2275 hide: true,
2276 };
2277 let mut features = allowlist.to_features();
2278 features.push("app-all");
2279 features
2280 }
2281
2282 fn to_features(&self) -> Vec<&'static str> {
2283 if self.all {
2284 vec!["app-all"]
2285 } else {
2286 let mut features = Vec::new();
2287 check_feature!(self, features, show, "app-show");
2288 check_feature!(self, features, hide, "app-hide");
2289 features
2290 }
2291 }
2292}
2293
2294#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2305#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2306#[serde(rename_all = "camelCase", deny_unknown_fields)]
2307pub struct AllowlistConfig {
2308 #[serde(default)]
2310 pub all: bool,
2311 #[serde(default)]
2313 pub fs: FsAllowlistConfig,
2314 #[serde(default)]
2316 pub window: WindowAllowlistConfig,
2317 #[serde(default)]
2319 pub shell: ShellAllowlistConfig,
2320 #[serde(default)]
2322 pub dialog: DialogAllowlistConfig,
2323 #[serde(default)]
2325 pub http: HttpAllowlistConfig,
2326 #[serde(default)]
2328 pub notification: NotificationAllowlistConfig,
2329 #[serde(default, alias = "global-shortcut")]
2331 pub global_shortcut: GlobalShortcutAllowlistConfig,
2332 #[serde(default)]
2334 pub os: OsAllowlistConfig,
2335 #[serde(default)]
2337 pub path: PathAllowlistConfig,
2338 #[serde(default)]
2340 pub protocol: ProtocolAllowlistConfig,
2341 #[serde(default)]
2343 pub process: ProcessAllowlistConfig,
2344 #[serde(default)]
2346 pub clipboard: ClipboardAllowlistConfig,
2347 #[serde(default)]
2349 pub app: AppAllowlistConfig,
2350}
2351
2352impl Allowlist for AllowlistConfig {
2353 fn all_features() -> Vec<&'static str> {
2354 let mut features = vec!["api-all"];
2355 features.extend(FsAllowlistConfig::all_features());
2356 features.extend(WindowAllowlistConfig::all_features());
2357 features.extend(ShellAllowlistConfig::all_features());
2358 features.extend(DialogAllowlistConfig::all_features());
2359 features.extend(HttpAllowlistConfig::all_features());
2360 features.extend(NotificationAllowlistConfig::all_features());
2361 features.extend(GlobalShortcutAllowlistConfig::all_features());
2362 features.extend(OsAllowlistConfig::all_features());
2363 features.extend(PathAllowlistConfig::all_features());
2364 features.extend(ProtocolAllowlistConfig::all_features());
2365 features.extend(ProcessAllowlistConfig::all_features());
2366 features.extend(ClipboardAllowlistConfig::all_features());
2367 features.extend(AppAllowlistConfig::all_features());
2368 features
2369 }
2370
2371 fn to_features(&self) -> Vec<&'static str> {
2372 if self.all {
2373 vec!["api-all"]
2374 } else {
2375 let mut features = Vec::new();
2376 features.extend(self.fs.to_features());
2377 features.extend(self.window.to_features());
2378 features.extend(self.shell.to_features());
2379 features.extend(self.dialog.to_features());
2380 features.extend(self.http.to_features());
2381 features.extend(self.notification.to_features());
2382 features.extend(self.global_shortcut.to_features());
2383 features.extend(self.os.to_features());
2384 features.extend(self.path.to_features());
2385 features.extend(self.protocol.to_features());
2386 features.extend(self.process.to_features());
2387 features.extend(self.clipboard.to_features());
2388 features.extend(self.app.to_features());
2389 features
2390 }
2391 }
2392}
2393
2394#[skip_serializing_none]
2396#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2397#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2398#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2399pub enum PatternKind {
2400 Brownfield,
2402 Isolation {
2404 dir: PathBuf,
2406 },
2407}
2408
2409impl Default for PatternKind {
2410 fn default() -> Self {
2411 Self::Brownfield
2412 }
2413}
2414
2415#[skip_serializing_none]
2419#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2420#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2421#[serde(rename_all = "camelCase", deny_unknown_fields)]
2422pub struct TauriConfig {
2423 #[serde(default)]
2425 pub pattern: PatternKind,
2426 #[serde(default)]
2428 pub windows: Vec<WindowConfig>,
2429 pub cli: Option<CliConfig>,
2431 #[serde(default)]
2433 pub bundle: BundleConfig,
2434 #[serde(default)]
2436 pub allowlist: AllowlistConfig,
2437 #[serde(default)]
2439 pub security: SecurityConfig,
2440 #[serde(default)]
2442 pub updater: UpdaterConfig,
2443 #[serde(alias = "system-tray")]
2445 pub system_tray: Option<SystemTrayConfig>,
2446 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2448 pub macos_private_api: bool,
2449}
2450
2451#[skip_serializing_none]
2455#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
2456#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2457pub struct UpdaterEndpoint(pub Url);
2458
2459impl std::fmt::Display for UpdaterEndpoint {
2460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2461 write!(f, "{}", self.0)
2462 }
2463}
2464
2465impl<'de> Deserialize<'de> for UpdaterEndpoint {
2466 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2467 where
2468 D: Deserializer<'de>,
2469 {
2470 let url = Url::deserialize(deserializer)?;
2471 #[cfg(all(not(debug_assertions), not(feature = "schema")))]
2472 {
2473 if url.scheme() != "https" {
2474 return Err(serde::de::Error::custom(
2475 "The configured updater endpoint must use the `https` protocol.",
2476 ));
2477 }
2478 }
2479 Ok(Self(url))
2480 }
2481}
2482
2483#[derive(Debug, PartialEq, Eq, Clone)]
2485#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2486#[cfg_attr(feature = "schema", schemars(rename_all = "camelCase"))]
2487pub enum WindowsUpdateInstallMode {
2488 BasicUi,
2490 Quiet,
2493 Passive,
2495 }
2498
2499impl Display for WindowsUpdateInstallMode {
2500 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2501 write!(
2502 f,
2503 "{}",
2504 match self {
2505 Self::BasicUi => "basicUI",
2506 Self::Quiet => "quiet",
2507 Self::Passive => "passive",
2508 }
2509 )
2510 }
2511}
2512
2513impl Default for WindowsUpdateInstallMode {
2514 fn default() -> Self {
2515 Self::Passive
2516 }
2517}
2518
2519impl Serialize for WindowsUpdateInstallMode {
2520 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2521 where
2522 S: Serializer,
2523 {
2524 serializer.serialize_str(self.to_string().as_ref())
2525 }
2526}
2527
2528impl<'de> Deserialize<'de> for WindowsUpdateInstallMode {
2529 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
2530 where
2531 D: Deserializer<'de>,
2532 {
2533 let s = String::deserialize(deserializer)?;
2534 match s.to_lowercase().as_str() {
2535 "basicui" => Ok(Self::BasicUi),
2536 "quiet" => Ok(Self::Quiet),
2537 "passive" => Ok(Self::Passive),
2538 _ => Err(DeError::custom(format!(
2539 "unknown update install mode '{s}'"
2540 ))),
2541 }
2542 }
2543}
2544
2545#[skip_serializing_none]
2549#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
2550#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2551#[serde(rename_all = "camelCase", deny_unknown_fields)]
2552pub struct UpdaterWindowsConfig {
2553 #[serde(default, alias = "installer-args")]
2555 pub installer_args: Vec<String>,
2556 #[serde(default, alias = "install-mode")]
2558 pub install_mode: WindowsUpdateInstallMode,
2559}
2560
2561#[skip_serializing_none]
2565#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
2566#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2567#[serde(rename_all = "camelCase", deny_unknown_fields)]
2568pub struct UpdaterConfig {
2569 #[serde(default)]
2571 pub active: bool,
2572 #[serde(default = "default_true")]
2574 pub dialog: bool,
2575 #[allow(rustdoc::bare_urls)]
2586 pub endpoints: Option<Vec<UpdaterEndpoint>>,
2587 #[serde(default)] pub pubkey: String,
2590 #[serde(default)]
2592 pub windows: UpdaterWindowsConfig,
2593}
2594
2595impl<'de> Deserialize<'de> for UpdaterConfig {
2596 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2597 where
2598 D: Deserializer<'de>,
2599 {
2600 #[derive(Deserialize)]
2601 struct InnerUpdaterConfig {
2602 #[serde(default)]
2603 active: bool,
2604 #[serde(default = "default_true")]
2605 dialog: bool,
2606 endpoints: Option<Vec<UpdaterEndpoint>>,
2607 pubkey: Option<String>,
2608 #[serde(default)]
2609 windows: UpdaterWindowsConfig,
2610 }
2611
2612 let config = InnerUpdaterConfig::deserialize(deserializer)?;
2613
2614 if config.active && config.pubkey.is_none() {
2615 return Err(DeError::custom(
2616 "The updater `pubkey` configuration is required.",
2617 ));
2618 }
2619
2620 Ok(UpdaterConfig {
2621 active: config.active,
2622 dialog: config.dialog,
2623 endpoints: config.endpoints,
2624 pubkey: config.pubkey.unwrap_or_default(),
2625 windows: config.windows,
2626 })
2627 }
2628}
2629
2630impl Default for UpdaterConfig {
2631 fn default() -> Self {
2632 Self {
2633 active: false,
2634 dialog: true,
2635 endpoints: None,
2636 pubkey: "".into(),
2637 windows: Default::default(),
2638 }
2639 }
2640}
2641
2642#[skip_serializing_none]
2646#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2647#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2648#[serde(rename_all = "camelCase", deny_unknown_fields)]
2649pub struct SystemTrayConfig {
2650 #[serde(alias = "icon-path")]
2652 pub icon_path: PathBuf,
2653 #[serde(default, alias = "icon-as-template")]
2655 pub icon_as_template: bool,
2656 #[serde(default = "default_true", alias = "menu-on-left-click")]
2658 pub menu_on_left_click: bool,
2659 pub title: Option<String>,
2661}
2662
2663#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2665#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2666#[serde(untagged, deny_unknown_fields)]
2667#[non_exhaustive]
2668pub enum AppUrl {
2669 Url(WindowUrl),
2671 Files(Vec<PathBuf>),
2673}
2674
2675impl std::fmt::Display for AppUrl {
2676 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2677 match self {
2678 Self::Url(url) => write!(f, "{url}"),
2679 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2680 }
2681 }
2682}
2683
2684#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2686#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2687#[serde(rename_all = "camelCase", untagged)]
2688pub enum BeforeDevCommand {
2689 Script(String),
2691 ScriptWithOptions {
2693 script: String,
2695 cwd: Option<String>,
2697 #[serde(default)]
2699 wait: bool,
2700 },
2701}
2702
2703#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2705#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2706#[serde(rename_all = "camelCase", untagged)]
2707pub enum HookCommand {
2708 Script(String),
2710 ScriptWithOptions {
2712 script: String,
2714 cwd: Option<String>,
2716 },
2717}
2718
2719#[skip_serializing_none]
2723#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2724#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2725#[serde(rename_all = "camelCase", deny_unknown_fields)]
2726pub struct BuildConfig {
2727 pub runner: Option<String>,
2729 #[serde(default = "default_dev_path", alias = "dev-path")]
2737 pub dev_path: AppUrl,
2738 #[serde(default = "default_dist_dir", alias = "dist-dir")]
2750 pub dist_dir: AppUrl,
2751 #[serde(alias = "before-dev-command")]
2755 pub before_dev_command: Option<BeforeDevCommand>,
2756 #[serde(alias = "before-build-command")]
2760 pub before_build_command: Option<HookCommand>,
2761 #[serde(alias = "before-bundle-command")]
2765 pub before_bundle_command: Option<HookCommand>,
2766 pub features: Option<Vec<String>>,
2768 #[serde(default, alias = "with-global-tauri")]
2770 pub with_global_tauri: bool,
2771}
2772
2773impl Default for BuildConfig {
2774 fn default() -> Self {
2775 Self {
2776 runner: None,
2777 dev_path: default_dev_path(),
2778 dist_dir: default_dist_dir(),
2779 before_dev_command: None,
2780 before_build_command: None,
2781 before_bundle_command: None,
2782 features: None,
2783 with_global_tauri: false,
2784 }
2785 }
2786}
2787
2788fn default_dev_path() -> AppUrl {
2789 AppUrl::Url(WindowUrl::External(
2790 Url::parse("http://localhost:8080").unwrap(),
2791 ))
2792}
2793
2794fn default_dist_dir() -> AppUrl {
2795 AppUrl::Url(WindowUrl::App("../dist".into()))
2796}
2797
2798#[derive(Debug, PartialEq, Eq)]
2799struct PackageVersion(String);
2800
2801impl<'d> serde::Deserialize<'d> for PackageVersion {
2802 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<PackageVersion, D::Error> {
2803 struct PackageVersionVisitor;
2804
2805 impl<'d> Visitor<'d> for PackageVersionVisitor {
2806 type Value = PackageVersion;
2807
2808 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2809 write!(
2810 formatter,
2811 "a semver string or a path to a package.json file"
2812 )
2813 }
2814
2815 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2816 let path = PathBuf::from(value);
2817 if path.exists() {
2818 let json_str = read_to_string(&path)
2819 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2820 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2821 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2822 if let Some(obj) = package_json.as_object() {
2823 let version = obj
2824 .get("version")
2825 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2826 .as_str()
2827 .ok_or_else(|| {
2828 DeError::custom(format!("`{} > version` must be a string", path.display()))
2829 })?;
2830 Ok(PackageVersion(
2831 Version::from_str(version)
2832 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2833 .to_string(),
2834 ))
2835 } else {
2836 Err(DeError::custom(
2837 "`package > version` value is not a path to a JSON object",
2838 ))
2839 }
2840 } else {
2841 Ok(PackageVersion(
2842 Version::from_str(value)
2843 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2844 .to_string(),
2845 ))
2846 }
2847 }
2848 }
2849
2850 deserializer.deserialize_string(PackageVersionVisitor {})
2851 }
2852}
2853
2854#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2858#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2859#[serde(rename_all = "camelCase", deny_unknown_fields)]
2860pub struct PackageConfig {
2861 #[serde(alias = "product-name")]
2863 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2864 pub product_name: Option<String>,
2865 #[serde(deserialize_with = "version_deserializer", default)]
2867 pub version: Option<String>,
2868}
2869
2870fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2871where
2872 D: Deserializer<'de>,
2873{
2874 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2875}
2876
2877#[allow(rustdoc::invalid_codeblock_attributes)]
2949#[skip_serializing_none]
2950#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2951#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2952#[serde(rename_all = "camelCase", deny_unknown_fields)]
2953pub struct Config {
2954 #[serde(rename = "$schema")]
2956 pub schema: Option<String>,
2957 #[serde(default)]
2959 pub package: PackageConfig,
2960 #[serde(default)]
2962 pub tauri: TauriConfig,
2963 #[serde(default = "default_build")]
2965 pub build: BuildConfig,
2966 #[serde(default)]
2968 pub plugins: PluginConfig,
2969}
2970
2971#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2975#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2976pub struct PluginConfig(pub HashMap<String, JsonValue>);
2977
2978fn default_build() -> BuildConfig {
2979 BuildConfig {
2980 runner: None,
2981 dev_path: default_dev_path(),
2982 dist_dir: default_dist_dir(),
2983 before_dev_command: None,
2984 before_build_command: None,
2985 before_bundle_command: None,
2986 features: None,
2987 with_global_tauri: false,
2988 }
2989}
2990
2991#[derive(Debug, Clone, PartialEq, Eq)]
2993#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2994pub enum TitleBarStyle {
2995 Visible,
2997 Transparent,
3001 Overlay,
3008}
3009
3010impl Default for TitleBarStyle {
3011 fn default() -> Self {
3012 Self::Visible
3013 }
3014}
3015
3016impl Serialize for TitleBarStyle {
3017 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3018 where
3019 S: Serializer,
3020 {
3021 serializer.serialize_str(self.to_string().as_ref())
3022 }
3023}
3024
3025impl<'de> Deserialize<'de> for TitleBarStyle {
3026 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
3027 where
3028 D: Deserializer<'de>,
3029 {
3030 let s = String::deserialize(deserializer)?;
3031 Ok(match s.to_lowercase().as_str() {
3032 "transparent" => Self::Transparent,
3033 "overlay" => Self::Overlay,
3034 _ => Self::Visible,
3035 })
3036 }
3037}
3038
3039impl Display for TitleBarStyle {
3040 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3041 write!(
3042 f,
3043 "{}",
3044 match self {
3045 Self::Visible => "Visible",
3046 Self::Transparent => "Transparent",
3047 Self::Overlay => "Overlay",
3048 }
3049 )
3050 }
3051}
3052
3053#[derive(Debug, Copy, Clone, PartialEq, Eq)]
3055#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
3056#[non_exhaustive]
3057pub enum Theme {
3058 Light,
3060 Dark,
3062}
3063
3064impl Serialize for Theme {
3065 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3066 where
3067 S: Serializer,
3068 {
3069 serializer.serialize_str(self.to_string().as_ref())
3070 }
3071}
3072
3073impl<'de> Deserialize<'de> for Theme {
3074 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
3075 where
3076 D: Deserializer<'de>,
3077 {
3078 let s = String::deserialize(deserializer)?;
3079 Ok(match s.to_lowercase().as_str() {
3080 "dark" => Self::Dark,
3081 _ => Self::Light,
3082 })
3083 }
3084}
3085
3086impl Display for Theme {
3087 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3088 write!(
3089 f,
3090 "{}",
3091 match self {
3092 Self::Light => "light",
3093 Self::Dark => "dark",
3094 }
3095 )
3096 }
3097}
3098
3099#[cfg(test)]
3100mod test {
3101 use super::*;
3102
3103 #[test]
3106 fn test_defaults() {
3108 let t_config = TauriConfig::default();
3110 let b_config = BuildConfig::default();
3112 let d_path = default_dev_path();
3114 let d_windows: Vec<WindowConfig> = vec![];
3116 let d_bundle = BundleConfig::default();
3118 let d_updater = UpdaterConfig::default();
3120
3121 let tauri = TauriConfig {
3123 pattern: Default::default(),
3124 windows: vec![],
3125 bundle: BundleConfig {
3126 active: false,
3127 targets: Default::default(),
3128 identifier: String::from(""),
3129 publisher: None,
3130 icon: Vec::new(),
3131 resources: None,
3132 copyright: None,
3133 category: None,
3134 short_description: None,
3135 long_description: None,
3136 appimage: Default::default(),
3137 deb: Default::default(),
3138 macos: Default::default(),
3139 external_bin: None,
3140 windows: Default::default(),
3141 },
3142 cli: None,
3143 updater: UpdaterConfig {
3144 active: false,
3145 dialog: true,
3146 pubkey: "".into(),
3147 endpoints: None,
3148 windows: Default::default(),
3149 },
3150 security: SecurityConfig {
3151 csp: None,
3152 dev_csp: None,
3153 freeze_prototype: false,
3154 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3155 dangerous_remote_domain_ipc_access: Vec::new(),
3156 dangerous_use_http_scheme: false,
3157 },
3158 allowlist: AllowlistConfig::default(),
3159 system_tray: None,
3160 macos_private_api: false,
3161 };
3162
3163 let build = BuildConfig {
3165 runner: None,
3166 dev_path: AppUrl::Url(WindowUrl::External(
3167 Url::parse("http://localhost:8080").unwrap(),
3168 )),
3169 dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())),
3170 before_dev_command: None,
3171 before_build_command: None,
3172 before_bundle_command: None,
3173 features: None,
3174 with_global_tauri: false,
3175 };
3176
3177 assert_eq!(t_config, tauri);
3179 assert_eq!(b_config, build);
3180 assert_eq!(d_bundle, tauri.bundle);
3181 assert_eq!(d_updater, tauri.updater);
3182 assert_eq!(
3183 d_path,
3184 AppUrl::Url(WindowUrl::External(
3185 Url::parse("http://localhost:8080").unwrap()
3186 ))
3187 );
3188 assert_eq!(d_windows, tauri.windows);
3189 }
3190}