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
+81 -15
Diff #0
+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 "#; ··· 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
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