conf 0.4.5

A derive-based config parser for CLI args, env, and structured config files
Documentation
use super::NextValueProducer;
use crate::{Conf, ConfContext, InitializationStateMachine, InnerError, Subcommands};

/// Extension to Conf trait with serde-integration implementation details.
///
/// To derive this trait, use `#[derive(Conf)]` together with the `#[conf(serde)]` annotation
/// on your struct.
///
/// Requires the `"serde"` feature to be enabled (it is on by default).
///
/// Any flattened substructures must also have `#[conf(serde)]`.
/// May cause requirements on other fields to support `serde::Deserialize` --
/// you can use `#[conf(serde(skip))]` to avoid that.
///
/// Note that this is NOT based on deriving `serde::Deserialize` on your structure, because
/// we would not be able to properly implement layering or to collect errors comprehensively.
/// Attributes such as `#[serde(rename)]` on your conf structure will have no effect.
/// Only `#[conf(serde(...))]` options will affect the behavior.
///
/// **Hand-written implementations of this trait are not supported**.
///
/// You should think of this trait as a "non-exhaustive trait" with hidden required items.
/// `conf` is free to add, modify, or remove these implementation details without considering it
/// a semver breaking change, so the only stable way to get an impl is via the derive macro.
pub trait ConfSerde: Conf + Sized {
    /// The initialization state machine that was code-genned for this struct
    ///
    /// To obtain an actual seed, implementing serde DeserializeSeed, use ConfSerdeSeed.
    #[doc(hidden)]
    type ISM<'a>: From<ConfSerdeContext<'a>>
        + for<'de> InitializationStateMachine<'de, Value = Self>;

    /// Struct name used with serde
    #[doc(hidden)]
    const STRUCT_NAME: &str;

    /// Struct keys used with serde (exhaustive, including aliases)
    /// None if they aren't known at build-time due to e.g. flatten
    #[doc(hidden)]
    const STRUCT_KEYS: Option<&[&str]>;

    /// Expecting text used with serde
    #[doc(hidden)]
    fn expecting(f: &mut core::fmt::Formatter) -> core::fmt::Result;
}

/// Extension to Subcommands trait with serde-integration implementation details.
///
/// To derive this trait, use `#[derive(Subcommands)]` together with the `#[conf(serde)]` annotation
/// on your enum.
///
/// Requires the `"serde"` feature to be enabled (it is on by default).
///
/// Each variant must have a `Conf` struct which is annotated `#[conf(serde)]`, or else
/// the variant must be marked `#[conf(serde(skip))]`.
///
/// **Hand-written implementations of this trait are not supported**.
///
/// You should think of this trait as a "non-exhaustive trait" with hidden required items.
/// `conf` is free to add, modify, or remove these implementation details without considering it
/// a semver breaking change, so the only stable way to get an impl is via the derive macro.
pub trait SubcommandsSerde: Subcommands + Sized {
    /// List of (command_name, serde_name) pairs, for those subcommands that are not serde(skip)
    #[doc(hidden)]
    const SERDE_NAMES: &'static [(&'static str, &'static str)];

    // Similar to Subcommands::from_conf_context but now with serde data as well.
    //
    // Arguments:
    // * command_name: The command name that appeared on the command line
    // * ctxt: ConfSerdeContext for the subcommand
    // * map_access: Ready to provide the next_value corresponding to this subcommand.
    //
    // Callee is guaranteed that:
    // * command_name is one of the command names listed in Self::SERDE_NAMES
    // * command_name and conf_serde_context are the results of `ConfSerdeContext::for_subcommand`,
    //   so this command name appeared on the command-line.
    // * the key from the most recent MapAccess::next_key() call matched to a serde_name that is
    //   paired with this command_name in Self::SERDE_NAMES.
    //
    // Note:
    // MapAccess is provided to allow calling next_value_seed.
    // We should really wrap that so that only that function can be called,
    // and only once, in order to have a stronger API.
    // For now this is all doc(hidden) anyways.
    #[doc(hidden)]
    fn from_conf_serde_context<'de, NVP>(
        command_name: &str,
        ctxt: ConfSerdeContext,
        next_value_producer: NVP,
    ) -> Result<Self, Vec<InnerError>>
    where
        NVP: NextValueProducer<'de>;
}

/// A regular [`ConfContext`], plus any additional context about the serde document we are parsing.
/// An instance of this should be contained in any [`ConfSerde::Seed`] type.
///
/// Note: Depth parameter isn't really used anymore, but it is there anyways.
/// Note: This is non-exhaustive mainly so that the proc-macro code uses the functions
///       to construct it, and we add more functions as needed, which is more maintainable.
#[doc(hidden)]
#[non_exhaustive]
pub struct ConfSerdeContext<'a> {
    pub conf_context: ConfContext<'a>,
    pub document_name: &'a str,
    pub depth: usize,
}

impl<'a> ConfSerdeContext<'a> {
    /// Create `ConfSerdeContext` from `ConfContext` and document name
    pub(crate) fn new(mut conf_context: ConfContext<'a>, document_name: &'a str) -> Self {
        // Mark that a serde source is present
        conf_context.serde_source_is_present = true;
        Self {
            conf_context,
            document_name,
            depth: 0,
        }
    }

    /// Same as [`ConfContext::for_flattened`] but now for a `ConfSerdeContext`
    pub fn for_flattened(&self, id_prefix: &str) -> Self {
        Self {
            conf_context: self.conf_context.for_flattened(id_prefix),
            document_name: self.document_name,
            depth: self.depth + 1,
        }
    }

    /// Same as [`ConfContext::for_subcommand`], but now for a `ConfSerdeContext`
    pub fn for_subcommand(&self) -> Option<(String, Self)> {
        self.conf_context
            .for_subcommand()
            .map(|(subcommand_name, conf_context)| {
                (
                    subcommand_name,
                    Self {
                        conf_context,
                        document_name: self.document_name,
                        depth: self.depth,
                    },
                )
            })
    }
}