tauri_utils/config_v1/
parse.rs1use serde::de::DeserializeOwned;
6use serde_json::Value;
7use std::ffi::OsStr;
8use std::path::{Path, PathBuf};
9use thiserror::Error;
10
11const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];
13
14const ENABLED_FORMATS: &[ConfigFormat] = &[
16 ConfigFormat::Json,
17 #[cfg(feature = "config-json5")]
18 ConfigFormat::Json5,
19 #[cfg(feature = "config-toml")]
20 ConfigFormat::Toml,
21];
22
23#[derive(Debug, Copy, Clone)]
25enum ConfigFormat {
26 Json,
28 Json5,
30 Toml,
32}
33
34impl ConfigFormat {
35 fn into_file_name(self) -> &'static str {
37 match self {
38 Self::Json => "tauri.conf.json",
39 Self::Json5 => "tauri.conf.json5",
40 Self::Toml => "Tauri.toml",
41 }
42 }
43
44 fn into_platform_file_name(self) -> &'static str {
45 match self {
46 Self::Json => {
47 if cfg!(target_os = "macos") {
48 "tauri.macos.conf.json"
49 } else if cfg!(windows) {
50 "tauri.windows.conf.json"
51 } else {
52 "tauri.linux.conf.json"
53 }
54 }
55 Self::Json5 => {
56 if cfg!(target_os = "macos") {
57 "tauri.macos.conf.json5"
58 } else if cfg!(windows) {
59 "tauri.windows.conf.json5"
60 } else {
61 "tauri.linux.conf.json5"
62 }
63 }
64 Self::Toml => {
65 if cfg!(target_os = "macos") {
66 "Tauri.macos.toml"
67 } else if cfg!(windows) {
68 "Tauri.windows.toml"
69 } else {
70 "Tauri.linux.toml"
71 }
72 }
73 }
74 }
75}
76
77#[derive(Debug, Error)]
79#[non_exhaustive]
80pub enum ConfigError {
81 #[error("unable to parse JSON Tauri config file at {path} because {error}")]
83 FormatJson {
84 path: PathBuf,
86
87 error: serde_json::Error,
89 },
90
91 #[cfg(feature = "config-json5")]
93 #[error("unable to parse JSON5 Tauri config file at {path} because {error}")]
94 FormatJson5 {
95 path: PathBuf,
97
98 error: ::json5::Error,
100 },
101
102 #[cfg(feature = "config-toml")]
104 #[error("unable to parse toml Tauri config file at {path} because {error}")]
105 FormatToml {
106 path: PathBuf,
108
109 error: ::toml::de::Error,
111 },
112
113 #[error("unsupported format encountered {0}")]
115 UnsupportedFormat(String),
116
117 #[error("supported (but disabled) format encountered {extension} - try enabling `{feature}` ")]
119 DisabledFormat {
120 extension: String,
122
123 feature: String,
125 },
126
127 #[error("unable to read Tauri config file at {path} because {error}")]
129 Io {
130 path: PathBuf,
132
133 error: std::io::Error,
135 },
136}
137
138pub fn parse_value(path: impl Into<PathBuf>) -> Result<(Value, PathBuf), ConfigError> {
140 do_parse(path.into())
141}
142
143fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<(D, PathBuf), ConfigError> {
144 let file_name = path
145 .file_name()
146 .map(OsStr::to_string_lossy)
147 .unwrap_or_default();
148 let lookup_platform_config = ENABLED_FORMATS
149 .iter()
150 .any(|format| file_name == format.into_platform_file_name());
151
152 let json5 = path.with_file_name(if lookup_platform_config {
153 ConfigFormat::Json5.into_platform_file_name()
154 } else {
155 ConfigFormat::Json5.into_file_name()
156 });
157 let toml = path.with_file_name(if lookup_platform_config {
158 ConfigFormat::Toml.into_platform_file_name()
159 } else {
160 ConfigFormat::Toml.into_file_name()
161 });
162
163 let path_ext = path
164 .extension()
165 .map(OsStr::to_string_lossy)
166 .unwrap_or_default();
167
168 if path.exists() {
169 let raw = read_to_string(&path)?;
170
171 #[allow(clippy::let_and_return)]
173 let json = do_parse_json(&raw, &path);
174
175 #[cfg(feature = "config-json5")]
180 let json = {
181 match do_parse_json5(&raw, &path) {
182 json5 @ Ok(_) => json5,
183
184 Err(_) => json,
186 }
187 };
188
189 json.map(|j| (j, path))
190 } else if json5.exists() {
191 #[cfg(feature = "config-json5")]
192 {
193 let raw = read_to_string(&json5)?;
194 do_parse_json5(&raw, &path).map(|config| (config, json5))
195 }
196
197 #[cfg(not(feature = "config-json5"))]
198 Err(ConfigError::DisabledFormat {
199 extension: ".json5".into(),
200 feature: "config-json5".into(),
201 })
202 } else if toml.exists() {
203 #[cfg(feature = "config-toml")]
204 {
205 let raw = read_to_string(&toml)?;
206 do_parse_toml(&raw, &path).map(|config| (config, toml))
207 }
208
209 #[cfg(not(feature = "config-toml"))]
210 Err(ConfigError::DisabledFormat {
211 extension: ".toml".into(),
212 feature: "config-toml".into(),
213 })
214 } else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
215 Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
216 } else {
217 Err(ConfigError::Io {
218 path,
219 error: std::io::ErrorKind::NotFound.into(),
220 })
221 }
222}
223
224fn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
225 serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {
226 path: path.into(),
227 error,
228 })
229}
230
231#[cfg(feature = "config-json5")]
232fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
233 ::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {
234 path: path.into(),
235 error,
236 })
237}
238
239#[cfg(feature = "config-toml")]
240fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
241 ::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
242 path: path.into(),
243 error,
244 })
245}
246
247fn read_to_string(path: &Path) -> Result<String, ConfigError> {
249 std::fs::read_to_string(path).map_err(|error| ConfigError::Io {
250 path: path.into(),
251 error,
252 })
253}