Repo of no-std crates for my personal embedded projects

feat: Config format tweak with stronger type validation #8

merged opened by sachy.dev targeting main from format-validate

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.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:usjm3ynnir6y4inkcdovrfei/sh.tangled.repo.pull/3md646poqio22
+94 -15
Diff #1
+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
+90 -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 {}, use [statics] for these", name, 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 + 160 + #[test] 161 + fn it_disallows_string_constants() { 162 + let input = r#"[constants] 163 + FIRST = { type = "&str", value = "42" } 164 + "#; 165 + 166 + let expected = "FIRST is a &str, use [statics] for these"; 167 + 168 + let output = parse_config(input).expect_err("Output somehow successfully parsed"); 169 + 170 + assert_eq!(output.to_string(), expected); 171 + } 172 + 97 173 #[test] 98 174 fn it_handles_only_statics() -> Result<()> { 99 175 let input = r#"[statics] ··· 112 188 #[test] 113 189 fn it_handles_only_constants() -> Result<()> { 114 190 let input = r#"[constants] 115 - FIRST = { type = "u8", value = 42 } 191 + FIRST = { type = "u8", value = "42" } 116 192 "#; 117 193 118 194 let expected = "pub const FIRST: u8 = 42;\n"; ··· 149 225 #[test] 150 226 fn it_outputs_to_file() -> Result<()> { 151 227 let input = r#"[constants] 152 - FIRST = { type = "u8", value = 2 } 153 - VALUE = { type = "u64", value = 42 } 228 + FIRST = { type = "u128", value = "340282366920938463463374607431768211455" } 229 + VALUE = { type = "u64", value = "42" } 154 230 [statics] 155 231 EXAMPLE = "thing" 156 232 "#; 157 233 let expected = r#"pub static EXAMPLE: &str = "thing"; 158 - pub const FIRST: u8 = 2; 234 + pub const FIRST: u128 = 340282366920938463463374607431768211455; 159 235 pub const VALUE: u64 = 42; 160 236 "#; 161 237 let output: Vec<u8> = Vec::new();

History

2 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
feat: Config format tweak with stronger type validation
2/2 success
expand
expand 0 comments
pull request successfully merged
sachy.dev submitted #0
1 commit
expand
feat: Config format tweak with stronger type validation
2/2 success
expand
expand 0 comments