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 {
59 toml::to_string_pretty(self).expect("showcase config serialization should not fail")
60 }
61
62 pub fn write_default_if_missing(path: impl AsRef<Path>) -> std::io::Result<bool> {
63 let path = path.as_ref();
64 if path.exists() {
65 return Ok(false);
66 }
67
68 std::fs::write(path, Self::default().as_toml_string())?;
69 Ok(true)
70 }
71
72 pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self, String> {
73 let content = std::fs::read_to_string(path.as_ref())
74 .map_err(|err| format!("failed to read {}: {err}", path.as_ref().display()))?;
75 Self::from_toml_str(&content)
76 }
77
78 pub fn from_toml_str(content: &str) -> Result<Self, String> {
79 toml::from_str(content).map_err(|err| format!("failed to parse showcase config: {err}"))
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::time::{SystemTime, UNIX_EPOCH};
87
88 #[test]
89 fn default_round_trips_through_toml() {
90 let config = ShowcaseConfig::default();
91 let parsed = ShowcaseConfig::from_toml_str(&config.as_toml_string()).expect("valid toml");
92 assert_eq!(parsed, config);
93 }
94
95 #[test]
96 fn parse_custom_values() {
97 let content = r#"
98[project]
99name = "demo"
100entry_crate = "client"
101showcase_crate = "ui-showcase"
102
103[dev]
104port = 7000
105host = "0.0.0.0"
106
107[build]
108out_dir = "dist/showcase"
109base_path = "/showcase"
110"#;
111 let parsed = ShowcaseConfig::from_toml_str(content).expect("should parse");
112 assert_eq!(parsed.project.name, "demo");
113 assert_eq!(parsed.project.entry_crate, "client");
114 assert_eq!(parsed.project.showcase_crate, "ui-showcase");
115 assert_eq!(parsed.dev.port, 7000);
116 assert_eq!(parsed.dev.host, "0.0.0.0");
117 assert_eq!(parsed.build.out_dir, "dist/showcase");
118 assert_eq!(parsed.build.base_path, "/showcase");
119 }
120
121 #[test]
122 fn parse_rejects_invalid_assignment() {
123 let err = ShowcaseConfig::from_toml_str("[project]\nname \"demo\"")
124 .expect_err("missing = should fail");
125 assert!(err.contains("failed to parse showcase config"));
126 }
127
128 #[test]
129 fn parse_rejects_unquoted_string() {
130 let err =
131 ShowcaseConfig::from_toml_str("[project]\nname = demo").expect_err("must be quoted");
132 assert!(err.contains("failed to parse showcase config"));
133 }
134
135 #[test]
136 fn parse_rejects_invalid_port() {
137 let err = ShowcaseConfig::from_toml_str("[dev]\nport = 99999").expect_err("invalid port");
138 assert!(err.contains("failed to parse showcase config"));
139 }
140
141 #[test]
142 fn parse_rejects_unknown_fields() {
143 let err = ShowcaseConfig::from_toml_str("[project]\nunknown = \"value\"")
144 .expect_err("unknown fields should fail");
145 assert!(err.contains("failed to parse showcase config"));
146 }
147
148 #[test]
149 fn parse_fills_missing_sections_from_defaults() {
150 let parsed = ShowcaseConfig::from_toml_str("[project]\nname = \"demo\"")
151 .expect("partial config should parse");
152
153 assert_eq!(parsed.project.name, "demo");
154 assert_eq!(parsed.project.entry_crate, "web");
155 assert_eq!(parsed.project.showcase_crate, "showcase");
156 assert_eq!(parsed.dev, ShowcaseDevConfig::default());
157 assert_eq!(parsed.build, ShowcaseBuildConfig::default());
158 }
159
160 #[test]
161 fn write_default_if_missing_only_writes_once() {
162 let unique = SystemTime::now()
163 .duration_since(UNIX_EPOCH)
164 .expect("clock should be monotonic")
165 .as_nanos();
166 let dir = std::env::temp_dir().join(format!("dioxus-showcase-config-test-{unique}"));
167 std::fs::create_dir_all(&dir).expect("create temp dir");
168 let path = dir.join("DioxusShowcase.toml");
169
170 let first = ShowcaseConfig::write_default_if_missing(&path).expect("first write");
171 let second = ShowcaseConfig::write_default_if_missing(&path).expect("second write");
172 let written = std::fs::read_to_string(&path).expect("read config");
173
174 assert!(first);
175 assert!(!second);
176 assert!(written.contains("[project]"));
177
178 let _ = std::fs::remove_dir_all(&dir);
179 }
180}