diff --git a/rust/extractor/src/config.rs b/rust/extractor/src/config.rs index dfa9f5d37d20..66edb94c7f85 100644 --- a/rust/extractor/src/config.rs +++ b/rust/extractor/src/config.rs @@ -1,6 +1,9 @@ +mod deserialize_vec; + use anyhow::Context; use clap::Parser; use codeql_extractor::trap; +use deserialize_vec::deserialize_newline_or_comma_separated; use figment::{ providers::{Env, Format, Serialized, Yaml}, value::Value, @@ -14,7 +17,7 @@ use ra_ap_intern::Symbol; use ra_ap_paths::Utf8PathBuf; use ra_ap_project_model::{CargoConfig, CargoFeatures, CfgOverrides, RustLibSource}; use rust_extractor_macros::extractor_cli_config; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::ops::Not; use std::path::PathBuf; @@ -37,14 +40,6 @@ impl From for trap::Compression { } } -// required by the extractor_cli_config macro. -fn deserialize_newline_or_comma_separated<'a, D: Deserializer<'a>, T: for<'b> From<&'b str>>( - deserializer: D, -) -> Result, D::Error> { - let value = String::deserialize(deserializer)?; - Ok(value.split(['\n', ',']).map(T::from).collect()) -} - #[extractor_cli_config] pub struct Config { pub scratch_dir: PathBuf, diff --git a/rust/extractor/src/config/deserialize_vec.rs b/rust/extractor/src/config/deserialize_vec.rs new file mode 100644 index 000000000000..eebe413ca298 --- /dev/null +++ b/rust/extractor/src/config/deserialize_vec.rs @@ -0,0 +1,49 @@ +use serde::de::Visitor; +use serde::Deserializer; +use std::fmt::Formatter; +use std::marker::PhantomData; + +// phantom data ise required to allow parametrizing on `T` without actual `T` data +struct VectorVisitor>(PhantomData); + +impl> VectorVisitor { + fn new() -> Self { + VectorVisitor(PhantomData) + } +} + +impl<'de, T: From> Visitor<'de> for VectorVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("either a sequence, or a comma or newline separated string") + } + + fn visit_str(self, value: &str) -> Result, E> { + Ok(value + .split(['\n', ',']) + .map(|s| T::from(s.to_owned())) + .collect()) + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let mut ret = Vec::new(); + while let Some(el) = seq.next_element::()? { + ret.push(T::from(el)); + } + Ok(ret) + } +} + +/// deserialize into a vector of `T` either of: +/// * a sequence of elements serializable into `String`s, or +/// * a single element serializable into `String`, then split on `,` and `\n` +/// This is required to be in scope when the `extractor_cli_config` macro is used. +pub(crate) fn deserialize_newline_or_comma_separated<'a, D: Deserializer<'a>, T: From>( + deserializer: D, +) -> Result, D::Error> { + deserializer.deserialize_seq(VectorVisitor::new()) +}