| //! Deserialization for converting [`ConfigValue`] instances to target types. | |
| //! | |
| //! The [`Deserializer`] type is the main driver of deserialization. | |
| //! The workflow is roughly: | |
| //! | |
| //! 1. [`GlobalContext::get<T>()`] creates [`Deserializer`] and calls `T::deserialize()` | |
| //! 2. Then call type-specific deserialize methods as in normal serde deserialization. | |
| //! - For primitives, `deserialize_*` methods look up [`ConfigValue`] instances | |
| //! in [`GlobalContext`] and convert. | |
| //! - Structs and maps are handled by [`ConfigMapAccess`]. | |
| //! - Sequences are handled by [`ConfigSeqAccess`], | |
| //! which later uses [`ArrayItemDeserializer`] for each array item. | |
| //! - [`Value<T>`] is delegated to [`ValueDeserializer`] in `deserialize_struct`. | |
| //! | |
| //! The purpose of this workflow is to: | |
| //! | |
| //! - Retrieve the correct config value based on source location precedence | |
| //! - Provide richer error context showing where a config is defined | |
| //! - Provide a richer internal API to map to concrete config types | |
| //! without touching underlying [`ConfigValue`] directly | |
| //! | |
| //! [`ConfigValue`]: CV | |
| use crate::config::ArrayItemKeyPath; | |
| use crate::context::value; | |
| use crate::context::{ConfigError, ConfigKey, GlobalContext}; | |
| use crate::context::{ConfigValue as CV, Definition, Value}; | |
| use crate::util::data_structures::HashSet; | |
| use serde::{de, de::IntoDeserializer}; | |
| use std::vec; | |
| /// Serde deserializer used to convert config values to a target type using | |
| /// [`GlobalContext::get`]. | |
| #[derive(Clone)] | |
| pub(super) struct Deserializer<'gctx> { | |
| pub(super) gctx: &'gctx GlobalContext, | |
| /// The current key being deserialized. | |
| pub(super) key: ConfigKey, | |
| /// Whether or not this key part is allowed to be an inner table. For | |
| /// example, `profile.dev.build-override` needs to check if | |
| /// `CARGO_PROFILE_DEV_BUILD_OVERRIDE_` prefixes exist. But | |
| /// `CARGO_BUILD_TARGET` should not check for prefixes because it would | |
| /// collide with `CARGO_BUILD_TARGET_DIR`. See `ConfigMapAccess` for | |
| /// details. | |
| pub(super) env_prefix_ok: bool, | |
| } | |
| macro_rules! deserialize_method { | |
| ($method:ident, $visit:ident, $getter:ident) => { | |
| fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| let v = self | |
| .gctx | |
| .$getter(&self.key)? | |
| .ok_or_else(|| ConfigError::missing(&self.key))?; | |
| let Value { val, definition } = v; | |
| let res: Result<V::Value, ConfigError> = visitor.$visit(val); | |
| res.map_err(|e| e.with_key_context(&self.key, Some(definition))) | |
| } | |
| }; | |
| } | |
| impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> { | |
| type Error = ConfigError; | |
| fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| let cv = self.gctx.get_cv_with_env(&self.key)?; | |
| if let Some(cv) = cv { | |
| let res: (Result<V::Value, ConfigError>, Definition) = match cv { | |
| CV::Integer(i, def) => (visitor.visit_i64(i), def), | |
| CV::String(s, def) => (visitor.visit_string(s), def), | |
| CV::List(_, def) => (visitor.visit_seq(ConfigSeqAccess::new(self.clone())?), def), | |
| CV::Table(_, def) => ( | |
| visitor.visit_map(ConfigMapAccess::new_map(self.clone())?), | |
| def, | |
| ), | |
| CV::Boolean(b, def) => (visitor.visit_bool(b), def), | |
| }; | |
| let (res, def) = res; | |
| return res.map_err(|e| e.with_key_context(&self.key, Some(def))); | |
| } | |
| // The effect here is the same as in `deserialize_option`. | |
| if self.gctx.has_key(&self.key, self.env_prefix_ok)? { | |
| return visitor.visit_some(self); | |
| } | |
| Err(ConfigError::missing(&self.key)) | |
| } | |
| deserialize_method!(deserialize_bool, visit_bool, get_bool); | |
| deserialize_method!(deserialize_i8, visit_i64, get_integer); | |
| deserialize_method!(deserialize_i16, visit_i64, get_integer); | |
| deserialize_method!(deserialize_i32, visit_i64, get_integer); | |
| deserialize_method!(deserialize_i64, visit_i64, get_integer); | |
| deserialize_method!(deserialize_u8, visit_i64, get_integer); | |
| deserialize_method!(deserialize_u16, visit_i64, get_integer); | |
| deserialize_method!(deserialize_u32, visit_i64, get_integer); | |
| deserialize_method!(deserialize_u64, visit_i64, get_integer); | |
| deserialize_method!(deserialize_string, visit_string, get_string_priv); | |
| fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| if self.gctx.has_key(&self.key, self.env_prefix_ok)? { | |
| visitor.visit_some(self) | |
| } else { | |
| // Treat missing values as `None`. | |
| visitor.visit_none() | |
| } | |
| } | |
| fn deserialize_struct<V>( | |
| self, | |
| name: &'static str, | |
| fields: &'static [&'static str], | |
| visitor: V, | |
| ) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| // Match on the magical struct name/field names that are passed in to | |
| // detect when we're deserializing `Value<T>`. | |
| // | |
| // See more comments in `value.rs` for the protocol used here. | |
| if name == value::NAME && fields == value::FIELDS { | |
| let source = ValueSource::with_deserializer(self)?; | |
| return visitor.visit_map(ValueDeserializer::new(source)); | |
| } | |
| visitor.visit_map(ConfigMapAccess::new_struct(self, fields)?) | |
| } | |
| fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| visitor.visit_map(ConfigMapAccess::new_map(self)?) | |
| } | |
| fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| visitor.visit_seq(ConfigSeqAccess::new(self)?) | |
| } | |
| fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| visitor.visit_seq(ConfigSeqAccess::new(self)?) | |
| } | |
| fn deserialize_tuple_struct<V>( | |
| self, | |
| _name: &'static str, | |
| _len: usize, | |
| visitor: V, | |
| ) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| visitor.visit_seq(ConfigSeqAccess::new(self)?) | |
| } | |
| fn deserialize_newtype_struct<V>( | |
| self, | |
| name: &'static str, | |
| visitor: V, | |
| ) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| if name == "StringList" { | |
| let mut res = Vec::new(); | |
| match self.gctx.config()?.get_cv(&self.key)? { | |
| Some(CV::List(val, _def)) => res.extend(val), | |
| Some(CV::String(val, def)) => { | |
| let split_vs = val | |
| .split_whitespace() | |
| .map(|s| CV::String(s.to_string(), def.clone())); | |
| res.extend(split_vs); | |
| } | |
| Some(val) => { | |
| self.gctx | |
| .expected("string or array of strings", &self.key, &val)?; | |
| } | |
| None => {} | |
| } | |
| self.gctx.get_env_list(&self.key, &mut res)?; | |
| let vals: Vec<String> = res | |
| .into_iter() | |
| .map(|val| match val { | |
| CV::String(s, _definition) => Ok(s), | |
| other => Err(ConfigError::expected(&self.key, "string", &other)), | |
| }) | |
| .collect::<Result<_, _>>()?; | |
| visitor.visit_newtype_struct(vals.into_deserializer()) | |
| } else { | |
| visitor.visit_newtype_struct(self) | |
| } | |
| } | |
| fn deserialize_enum<V>( | |
| self, | |
| _name: &'static str, | |
| _variants: &'static [&'static str], | |
| visitor: V, | |
| ) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| let value = self | |
| .gctx | |
| .get_string_priv(&self.key)? | |
| .ok_or_else(|| ConfigError::missing(&self.key))?; | |
| let Value { val, definition } = value; | |
| visitor | |
| .visit_enum(val.into_deserializer()) | |
| .map_err(|e: ConfigError| e.with_key_context(&self.key, Some(definition))) | |
| } | |
| // These aren't really supported, yet. | |
| serde::forward_to_deserialize_any! { | |
| f32 f64 char str bytes | |
| byte_buf unit unit_struct | |
| identifier ignored_any | |
| } | |
| } | |
| struct ConfigMapAccess<'gctx> { | |
| de: Deserializer<'gctx>, | |
| /// The fields that this map should deserialize. | |
| fields: Vec<KeyKind>, | |
| /// Current field being deserialized. | |
| field_index: usize, | |
| } | |
| #[derive(Debug, PartialEq, Eq, Hash)] | |
| enum KeyKind { | |
| Normal(String), | |
| CaseSensitive(String), | |
| } | |
| impl<'gctx> ConfigMapAccess<'gctx> { | |
| fn new_map(de: Deserializer<'gctx>) -> Result<ConfigMapAccess<'gctx>, ConfigError> { | |
| let mut fields = Vec::new(); | |
| if let Some(mut v) = de.gctx.get_table(&de.key)? { | |
| // `v: Value<HashMap<String, CV>>` | |
| for (key, _value) in v.val.drain() { | |
| fields.push(KeyKind::CaseSensitive(key)); | |
| } | |
| } | |
| if de.gctx.cli_unstable().advanced_env { | |
| // `CARGO_PROFILE_DEV_PACKAGE_` | |
| let env_prefix = format!("{}_", de.key.as_env_key()); | |
| for env_key in de.gctx.env_keys() { | |
| // `CARGO_PROFILE_DEV_PACKAGE_bar_OPT_LEVEL = 3` | |
| if let Some(rest) = env_key.strip_prefix(&env_prefix) { | |
| // `rest = bar_OPT_LEVEL` | |
| let part = rest.splitn(2, '_').next().unwrap(); | |
| // `part = "bar"` | |
| fields.push(KeyKind::CaseSensitive(part.to_string())); | |
| } | |
| } | |
| } | |
| Ok(ConfigMapAccess { | |
| de, | |
| fields, | |
| field_index: 0, | |
| }) | |
| } | |
| fn new_struct( | |
| de: Deserializer<'gctx>, | |
| given_fields: &'static [&'static str], | |
| ) -> Result<ConfigMapAccess<'gctx>, ConfigError> { | |
| let table = de.gctx.get_table(&de.key)?; | |
| // Assume that if we're deserializing a struct it exhaustively lists all | |
| // possible fields on this key that we're *supposed* to use, so take | |
| // this opportunity to warn about any keys that aren't recognized as | |
| // fields and warn about them. | |
| if let Some(v) = table.as_ref() { | |
| let unused_keys = v | |
| .val | |
| .iter() | |
| .filter(|(k, _v)| !given_fields.iter().any(|gk| gk == k)); | |
| for (unused_key, unused_value) in unused_keys { | |
| de.gctx.shell().warn(format!( | |
| "unused config key `{}.{}` in `{}`", | |
| de.key, | |
| unused_key, | |
| unused_value.definition() | |
| ))?; | |
| } | |
| } | |
| let mut fields = HashSet::default(); | |
| // If the caller is interested in a field which we can provide from | |
| // the environment, get it from there. | |
| for field in given_fields { | |
| let mut field_key = de.key.clone(); | |
| field_key.push(field); | |
| for env_key in de.gctx.env_keys() { | |
| let Some(nested_field) = env_key.strip_prefix(field_key.as_env_key()) else { | |
| continue; | |
| }; | |
| // This distinguishes fields that share the same prefix. | |
| // For example, when env_key is UNSTABLE_GITOXIDE_FETCH | |
| // and field_key is UNSTABLE_GIT, the field shouldn't be | |
| // added because `unstable.gitoxide.fetch` doesn't | |
| // belong to `unstable.git` struct. | |
| if nested_field.is_empty() || nested_field.starts_with('_') { | |
| fields.insert(KeyKind::Normal(field.to_string())); | |
| } | |
| } | |
| } | |
| // Add everything from the config table we're interested in that we | |
| // haven't already provided via an environment variable | |
| if let Some(v) = table { | |
| for key in v.val.keys() { | |
| fields.insert(KeyKind::Normal(key.clone())); | |
| } | |
| } | |
| Ok(ConfigMapAccess { | |
| de, | |
| fields: fields.into_iter().collect(), | |
| field_index: 0, | |
| }) | |
| } | |
| } | |
| impl<'de, 'gctx> de::MapAccess<'de> for ConfigMapAccess<'gctx> { | |
| type Error = ConfigError; | |
| fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> | |
| where | |
| K: de::DeserializeSeed<'de>, | |
| { | |
| if self.field_index >= self.fields.len() { | |
| return Ok(None); | |
| } | |
| let field = match &self.fields[self.field_index] { | |
| KeyKind::Normal(s) | KeyKind::CaseSensitive(s) => s.as_str(), | |
| }; | |
| seed.deserialize(field.into_deserializer()).map(Some) | |
| } | |
| fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::DeserializeSeed<'de>, | |
| { | |
| let field = &self.fields[self.field_index]; | |
| self.field_index += 1; | |
| // Set this as the current key in the deserializer. | |
| let field = match field { | |
| KeyKind::Normal(field) => { | |
| self.de.key.push(field); | |
| field | |
| } | |
| KeyKind::CaseSensitive(field) => { | |
| self.de.key.push_sensitive(field); | |
| field | |
| } | |
| }; | |
| // Env vars that are a prefix of another with a dash/underscore cannot | |
| // be supported by our serde implementation, so check for them here. | |
| // Example: | |
| // CARGO_BUILD_TARGET | |
| // CARGO_BUILD_TARGET_DIR | |
| // or | |
| // CARGO_PROFILE_DEV_DEBUG | |
| // CARGO_PROFILE_DEV_DEBUG_ASSERTIONS | |
| // The `deserialize_option` method does not know the type of the field. | |
| // If the type is an Option<struct> (like | |
| // `profile.dev.build-override`), then it needs to check for env vars | |
| // starting with CARGO_FOO_BAR_. This is a problem for keys like | |
| // CARGO_BUILD_TARGET because checking for a prefix would incorrectly | |
| // match CARGO_BUILD_TARGET_DIR. `deserialize_option` would have no | |
| // choice but to call `visit_some()` which would then fail if | |
| // CARGO_BUILD_TARGET isn't set. So we check for these prefixes and | |
| // disallow them here. | |
| let env_prefix = format!("{}_", field).replace('-', "_"); | |
| let env_prefix_ok = !self.fields.iter().any(|field| { | |
| let field = match field { | |
| KeyKind::Normal(s) | KeyKind::CaseSensitive(s) => s.as_str(), | |
| }; | |
| field.replace('-', "_").starts_with(&env_prefix) | |
| }); | |
| let result = seed | |
| .deserialize(Deserializer { | |
| gctx: self.de.gctx, | |
| key: self.de.key.clone(), | |
| env_prefix_ok, | |
| }) | |
| .map_err(|e| { | |
| if !e.is_missing_field() { | |
| return e; | |
| } | |
| e.with_key_context( | |
| &self.de.key, | |
| self.de | |
| .gctx | |
| .get_cv_with_env(&self.de.key) | |
| .ok() | |
| .and_then(|cv| cv.map(|cv| cv.definition().clone())), | |
| ) | |
| }); | |
| self.de.key.pop(); | |
| result | |
| } | |
| } | |
| struct ConfigSeqAccess<'gctx> { | |
| de: Deserializer<'gctx>, | |
| list_iter: std::iter::Enumerate<vec::IntoIter<CV>>, | |
| } | |
| impl ConfigSeqAccess<'_> { | |
| fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess<'_>, ConfigError> { | |
| let mut res = Vec::new(); | |
| match de.gctx.config()?.get_cv(&de.key)? { | |
| Some(CV::List(val, _definition)) => { | |
| res.extend(val); | |
| } | |
| Some(val) => { | |
| de.gctx.expected("list", &de.key, &val)?; | |
| } | |
| None => {} | |
| } | |
| de.gctx.get_env_list(&de.key, &mut res)?; | |
| Ok(ConfigSeqAccess { | |
| de, | |
| list_iter: res.into_iter().enumerate(), | |
| }) | |
| } | |
| } | |
| impl<'de, 'gctx> de::SeqAccess<'de> for ConfigSeqAccess<'gctx> { | |
| type Error = ConfigError; | |
| fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error> | |
| where | |
| T: de::DeserializeSeed<'de>, | |
| { | |
| let Some((i, cv)) = self.list_iter.next() else { | |
| return Ok(None); | |
| }; | |
| let mut key_path = ArrayItemKeyPath::new(self.de.key.clone()); | |
| let definition = Some(cv.definition().clone()); | |
| let de = ArrayItemDeserializer { | |
| cv, | |
| key_path: &mut key_path, | |
| }; | |
| seed.deserialize(de) | |
| .map_err(|e| { | |
| // This along with ArrayItemKeyPath provide a better error context of the | |
| // ConfigValue definition + the key path within an array item that native | |
| // TOML key path can't express. For example, `foo.bar[3].baz`. | |
| key_path.push_index(i); | |
| e.with_array_item_key_context(&key_path, definition) | |
| }) | |
| .map(Some) | |
| } | |
| } | |
| /// Source of data for [`ValueDeserializer`] | |
| enum ValueSource<'gctx, 'err> { | |
| /// The deserializer used to actually deserialize a Value struct. | |
| Deserializer { | |
| de: Deserializer<'gctx>, | |
| definition: Definition, | |
| }, | |
| /// A [`ConfigValue`](CV). | |
| /// | |
| /// This is used for situations where you can't address type via a TOML key, | |
| /// such as a value inside an array. | |
| /// The [`ConfigSeqAccess`] doesn't know what type it should deserialize to | |
| /// so [`ArrayItemDeserializer`] needs to be able to handle all of them. | |
| ConfigValue { | |
| cv: CV, | |
| key_path: &'err mut ArrayItemKeyPath, | |
| }, | |
| } | |
| impl<'gctx, 'err> ValueSource<'gctx, 'err> { | |
| fn with_deserializer(de: Deserializer<'gctx>) -> Result<ValueSource<'gctx, 'err>, ConfigError> { | |
| // Figure out where this key is defined. | |
| let definition = { | |
| let env = de.key.as_env_key(); | |
| let env_def = Definition::Environment(env.to_string()); | |
| match ( | |
| de.gctx.env.contains_key(env), | |
| de.gctx.config()?.get_cv(&de.key)?, | |
| ) { | |
| (true, Some(cv)) => { | |
| // Both, pick highest priority. | |
| if env_def.is_higher_priority(cv.definition()) { | |
| env_def | |
| } else { | |
| cv.definition().clone() | |
| } | |
| } | |
| (false, Some(cv)) => cv.definition().clone(), | |
| // Assume it is an environment, even if the key is not set. | |
| // This can happen for intermediate tables, like | |
| // CARGO_FOO_BAR_* where `CARGO_FOO_BAR` is not set. | |
| (_, None) => env_def, | |
| } | |
| }; | |
| Ok(Self::Deserializer { de, definition }) | |
| } | |
| fn with_cv(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> ValueSource<'gctx, 'err> { | |
| ValueSource::ConfigValue { cv, key_path } | |
| } | |
| } | |
| /// This is a deserializer that deserializes into a `Value<T>` for | |
| /// configuration. | |
| /// | |
| /// This is a special deserializer because it deserializes one of its struct | |
| /// fields into the location that this configuration value was defined in. | |
| /// | |
| /// See more comments in `value.rs` for the protocol used here. | |
| struct ValueDeserializer<'gctx, 'err> { | |
| hits: u32, | |
| source: ValueSource<'gctx, 'err>, | |
| } | |
| impl<'gctx, 'err> ValueDeserializer<'gctx, 'err> { | |
| fn new(source: ValueSource<'gctx, 'err>) -> ValueDeserializer<'gctx, 'err> { | |
| Self { hits: 0, source } | |
| } | |
| fn definition(&self) -> &Definition { | |
| match &self.source { | |
| ValueSource::Deserializer { definition, .. } => definition, | |
| ValueSource::ConfigValue { cv, .. } => cv.definition(), | |
| } | |
| } | |
| } | |
| impl<'de, 'gctx, 'err> de::MapAccess<'de> for ValueDeserializer<'gctx, 'err> { | |
| type Error = ConfigError; | |
| fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> | |
| where | |
| K: de::DeserializeSeed<'de>, | |
| { | |
| self.hits += 1; | |
| match self.hits { | |
| 1 => seed | |
| .deserialize(value::VALUE_FIELD.into_deserializer()) | |
| .map(Some), | |
| 2 => seed | |
| .deserialize(value::DEFINITION_FIELD.into_deserializer()) | |
| .map(Some), | |
| _ => Ok(None), | |
| } | |
| } | |
| fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::DeserializeSeed<'de>, | |
| { | |
| // If this is the first time around we deserialize the `value` field | |
| // which is the actual deserializer | |
| if self.hits == 1 { | |
| return match &mut self.source { | |
| ValueSource::Deserializer { de, definition } => seed | |
| .deserialize(de.clone()) | |
| .map_err(|e| e.with_key_context(&de.key, Some(definition.clone()))), | |
| ValueSource::ConfigValue { cv, key_path } => { | |
| let de = ArrayItemDeserializer { | |
| cv: cv.clone(), | |
| key_path, | |
| }; | |
| seed.deserialize(de) | |
| } | |
| }; | |
| } | |
| // ... otherwise we're deserializing the `definition` field, so we need | |
| // to figure out where the field we just deserialized was defined at. | |
| match self.definition() { | |
| Definition::BuiltIn => seed.deserialize(0.into_deserializer()), | |
| Definition::Path(path) => { | |
| seed.deserialize(Tuple2Deserializer(1i32, path.to_string_lossy())) | |
| } | |
| Definition::Environment(env) => { | |
| seed.deserialize(Tuple2Deserializer(2i32, env.as_str())) | |
| } | |
| Definition::Cli(path) => { | |
| let s = path | |
| .as_ref() | |
| .map(|p| p.to_string_lossy()) | |
| .unwrap_or_default(); | |
| seed.deserialize(Tuple2Deserializer(3i32, s)) | |
| } | |
| } | |
| } | |
| } | |
| /// A deserializer for individual [`ConfigValue`](CV) items in arrays | |
| /// | |
| /// It is implemented to handle any types inside a sequence, like `Vec<String>`, | |
| /// `Vec<Value<i32>>`, or even `Vev<HashMap<String, Vec<bool>>>`. | |
| struct ArrayItemDeserializer<'err> { | |
| cv: CV, | |
| key_path: &'err mut ArrayItemKeyPath, | |
| } | |
| impl<'de, 'err> de::Deserializer<'de> for ArrayItemDeserializer<'err> { | |
| type Error = ConfigError; | |
| fn deserialize_struct<V>( | |
| self, | |
| name: &'static str, | |
| fields: &'static [&'static str], | |
| visitor: V, | |
| ) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| // Match on the magical struct name/field names that are passed in to | |
| // detect when we're deserializing `Value<T>`. | |
| // | |
| // See more comments in `value.rs` for the protocol used here. | |
| if name == value::NAME && fields == value::FIELDS { | |
| let source = ValueSource::with_cv(self.cv, self.key_path); | |
| return visitor.visit_map(ValueDeserializer::new(source)); | |
| } | |
| visitor.visit_map(ArrayItemMapAccess::with_struct( | |
| self.cv, | |
| fields, | |
| self.key_path, | |
| )) | |
| } | |
| fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| match self.cv { | |
| CV::String(s, _) => visitor.visit_string(s), | |
| CV::Integer(i, _) => visitor.visit_i64(i), | |
| CV::Boolean(b, _) => visitor.visit_bool(b), | |
| l @ CV::List(_, _) => visitor.visit_seq(ArrayItemSeqAccess::new(l, self.key_path)), | |
| t @ CV::Table(_, _) => visitor.visit_map(ArrayItemMapAccess::new(t, self.key_path)), | |
| } | |
| } | |
| // Forward everything to deserialize_any | |
| serde::forward_to_deserialize_any! { | |
| bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq | |
| bytes byte_buf map option unit newtype_struct | |
| ignored_any unit_struct tuple_struct tuple enum identifier | |
| } | |
| } | |
| /// Sequence access for nested arrays within [`ArrayItemDeserializer`] | |
| struct ArrayItemSeqAccess<'err> { | |
| items: std::iter::Enumerate<vec::IntoIter<CV>>, | |
| key_path: &'err mut ArrayItemKeyPath, | |
| } | |
| impl<'err> ArrayItemSeqAccess<'err> { | |
| fn new(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> ArrayItemSeqAccess<'err> { | |
| let items = match cv { | |
| CV::List(list, _) => list.into_iter().enumerate(), | |
| _ => unreachable!("must be a list"), | |
| }; | |
| Self { items, key_path } | |
| } | |
| } | |
| impl<'de, 'err> de::SeqAccess<'de> for ArrayItemSeqAccess<'err> { | |
| type Error = ConfigError; | |
| fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error> | |
| where | |
| T: de::DeserializeSeed<'de>, | |
| { | |
| match self.items.next() { | |
| Some((i, cv)) => { | |
| let de = ArrayItemDeserializer { | |
| cv, | |
| key_path: self.key_path, | |
| }; | |
| seed.deserialize(de) | |
| .inspect_err(|_| self.key_path.push_index(i)) | |
| .map(Some) | |
| } | |
| None => Ok(None), | |
| } | |
| } | |
| } | |
| /// Map access for nested tables within [`ArrayItemDeserializer`] | |
| struct ArrayItemMapAccess<'err> { | |
| cv: CV, | |
| keys: vec::IntoIter<String>, | |
| current_key: Option<String>, | |
| key_path: &'err mut ArrayItemKeyPath, | |
| } | |
| impl<'err> ArrayItemMapAccess<'err> { | |
| fn new(cv: CV, key_path: &'err mut ArrayItemKeyPath) -> Self { | |
| let keys = match &cv { | |
| CV::Table(map, _) => map.keys().cloned().collect::<Vec<_>>().into_iter(), | |
| _ => unreachable!("must be a map"), | |
| }; | |
| Self { | |
| cv, | |
| keys, | |
| current_key: None, | |
| key_path, | |
| } | |
| } | |
| fn with_struct(cv: CV, given_fields: &[&str], key_path: &'err mut ArrayItemKeyPath) -> Self { | |
| // TODO: We might want to warn unused fields, | |
| // like what we did in ConfigMapAccess::new_struct | |
| let keys = given_fields | |
| .into_iter() | |
| .map(|s| s.to_string()) | |
| .collect::<Vec<_>>() | |
| .into_iter(); | |
| Self { | |
| cv, | |
| keys, | |
| current_key: None, | |
| key_path, | |
| } | |
| } | |
| } | |
| impl<'de, 'err> de::MapAccess<'de> for ArrayItemMapAccess<'err> { | |
| type Error = ConfigError; | |
| fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> | |
| where | |
| K: de::DeserializeSeed<'de>, | |
| { | |
| match self.keys.next() { | |
| Some(key) => { | |
| self.current_key = Some(key.clone()); | |
| seed.deserialize(key.into_deserializer()).map(Some) | |
| } | |
| None => Ok(None), | |
| } | |
| } | |
| fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error> | |
| where | |
| V: de::DeserializeSeed<'de>, | |
| { | |
| let key = self.current_key.take().unwrap(); | |
| match &self.cv { | |
| CV::Table(map, _) => { | |
| if let Some(cv) = map.get(&key) { | |
| let de = ArrayItemDeserializer { | |
| cv: cv.clone(), | |
| key_path: self.key_path, | |
| }; | |
| seed.deserialize(de) | |
| .inspect_err(|_| self.key_path.push_key(key)) | |
| } else { | |
| Err(ConfigError::new( | |
| format!("missing config key `{key}`"), | |
| self.cv.definition().clone(), | |
| )) | |
| } | |
| } | |
| _ => Err(ConfigError::new( | |
| "expected table".to_string(), | |
| self.cv.definition().clone(), | |
| )), | |
| } | |
| } | |
| } | |
| /// A deserializer which takes two values and deserializes into a tuple of those | |
| /// two values. This is similar to types like `StrDeserializer` in upstream | |
| /// serde itself. | |
| struct Tuple2Deserializer<T, U>(T, U); | |
| impl<'de, T, U> de::Deserializer<'de> for Tuple2Deserializer<T, U> | |
| where | |
| T: IntoDeserializer<'de, ConfigError>, | |
| U: IntoDeserializer<'de, ConfigError>, | |
| { | |
| type Error = ConfigError; | |
| fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, ConfigError> | |
| where | |
| V: de::Visitor<'de>, | |
| { | |
| struct SeqVisitor<T, U> { | |
| first: Option<T>, | |
| second: Option<U>, | |
| } | |
| impl<'de, T, U> de::SeqAccess<'de> for SeqVisitor<T, U> | |
| where | |
| T: IntoDeserializer<'de, ConfigError>, | |
| U: IntoDeserializer<'de, ConfigError>, | |
| { | |
| type Error = ConfigError; | |
| fn next_element_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> | |
| where | |
| K: de::DeserializeSeed<'de>, | |
| { | |
| if let Some(first) = self.first.take() { | |
| return seed.deserialize(first.into_deserializer()).map(Some); | |
| } | |
| if let Some(second) = self.second.take() { | |
| return seed.deserialize(second.into_deserializer()).map(Some); | |
| } | |
| Ok(None) | |
| } | |
| } | |
| visitor.visit_seq(SeqVisitor { | |
| first: Some(self.0), | |
| second: Some(self.1), | |
| }) | |
| } | |
| serde::forward_to_deserialize_any! { | |
| bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq | |
| bytes byte_buf map struct option unit newtype_struct | |
| ignored_any unit_struct tuple_struct tuple enum identifier | |
| } | |
| } |