use cargo_metadata::camino::Utf8PathBuf;
use serde::Deserialize;
use crate::config::{badge::Badge, layer::ApplyLayer, rustdoc::Rustdoc};
pub(crate) use self::loader::*;
pub(crate) mod badge;
mod de;
mod layer;
mod loader;
pub(crate) mod rustdoc;
#[cfg(test)]
mod testing;
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub(crate) struct Config {
#[serde(
default,
deserialize_with = "de::string_or_seq_opt_of_path_from_source"
)]
pub(crate) extra_targets: Option<Vec<Utf8PathBuf>>,
#[serde(default)]
pub(crate) badge: Badge,
#[serde(default)]
pub(crate) rustdoc: Rustdoc,
}
impl ApplyLayer for Config {
fn apply_layer(&mut self, layer: &Self) {
let Self {
extra_targets,
badge,
rustdoc,
} = self;
extra_targets.apply_layer(&layer.extra_targets);
badge.apply_layer(&layer.badge);
rustdoc.apply_layer(&layer.rustdoc);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum Inheritable<T> {
#[default]
Inherit,
Disabled,
Value(T),
}
impl<T> ApplyLayer for Inheritable<T>
where
T: Clone + ApplyLayer,
{
fn apply_layer(&mut self, layer: &Self) {
match (&mut *self, layer) {
(_, Self::Inherit) => {}
(target, Self::Disabled) => *target = Self::Disabled,
(target @ (Self::Disabled | Self::Inherit), Self::Value(layer)) => {
*target = Self::Value(layer.clone());
}
(Self::Value(target), Self::Value(layer)) => target.apply_layer(layer),
}
}
}
impl<T> Inheritable<T> {
pub(crate) fn as_option(&self) -> Option<&T> {
match self {
Self::Inherit | Self::Disabled => None,
Self::Value(value) => Some(value),
}
}
pub(crate) fn map<U, F>(self, f: F) -> Inheritable<U>
where
F: FnOnce(T) -> U,
{
match self {
Self::Inherit => Inheritable::Inherit,
Self::Disabled => Inheritable::Disabled,
Self::Value(value) => Inheritable::Value(f(value)),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use indoc::indoc;
use similar_asserts::assert_eq;
use crate::config::badge::BadgeStyle;
use super::*;
#[test]
fn config_deserialize_deserializes_extra_targets_from_str() {
let manifest = testing::manifest(indoc! {r#"
[package]
name = "foo"
version = "0.1.0"
[package.metadata.cargo-sync-rdme]
extra-targets = "./docs/target.md"
"#});
let config = manifest.package_config().unwrap().unwrap();
assert_eq!(
config.extra_targets.unwrap(),
["/path/to/workspace/docs/target.md"]
);
}
#[test]
fn config_apply_layer_updates_extra_targets_badge_and_rustdoc() {
let mut target = Config {
extra_targets: Some(vec!["./docs/target.md".into()]),
badge: Badge {
style: Some(BadgeStyle::Flat),
..Badge::default()
},
rustdoc: Rustdoc {
html_root_url: Some("https://docs.example.com/target/".to_owned()),
mappings: HashMap::from([(
"target::TargetType".to_owned(),
"https://reference.example.com/items/target-type".into(),
)]),
..Default::default()
},
};
let layer = Config {
extra_targets: Some(vec!["./docs/layer.md".into()]),
badge: Badge {
style: Some(BadgeStyle::FlatSquare),
..Badge::default()
},
rustdoc: Rustdoc {
html_root_url: Some("https://docs.example.com/layer/".to_owned()),
mappings: HashMap::from([(
"target::LayerType".to_owned(),
"https://reference.example.com/items/layer-type".to_owned(),
)]),
..Default::default()
},
};
target.apply_layer(&layer);
assert_eq!(
target,
Config {
extra_targets: Some(vec!["./docs/layer.md".into()]),
badge: Badge {
style: Some(BadgeStyle::FlatSquare),
default: None,
groups: HashMap::new()
},
rustdoc: Rustdoc {
html_root_url: Some("https://docs.example.com/layer/".to_owned()),
mappings: HashMap::from([
(
"target::TargetType".to_owned(),
"https://reference.example.com/items/target-type".to_owned(),
),
(
"target::LayerType".to_owned(),
"https://reference.example.com/items/layer-type".to_owned(),
),
]),
..Default::default()
},
}
);
}
#[test]
fn config_apply_layer_replaces_extra_targets_with_empty_list() {
let mut target = Config {
extra_targets: Some(vec!["./docs/target.md".into()]),
..Config::default()
};
let layer = Config {
extra_targets: Some(vec![]),
..Config::default()
};
target.apply_layer(&layer);
assert_eq!(target.extra_targets, Some(vec![]));
}
#[test]
fn inheritable_apply_layer_handles_all_state_transitions() {
let mut target = Inheritable::Value("from target".to_owned());
target.apply_layer(&Inheritable::Inherit);
assert_eq!(target, Inheritable::Value("from target".to_owned()));
let mut target = Inheritable::Value("from target".to_owned());
target.apply_layer(&Inheritable::Disabled);
assert_eq!(target, Inheritable::Disabled);
let mut inherited = Inheritable::Inherit;
inherited.apply_layer(&Inheritable::Value("from layer".to_owned()));
assert_eq!(inherited, Inheritable::Value("from layer".to_owned()));
let mut disabled = Inheritable::Disabled;
disabled.apply_layer(&Inheritable::Value("from layer".to_owned()));
assert_eq!(disabled, Inheritable::Value("from layer".to_owned()));
let mut target = Inheritable::Value("from target".to_owned());
target.apply_layer(&Inheritable::Value("from layer".to_owned()));
assert_eq!(target, Inheritable::Value("from layer".to_owned()));
}
#[test]
fn inheritable_helpers_preserve_state() {
assert_eq!(Inheritable::<String>::Inherit.as_option(), None);
assert_eq!(Inheritable::<String>::Disabled.as_option(), None);
assert_eq!(
Inheritable::Value("value".to_owned()).as_option(),
Some(&"value".to_owned())
);
assert_eq!(
Inheritable::Value("value".to_owned()).map(|value| value.len()),
Inheritable::Value(5)
);
assert_eq!(
Inheritable::<String>::Disabled.map(|value| value.len()),
Inheritable::Disabled
);
assert_eq!(
Inheritable::<String>::Inherit.map(|value| value.len()),
Inheritable::Inherit
);
}
}