dioxus_showcase_core/
config.rs1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(default, deny_unknown_fields)]
7pub struct ShowcaseProjectConfig {
8 pub name: String,
9 pub entry_crate: String,
10 pub showcase_crate: String,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(default, deny_unknown_fields)]
15pub struct ShowcaseDevConfig {
16 pub port: u16,
17 pub host: String,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(default, deny_unknown_fields)]
22pub struct ShowcaseBuildConfig {
23 pub out_dir: String,
24 pub base_path: String,
25}
26
27#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(default, deny_unknown_fields)]
29pub struct ShowcaseConfig {
30 pub project: ShowcaseProjectConfig,
31 pub dev: ShowcaseDevConfig,
32 pub build: ShowcaseBuildConfig,
33}
34
35impl Default for ShowcaseProjectConfig {
36 fn default() -> Self {
37 Self {
38 name: "my-ui".to_owned(),
39 entry_crate: "web".to_owned(),
40 showcase_crate: "showcase".to_owned(),
41 }
42 }
43}
44
45impl Default for ShowcaseDevConfig {
46 fn default() -> Self {
47 Self { port: 6111, host: "127.0.0.1".to_owned() }
48 }
49}
50
51impl Default for ShowcaseBuildConfig {
52 fn default() -> Self {
53 Self { out_dir: "target/showcase".to_owned(), base_path: "/".to_owned() }
54 }
55}
56
57impl ShowcaseConfig {
58 pub fn as_toml_string(&self) -> String {
60 toml::to_string_pretty(self).expect("showcase config serialization should not fail")
61 }
62
63 pub fn write_default_if_missing(path: impl AsRef<Path>) -> std::io::Result<bool> {
65 let path = path.as_ref();
66 if path.exists() {
67 return Ok(false);
68 }
69
70 std::fs::write(path, Self::default().as_toml_string())?;
71 Ok(true)
72 }
73
74 pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self, String> {
76 let content = std::fs::read_to_string(path.as_ref())
77 .map_err(|err| format!("failed to read {}: {err}", path.as_ref().display()))?;
78 Self::from_toml_str(&content)
79 }
80
81 pub fn from_toml_str(content: &str) -> Result<Self, String> {
83 toml::from_str(content).map_err(|err| format!("failed to parse showcase config: {err}"))
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::time::{SystemTime, UNIX_EPOCH};
91
92 #[test]
93 fn default_round_trips_through_toml() {
94 let config = ShowcaseConfig::default();
95 let parsed = ShowcaseConfig::from_toml_str(&config.as_toml_string()).expect("valid toml");
96 assert_eq!(parsed, config);
97 }
98
99 #[test]
100 fn parse_custom_values() {
101 let content = r#"
102[project]
103name = "demo"
104entry_crate = "client"
105showcase_crate = "ui-showcase"
106
107[dev]
108port = 7000
109host = "0.0.0.0"
110
111[build]
112out_dir = "dist/showcase"
113base_path = "/showcase"
114"#;
115 let parsed = ShowcaseConfig::from_toml_str(content).expect("should parse");
116 assert_eq!(parsed.project.name, "demo");
117 assert_eq!(parsed.project.entry_crate, "client");
118 assert_eq!(parsed.project.showcase_crate, "ui-showcase");
119 assert_eq!(parsed.dev.port, 7000);
120 assert_eq!(parsed.dev.host, "0.0.0.0");
121 assert_eq!(parsed.build.out_dir, "dist/showcase");
122 assert_eq!(parsed.build.base_path, "/showcase");
123 }
124
125 #[test]
126 fn parse_rejects_invalid_assignment() {
127 let err = ShowcaseConfig::from_toml_str("[project]\nname \"demo\"")
128 .expect_err("missing = should fail");
129 assert!(err.contains("failed to parse showcase config"));
130 }
131
132 #[test]
133 fn parse_rejects_unquoted_string() {
134 let err =
135 ShowcaseConfig::from_toml_str("[project]\nname = demo").expect_err("must be quoted");
136 assert!(err.contains("failed to parse showcase config"));
137 }
138
139 #[test]
140 fn parse_rejects_invalid_port() {
141 let err = ShowcaseConfig::from_toml_str("[dev]\nport = 99999").expect_err("invalid port");
142 assert!(err.contains("failed to parse showcase config"));
143 }
144
145 #[test]
146 fn parse_rejects_unknown_fields() {
147 let err = ShowcaseConfig::from_toml_str("[project]\nunknown = \"value\"")
148 .expect_err("unknown fields should fail");
149 assert!(err.contains("failed to parse showcase config"));
150 }
151
152 #[test]
153 fn parse_fills_missing_sections_from_defaults() {
154 let parsed = ShowcaseConfig::from_toml_str("[project]\nname = \"demo\"")
155 .expect("partial config should parse");
156
157 assert_eq!(parsed.project.name, "demo");
158 assert_eq!(parsed.project.entry_crate, "web");
159 assert_eq!(parsed.project.showcase_crate, "showcase");
160 assert_eq!(parsed.dev, ShowcaseDevConfig::default());
161 assert_eq!(parsed.build, ShowcaseBuildConfig::default());
162 }
163
164 #[test]
165 fn write_default_if_missing_only_writes_once() {
166 let unique = SystemTime::now()
167 .duration_since(UNIX_EPOCH)
168 .expect("clock should be monotonic")
169 .as_nanos();
170 let dir = std::env::temp_dir().join(format!("dioxus-showcase-config-test-{unique}"));
171 std::fs::create_dir_all(&dir).expect("create temp dir");
172 let path = dir.join("DioxusShowcase.toml");
173
174 let first = ShowcaseConfig::write_default_if_missing(&path).expect("first write");
175 let second = ShowcaseConfig::write_default_if_missing(&path).expect("second write");
176 let written = std::fs::read_to_string(&path).expect("read config");
177
178 assert!(first);
179 assert!(!second);
180 assert!(written.contains("[project]"));
181
182 let _ = std::fs::remove_dir_all(&dir);
183 }
184}