#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManifestTier {
DropIn,
CompatibleWithWarning,
NativeEquivalent,
IntentionalNonParity,
Unsupported,
}
impl ManifestTier {
pub fn as_str(self) -> &'static str {
match self {
ManifestTier::DropIn => "drop_in",
ManifestTier::CompatibleWithWarning => "compatible_with_warning",
ManifestTier::NativeEquivalent => "native_equivalent",
ManifestTier::IntentionalNonParity => "intentional_non_parity",
ManifestTier::Unsupported => "unsupported",
}
}
}
pub fn manifest_tier_for_category(category: &str) -> ManifestTier {
match category {
"alive-check" | "test-mode" | "ssl-no-listener" | "trojan-auto-tls"
| "get-static-content" | "reuse-port" => ManifestTier::NativeEquivalent,
"debug-mode"
| "pac-serving"
| "verbose-mode"
| "scheduler"
| "credential-in-toml"
| "rulefile-partial"
| "rulefile-parse"
| "rulefile-read"
| "direct-mode"
| "ul-no-listener"
| "log-file"
| "config-schema"
| "reverse-proxy-warning"
| "socks5_udp_framing_divergence"
| "system-proxy"
| "auth-timeout" => ManifestTier::CompatibleWithWarning,
"socks4_bind_unsupported" | "socks5_bind_unsupported" => ManifestTier::Unsupported,
_ => ManifestTier::Unsupported,
}
}
pub fn manifest_tier_for_unsupported_feature(feature: &'static str) -> ManifestTier {
match crate::diagnostics::classify_unsupported_feature_tier(feature) {
"drop_in" => ManifestTier::DropIn,
"compatible_with_warning" => ManifestTier::CompatibleWithWarning,
"native_equivalent" => ManifestTier::NativeEquivalent,
"intentional_non_parity" => ManifestTier::IntentionalNonParity,
_ => ManifestTier::Unsupported,
}
}
pub fn classify_aggregate_tier(
warnings: &[CompatWarning],
unsupported: &[UnsupportedFeature],
) -> ManifestTier {
if unsupported
.iter()
.any(|u| manifest_tier_for_unsupported_feature(u.feature) == ManifestTier::Unsupported)
{
return ManifestTier::Unsupported;
}
if warnings
.iter()
.any(|w| manifest_tier_for_category(w.category) == ManifestTier::Unsupported)
{
return ManifestTier::Unsupported;
}
if unsupported.iter().any(|u| {
manifest_tier_for_unsupported_feature(u.feature) == ManifestTier::IntentionalNonParity
}) || warnings
.iter()
.any(|w| manifest_tier_for_category(w.category) == ManifestTier::IntentionalNonParity)
{
return ManifestTier::IntentionalNonParity;
}
if warnings
.iter()
.any(|w| manifest_tier_for_category(w.category) == ManifestTier::CompatibleWithWarning)
{
return ManifestTier::CompatibleWithWarning;
}
if warnings
.iter()
.any(|w| manifest_tier_for_category(w.category) == ManifestTier::NativeEquivalent)
{
return ManifestTier::NativeEquivalent;
}
ManifestTier::DropIn
}
use crate::warnings::{CompatWarning, UnsupportedFeature};
#[cfg(test)]
mod tests {
use super::*;
fn warn(category: &'static str) -> CompatWarning {
CompatWarning {
category,
message: String::new(),
}
}
fn unsupported(feature: &'static str) -> UnsupportedFeature {
UnsupportedFeature {
feature,
detail: String::new(),
}
}
#[test]
fn empty_input_is_drop_in() {
let tier = classify_aggregate_tier(&[], &[]);
assert_eq!(tier, ManifestTier::DropIn);
}
#[test]
fn native_equivalent_warning_only() {
let tier = classify_aggregate_tier(&[warn("alive-check")], &[]);
assert_eq!(tier, ManifestTier::NativeEquivalent);
}
#[test]
fn compatible_warning_only() {
let tier = classify_aggregate_tier(&[warn("direct-mode")], &[]);
assert_eq!(tier, ManifestTier::CompatibleWithWarning);
}
#[test]
fn compatible_with_warning_dominates_native_equivalent() {
let tier = classify_aggregate_tier(&[warn("alive-check"), warn("direct-mode")], &[]);
assert_eq!(tier, ManifestTier::CompatibleWithWarning);
}
#[test]
fn intentional_non_parity_unsupported_feature_only() {
for feature in [
"ssh-listener",
"ssh-upstream",
"ssr-listener",
"ssr-upstream",
] {
let tier = classify_aggregate_tier(&[], &[unsupported(feature)]);
assert_eq!(
tier,
ManifestTier::IntentionalNonParity,
"expected intentional_non_parity for {feature}, got {tier:?}"
);
}
}
#[test]
fn intentional_non_parity_dominates_compatible_warning() {
let tier = classify_aggregate_tier(
&[],
&[unsupported("ssh-listener"), unsupported("ssh-upstream")],
);
let tier_with_warning =
classify_aggregate_tier(&[warn("direct-mode")], &[unsupported("ssh-listener")]);
assert_eq!(tier, ManifestTier::IntentionalNonParity);
assert_eq!(tier_with_warning, ManifestTier::IntentionalNonParity);
}
#[test]
fn hard_unsupported_dominates_intentional_non_parity() {
let tier =
classify_aggregate_tier(&[], &[unsupported("ssh-listener"), unsupported("daemon")]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn hard_unsupported_dominates_compatible_warning() {
let tier = classify_aggregate_tier(&[warn("direct-mode")], &[unsupported("daemon")]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn unknown_warning_category_is_unsupported() {
let tier = classify_aggregate_tier(&[warn("totally-new-category")], &[]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn unknown_unsupported_feature_id_is_unsupported() {
let tier = classify_aggregate_tier(&[], &[unsupported("made-up-feature")]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn socks4_bind_unsupported_aggregates_to_unsupported() {
let tier = classify_aggregate_tier(&[], &[unsupported("socks4-bind")]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn socks5_bind_unsupported_aggregates_to_unsupported() {
let tier = classify_aggregate_tier(&[], &[unsupported("socks5-bind")]);
assert_eq!(tier, ManifestTier::Unsupported);
}
#[test]
fn tier_strings_match_manifest() {
assert_eq!(ManifestTier::DropIn.as_str(), "drop_in");
assert_eq!(
ManifestTier::CompatibleWithWarning.as_str(),
"compatible_with_warning"
);
assert_eq!(ManifestTier::NativeEquivalent.as_str(), "native_equivalent");
assert_eq!(
ManifestTier::IntentionalNonParity.as_str(),
"intentional_non_parity"
);
assert_eq!(ManifestTier::Unsupported.as_str(), "unsupported");
}
#[test]
fn log_file_is_compatible_with_warning() {
assert_eq!(
manifest_tier_for_category("log-file"),
ManifestTier::CompatibleWithWarning
);
}
#[test]
fn socks4_bind_category_maps_to_unsupported() {
assert_eq!(
manifest_tier_for_category("socks4-bind"),
ManifestTier::Unsupported
);
}
#[test]
fn socks5_bind_category_maps_to_unsupported() {
assert_eq!(
manifest_tier_for_category("socks5-bind"),
ManifestTier::Unsupported
);
}
#[test]
fn manifest_tier_for_unsupported_feature_uses_native_classification() {
assert_eq!(
manifest_tier_for_unsupported_feature("ssh-listener"),
ManifestTier::IntentionalNonParity
);
assert_eq!(
manifest_tier_for_unsupported_feature("ssr-upstream"),
ManifestTier::IntentionalNonParity
);
assert_eq!(
manifest_tier_for_unsupported_feature("legacy-cipher"),
ManifestTier::Unsupported
);
assert_eq!(
manifest_tier_for_unsupported_feature("daemon"),
ManifestTier::Unsupported
);
assert_eq!(
manifest_tier_for_unsupported_feature("made-up-feature"),
ManifestTier::Unsupported
);
}
}