use serde::{Deserialize, Deserializer, de};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum VueVersion {
#[default]
V3,
V2_7,
V2,
V1,
V0_11,
V0_10,
}
impl VueVersion {
pub const ALL: [VueVersion; 6] = [
Self::V3,
Self::V2_7,
Self::V2,
Self::V1,
Self::V0_11,
Self::V0_10,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::V3 => "3",
Self::V2_7 => "2.7",
Self::V2 => "2",
Self::V1 => "1",
Self::V0_11 => "0.11",
Self::V0_10 => "0.10",
}
}
pub const fn is_legacy(self) -> bool {
!matches!(self, Self::V3)
}
pub fn from_config_str(raw: &str) -> Result<Self, ParseVueVersionError> {
let trimmed = raw.trim();
let bare = trimmed.strip_prefix(['v', 'V']).unwrap_or(trimmed);
match bare {
"3" => Ok(Self::V3),
"2.7" => Ok(Self::V2_7),
"2" => Ok(Self::V2),
"1" => Ok(Self::V1),
"0.11" => Ok(Self::V0_11),
"0.10" => Ok(Self::V0_10),
"0" => Err(ParseVueVersionError {
raw: raw.into(),
kind: ParseVueVersionErrorKind::AmbiguousZero,
}),
_ => Err(ParseVueVersionError {
raw: raw.into(),
kind: ParseVueVersionErrorKind::Unknown,
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseVueVersionError {
raw: crate::String,
kind: ParseVueVersionErrorKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ParseVueVersionErrorKind {
AmbiguousZero,
Unknown,
}
impl std::fmt::Display for ParseVueVersionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let raw = self.raw.as_str();
match self.kind {
ParseVueVersionErrorKind::AmbiguousZero => write!(
f,
"ambiguous vue.version \"{raw}\": Vue 0.10 and the 0.11-era line are \
distinct dialects; use \"0.10\" or \"0.11\""
),
ParseVueVersionErrorKind::Unknown => write!(
f,
"unknown vue.version \"{raw}\": expected \"3\" (default), \"2.7\", \
\"2\", \"1\", \"0.11\", or \"0.10\""
),
}
}
}
impl std::error::Error for ParseVueVersionError {}
impl<'de> Deserialize<'de> for VueVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VueVersionVisitor;
impl de::Visitor<'_> for VueVersionVisitor {
type Value = VueVersion;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a Vue version string: \"3\", \"2.7\", \"2\", \"1\", \"0.11\", or \"0.10\" \
(quote the value; 0.10 and 0.11 are not representable as numbers)",
)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
VueVersion::from_config_str(value).map_err(E::custom)
}
}
deserializer.deserialize_str(VueVersionVisitor)
}
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub(crate) struct RawVueConfig {
pub(crate) version: Option<VueVersion>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_canonical_and_v_prefixed_values() {
for dialect in VueVersion::ALL {
assert_eq!(VueVersion::from_config_str(dialect.as_str()), Ok(dialect));
let prefixed = crate::cstr!("v{}", dialect.as_str());
assert_eq!(VueVersion::from_config_str(&prefixed), Ok(dialect));
}
assert_eq!(
VueVersion::from_config_str(" 2.7 "),
Ok(VueVersion::V2_7),
"values are trimmed"
);
}
#[test]
fn only_vue3_is_not_legacy() {
for dialect in VueVersion::ALL {
assert_eq!(dialect.is_legacy(), dialect != VueVersion::V3);
}
}
#[test]
fn rejects_bare_zero_as_ambiguous() {
for raw in ["0", "v0", "V0"] {
let error = VueVersion::from_config_str(raw).unwrap_err();
let message = crate::cstr!("{error}");
assert!(message.contains("ambiguous"), "{message}");
assert!(message.contains("\"0.10\""), "{message}");
assert!(message.contains("\"0.11\""), "{message}");
}
}
#[test]
fn rejects_unknown_values_with_expected_list() {
for raw in ["2.6", "4", "vue2", ""] {
let error = VueVersion::from_config_str(raw).unwrap_err();
let message = crate::cstr!("{error}");
assert!(message.contains("unknown vue.version"), "{message}");
assert!(message.contains("\"2.7\""), "{message}");
assert!(message.contains("\"0.10\""), "{message}");
}
}
#[test]
fn deserializes_strings_and_rejects_numbers() {
let dialect: VueVersion = serde_json::from_str("\"0.10\"").unwrap();
assert_eq!(dialect, VueVersion::V0_10);
let error = serde_json::from_str::<VueVersion>("2.7").unwrap_err();
let message = crate::cstr!("{error}");
assert!(message.contains("Vue version string"), "{message}");
let error = serde_json::from_str::<VueVersion>("\"0\"").unwrap_err();
let message = crate::cstr!("{error}");
assert!(message.contains("ambiguous"), "{message}");
}
}