Fixes the config format to require values to be strings, so to avoid problems with toml Value not being able to accommodate certain types, but then adds parsing according to the declared type to ensure the values are valid before being formatted into constants.
+4
-1
.tangled/workflows/test.yml
+4
-1
.tangled/workflows/test.yml
···
10
10
- cargo
11
11
- rustfmt
12
12
- protobuf
13
+
- cargo-nextest
13
14
14
15
steps:
15
16
- name: Format check
16
17
command: cargo fmt --all --check
17
18
- name: Tests
18
-
command: cargo test --workspace --locked
19
+
command: cargo nextest run --workspace --locked --no-fail-fast
20
+
- name: Doc Tests
21
+
command: cargo test --workspace --locked --doc --no-fail-fast
+77
-14
sachy-config/src/lib.rs
+77
-14
sachy-config/src/lib.rs
···
1
+
use core::{error::Error, fmt::Debug, str::FromStr};
1
2
use std::io::Write;
2
3
3
-
use miette::{IntoDiagnostic, Result, miette};
4
+
use miette::{Context, IntoDiagnostic, Result, bail, miette};
5
+
use toml_edit::Value;
6
+
7
+
fn validate_kind_to_string<T: Debug + FromStr + Send + Sync>(
8
+
name: &str,
9
+
kind: &str,
10
+
value: &Value,
11
+
) -> Result<String>
12
+
where
13
+
T::Err: Send + Sync + Error + 'static,
14
+
{
15
+
value
16
+
.as_str()
17
+
.map_or_else(
18
+
|| Err(miette!("{} is not a value", value)),
19
+
|value_str| {
20
+
value_str.parse().into_diagnostic().wrap_err_with(|| {
21
+
format!("Parsing failure of constant \"{}\" as {}", name, kind)
22
+
})
23
+
},
24
+
)
25
+
.map(|value: T| format!("pub const {}: {} = {:?};\n", name, kind, value))
26
+
}
4
27
5
28
pub fn output_config<W: Write>(config: &str, mut writer: W) -> Result<()> {
6
29
let output = parse_config(config)?;
···
55
78
)
56
79
})?;
57
80
58
-
let constant_output = format!(
59
-
"pub const {}: {} = {};\n",
60
-
name,
61
-
kind,
62
-
value.clone().decorated("", "")
63
-
);
64
-
output.push_str(&constant_output);
81
+
let line = match kind {
82
+
"u8" => validate_kind_to_string::<u8>(name, kind, value)?,
83
+
"i8" => validate_kind_to_string::<i8>(name, kind, value)?,
84
+
"u16" => validate_kind_to_string::<u16>(name, kind, value)?,
85
+
"i16" => validate_kind_to_string::<i16>(name, kind, value)?,
86
+
"u32" => validate_kind_to_string::<u32>(name, kind, value)?,
87
+
"i32" => validate_kind_to_string::<i32>(name, kind, value)?,
88
+
"u64" => validate_kind_to_string::<u64>(name, kind, value)?,
89
+
"i64" => validate_kind_to_string::<i64>(name, kind, value)?,
90
+
"u128" => validate_kind_to_string::<u128>(name, kind, value)?,
91
+
"i128" => validate_kind_to_string::<i128>(name, kind, value)?,
92
+
"f32" => validate_kind_to_string::<f32>(name, kind, value)?,
93
+
"f64" => validate_kind_to_string::<f64>(name, kind, value)?,
94
+
"&str" => bail!("{} is a string, use [statics] for these", kind),
95
+
_ => bail!("Unsupported type: {}", kind),
96
+
};
97
+
98
+
output.push_str(&line);
99
+
65
100
Ok(())
66
101
})?;
67
102
···
77
112
#[test]
78
113
fn it_parses_config() -> Result<()> {
79
114
let input = r#"[constants]
80
-
FIRST = { type = "u8", value = 2 }
81
-
VALUE = { type = "f32", value = 42.0 }
115
+
FIRST = { type = "u8", value = "2" }
116
+
VALUE = { type = "f32", value = "42.0" }
82
117
[statics]
83
118
EXAMPLE = "thing"
84
119
"#;
···
94
129
Ok(())
95
130
}
96
131
132
+
#[test]
133
+
fn it_validates_value_types() -> Result<()> {
134
+
let input = r#"[constants]
135
+
FIRST = { type = "u8", value = "1234" }
136
+
VALUE = { type = "f32", value = "42.0" }
137
+
[statics]
138
+
EXAMPLE = "thing"
139
+
"#;
140
+
141
+
let output = parse_config(input);
142
+
143
+
let report = output.expect_err("Output was somehow successful");
144
+
145
+
let mut error_chain = report.chain();
146
+
147
+
assert_eq!(
148
+
"Parsing failure of constant \"FIRST\" as u8",
149
+
error_chain.next().unwrap().to_string()
150
+
);
151
+
assert_eq!(
152
+
"number too large to fit in target type",
153
+
error_chain.next().unwrap().to_string()
154
+
);
155
+
assert!(error_chain.next().is_none());
156
+
157
+
Ok(())
158
+
}
159
+
97
160
#[test]
98
161
fn it_handles_only_statics() -> Result<()> {
99
162
let input = r#"[statics]
···
112
175
#[test]
113
176
fn it_handles_only_constants() -> Result<()> {
114
177
let input = r#"[constants]
115
-
FIRST = { type = "u8", value = 42 }
178
+
FIRST = { type = "u8", value = "42" }
116
179
"#;
117
180
118
181
let expected = "pub const FIRST: u8 = 42;\n";
···
149
212
#[test]
150
213
fn it_outputs_to_file() -> Result<()> {
151
214
let input = r#"[constants]
152
-
FIRST = { type = "u8", value = 2 }
153
-
VALUE = { type = "u64", value = 42 }
215
+
FIRST = { type = "u128", value = "340282366920938463463374607431768211455" }
216
+
VALUE = { type = "u64", value = "42" }
154
217
[statics]
155
218
EXAMPLE = "thing"
156
219
"#;
157
220
let expected = r#"pub static EXAMPLE: &str = "thing";
158
-
pub const FIRST: u8 = 2;
221
+
pub const FIRST: u128 = 340282366920938463463374607431768211455;
159
222
pub const VALUE: u64 = 42;
160
223
"#;
161
224
let output: Vec<u8> = Vec::new();
History
2 rounds
0 comments
1 commit
expand
collapse
feat: Config format tweak with stronger type validation
Fixes the config format to require values to be strings, so to avoid problems with toml Value not being able to accommodate
certain types, but then adds parsing according to the declared type to ensure the values are valid before being
formatted into constants.
2/2 success
expand
collapse
expand 0 comments
pull request successfully merged
sachy.dev
submitted
#0
1 commit
expand
collapse
feat: Config format tweak with stronger type validation
Fixes the config format to require values to be strings, so to avoid problems with toml Value not being able to accommodate
certain types, but then adds parsing according to the declared type to ensure the values are valid before being
formatted into constants.