Repo of no-std crates for my personal embedded projects

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.

+81 -15
+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
··· 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 "#; ··· 95 130 } 96 131 97 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 + 160 + #[test] 98 161 fn it_handles_only_statics() -> Result<()> { 99 162 let input = r#"[statics] 100 163 EXAMPLE = "thing" ··· 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();