1use crate::config::Config;
6use crate::platform::Target;
7use json_patch::merge;
8use serde::de::DeserializeOwned;
9use serde_json::Value;
10use std::ffi::OsStr;
11use std::path::{Path, PathBuf};
12use thiserror::Error;
13
14pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];
16
17pub const SUPPORTED_FORMATS: &[ConfigFormat] =
19 &[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml];
20
21pub const ENABLED_FORMATS: &[ConfigFormat] = &[
23 ConfigFormat::Json,
24 #[cfg(feature = "config-json5")]
25 ConfigFormat::Json5,
26 #[cfg(feature = "config-toml")]
27 ConfigFormat::Toml,
28];
29
30#[derive(Debug, Copy, Clone)]
32pub enum ConfigFormat {
33 Json,
35 Json5,
37 Toml,
39}
40
41impl ConfigFormat {
42 pub fn into_file_name(self) -> &'static str {
44 match self {
45 Self::Json => "tauri.conf.json",
46 Self::Json5 => "tauri.conf.json5",
47 Self::Toml => "Tauri.toml",
48 }
49 }
50
51 fn into_platform_file_name(self, target: Target) -> &'static str {
52 match self {
53 Self::Json => match target {
54 Target::MacOS => "tauri.macos.conf.json",
55 Target::Windows => "tauri.windows.conf.json",
56 Target::Linux => "tauri.linux.conf.json",
57 Target::Android => "tauri.android.conf.json",
58 Target::Ios => "tauri.ios.conf.json",
59 },
60 Self::Json5 => match target {
61 Target::MacOS => "tauri.macos.conf.json5",
62 Target::Windows => "tauri.windows.conf.json5",
63 Target::Linux => "tauri.linux.conf.json5",
64 Target::Android => "tauri.android.conf.json5",
65 Target::Ios => "tauri.ios.conf.json5",
66 },
67 Self::Toml => match target {
68 Target::MacOS => "Tauri.macos.toml",
69 Target::Windows => "Tauri.windows.toml",
70 Target::Linux => "Tauri.linux.toml",
71 Target::Android => "Tauri.android.toml",
72 Target::Ios => "Tauri.ios.toml",
73 },
74 }
75 }
76}
77
78#[derive(Debug, Error)]
80#[non_exhaustive]
81pub enum ConfigError {
82 #[error("unable to parse JSON Tauri config file at {path} because {error}")]
84 FormatJson {
85 path: PathBuf,
87
88 error: serde_json::Error,
90 },
91
92 #[cfg(feature = "config-json5")]
94 #[error("unable to parse JSON5 Tauri config file at {path} because {error}")]
95 FormatJson5 {
96 path: PathBuf,
98
99 error: ::json5::Error,
101 },
102
103 #[cfg(feature = "config-toml")]
105 #[error("unable to parse toml Tauri config file at {path} because {error}")]
106 FormatToml {
107 path: PathBuf,
109
110 error: Box<::toml::de::Error>,
112 },
113
114 #[error("unsupported format encountered {0}")]
116 UnsupportedFormat(String),
117
118 #[error("supported (but disabled) format encountered {extension} - try enabling `{feature}` ")]
120 DisabledFormat {
121 extension: String,
123
124 feature: String,
126 },
127
128 #[error("unable to read Tauri config file at {path} because {error}")]
130 Io {
131 path: PathBuf,
133
134 error: std::io::Error,
136 },
137}
138
139pub fn folder_has_configuration_file(target: Target, folder: &Path) -> bool {
141 folder.join(ConfigFormat::Json.into_file_name()).exists()
142 || folder.join(ConfigFormat::Json5.into_file_name()).exists()
143 || folder.join(ConfigFormat::Toml.into_file_name()).exists()
144 || folder.join(ConfigFormat::Json.into_platform_file_name(target)).exists()
146 || folder.join(ConfigFormat::Json5.into_platform_file_name(target)).exists()
147 || folder.join(ConfigFormat::Toml.into_platform_file_name(target)).exists()
148}
149
150pub fn is_configuration_file(target: Target, path: &Path) -> bool {
152 path
153 .file_name()
154 .map(|file_name| {
155 file_name == OsStr::new(ConfigFormat::Json.into_file_name())
156 || file_name == OsStr::new(ConfigFormat::Json5.into_file_name())
157 || file_name == OsStr::new(ConfigFormat::Toml.into_file_name())
158 || file_name == OsStr::new(ConfigFormat::Json.into_platform_file_name(target))
160 || file_name == OsStr::new(ConfigFormat::Json5.into_platform_file_name(target))
161 || file_name == OsStr::new(ConfigFormat::Toml.into_platform_file_name(target))
162 })
163 .unwrap_or_default()
164}
165
166pub fn read_from(target: Target, root_dir: &Path) -> Result<(Value, Vec<PathBuf>), ConfigError> {
181 let (mut config, config_file_path) = parse_value(target, root_dir.join("tauri.conf.json"))?;
182 let mut config_paths = vec![config_file_path];
183 if let Some((platform_config, path)) = read_platform(target, root_dir)? {
184 config_paths.push(path);
185 merge(&mut config, &platform_config);
186 }
187 Ok((config, config_paths))
188}
189
190pub fn read_platform(
194 target: Target,
195 root_dir: &Path,
196) -> Result<Option<(Value, PathBuf)>, ConfigError> {
197 let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name(target));
198 if does_supported_file_name_exist(target, &platform_config_path) {
199 let (platform_config, path): (Value, PathBuf) = parse_value(target, platform_config_path)?;
200 Ok(Some((platform_config, path)))
201 } else {
202 Ok(None)
203 }
204}
205
206pub fn does_supported_file_name_exist(target: Target, path: impl Into<PathBuf>) -> bool {
211 let path = path.into();
212 let source_file_name = path.file_name().unwrap().to_str().unwrap();
213 let lookup_platform_config = ENABLED_FORMATS
214 .iter()
215 .any(|format| source_file_name == format.into_platform_file_name(target));
216 ENABLED_FORMATS.iter().any(|format| {
217 path
218 .with_file_name(if lookup_platform_config {
219 format.into_platform_file_name(target)
220 } else {
221 format.into_file_name()
222 })
223 .exists()
224 })
225}
226
227pub fn parse(target: Target, path: impl Into<PathBuf>) -> Result<(Config, PathBuf), ConfigError> {
242 do_parse(target, path.into())
243}
244
245pub fn parse_value(
247 target: Target,
248 path: impl Into<PathBuf>,
249) -> Result<(Value, PathBuf), ConfigError> {
250 do_parse(target, path.into())
251}
252
253fn do_parse<D: DeserializeOwned>(
254 target: Target,
255 path: PathBuf,
256) -> Result<(D, PathBuf), ConfigError> {
257 let file_name = path
258 .file_name()
259 .map(OsStr::to_string_lossy)
260 .unwrap_or_default();
261 let lookup_platform_config = ENABLED_FORMATS
262 .iter()
263 .any(|format| file_name == format.into_platform_file_name(target));
264
265 let json5 = path.with_file_name(if lookup_platform_config {
266 ConfigFormat::Json5.into_platform_file_name(target)
267 } else {
268 ConfigFormat::Json5.into_file_name()
269 });
270 let toml = path.with_file_name(if lookup_platform_config {
271 ConfigFormat::Toml.into_platform_file_name(target)
272 } else {
273 ConfigFormat::Toml.into_file_name()
274 });
275
276 let path_ext = path
277 .extension()
278 .map(OsStr::to_string_lossy)
279 .unwrap_or_default();
280
281 if path.exists() {
282 let raw = read_to_string(&path)?;
283
284 #[allow(clippy::let_and_return)]
286 let json = do_parse_json(&raw, &path);
287
288 #[cfg(feature = "config-json5")]
293 let json = {
294 match do_parse_json5(&raw, &path) {
295 json5 @ Ok(_) => json5,
296
297 Err(_) => json,
299 }
300 };
301
302 json.map(|j| (j, path))
303 } else if json5.exists() {
304 #[cfg(feature = "config-json5")]
305 {
306 let raw = read_to_string(&json5)?;
307 do_parse_json5(&raw, &json5).map(|config| (config, json5))
308 }
309
310 #[cfg(not(feature = "config-json5"))]
311 Err(ConfigError::DisabledFormat {
312 extension: ".json5".into(),
313 feature: "config-json5".into(),
314 })
315 } else if toml.exists() {
316 #[cfg(feature = "config-toml")]
317 {
318 let raw = read_to_string(&toml)?;
319 do_parse_toml(&raw, &toml).map(|config| (config, toml))
320 }
321
322 #[cfg(not(feature = "config-toml"))]
323 Err(ConfigError::DisabledFormat {
324 extension: ".toml".into(),
325 feature: "config-toml".into(),
326 })
327 } else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
328 Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
329 } else {
330 Err(ConfigError::Io {
331 path,
332 error: std::io::ErrorKind::NotFound.into(),
333 })
334 }
335}
336
337pub fn parse_json(raw: &str, path: &Path) -> Result<Config, ConfigError> {
341 do_parse_json(raw, path)
342}
343
344pub fn parse_json_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {
348 do_parse_json(raw, path)
349}
350
351fn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
352 serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {
353 path: path.into(),
354 error,
355 })
356}
357
358#[cfg(feature = "config-json5")]
363pub fn parse_json5(raw: &str, path: &Path) -> Result<Config, ConfigError> {
364 do_parse_json5(raw, path)
365}
366
367#[cfg(feature = "config-json5")]
372pub fn parse_json5_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {
373 do_parse_json5(raw, path)
374}
375
376#[cfg(feature = "config-json5")]
377fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
378 ::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {
379 path: path.into(),
380 error,
381 })
382}
383
384#[cfg(feature = "config-toml")]
385fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
386 ::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
387 path: path.into(),
388 error: Box::new(error),
389 })
390}
391
392fn read_to_string(path: &Path) -> Result<String, ConfigError> {
394 std::fs::read_to_string(path).map_err(|error| ConfigError::Io {
395 path: path.into(),
396 error,
397 })
398}