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