use crate::error::UnknownFixVersion;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FixVersion {
Fix40,
Fix41,
Fix42,
Fix43,
Fix44,
Fix50,
Fix50Sp1,
Fix50Sp2,
Fixt11,
}
impl FixVersion {
pub const ALL: [Self; 9] = [
Self::Fix40,
Self::Fix41,
Self::Fix42,
Self::Fix43,
Self::Fix44,
Self::Fix50,
Self::Fix50Sp1,
Self::Fix50Sp2,
Self::Fixt11,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Fix40 => "FIX.4.0",
Self::Fix41 => "FIX.4.1",
Self::Fix42 => "FIX.4.2",
Self::Fix43 => "FIX.4.3",
Self::Fix44 => "FIX.4.4",
Self::Fix50 => "FIX.5.0",
Self::Fix50Sp1 => "FIX.5.0SP1",
Self::Fix50Sp2 => "FIX.5.0SP2",
Self::Fixt11 => "FIXT.1.1",
}
}
#[must_use]
pub const fn begin_string(self) -> &'static str {
match self {
Self::Fix40 => "FIX.4.0",
Self::Fix41 => "FIX.4.1",
Self::Fix42 => "FIX.4.2",
Self::Fix43 => "FIX.4.3",
Self::Fix44 => "FIX.4.4",
Self::Fix50 | Self::Fix50Sp1 | Self::Fix50Sp2 | Self::Fixt11 => "FIXT.1.1",
}
}
pub const fn appl_ver_id(self) -> Option<&'static str> {
match self {
Self::Fix50 => Some("7"),
Self::Fix50Sp1 => Some("8"),
Self::Fix50Sp2 => Some("9"),
Self::Fix40 | Self::Fix41 | Self::Fix42 | Self::Fix43 | Self::Fix44 | Self::Fixt11 => {
None
}
}
}
#[must_use]
pub const fn uses_fixt(self) -> bool {
matches!(
self,
Self::Fix50 | Self::Fix50Sp1 | Self::Fix50Sp2 | Self::Fixt11
)
}
}
impl fmt::Display for FixVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for FixVersion {
type Err = UnknownFixVersion;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::ALL
.into_iter()
.find(|version| version.as_str() == value)
.ok_or_else(|| UnknownFixVersion::new(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fix_version_mapping_is_exhaustive_and_exact() {
let expected = [
(FixVersion::Fix40, "FIX.4.0", "FIX.4.0", None, false),
(FixVersion::Fix41, "FIX.4.1", "FIX.4.1", None, false),
(FixVersion::Fix42, "FIX.4.2", "FIX.4.2", None, false),
(FixVersion::Fix43, "FIX.4.3", "FIX.4.3", None, false),
(FixVersion::Fix44, "FIX.4.4", "FIX.4.4", None, false),
(FixVersion::Fix50, "FIX.5.0", "FIXT.1.1", Some("7"), true),
(
FixVersion::Fix50Sp1,
"FIX.5.0SP1",
"FIXT.1.1",
Some("8"),
true,
),
(
FixVersion::Fix50Sp2,
"FIX.5.0SP2",
"FIXT.1.1",
Some("9"),
true,
),
(FixVersion::Fixt11, "FIXT.1.1", "FIXT.1.1", None, true),
];
assert_eq!(
expected.len(),
FixVersion::ALL.len(),
"every version must be covered"
);
for (version, name, begin_string, appl_ver_id, uses_fixt) in expected {
assert!(
FixVersion::ALL.contains(&version),
"{version:?} is missing from FixVersion::ALL"
);
assert_eq!(version.as_str(), name);
assert_eq!(version.begin_string(), begin_string);
assert_eq!(version.appl_ver_id(), appl_ver_id);
assert_eq!(version.uses_fixt(), uses_fixt);
}
}
#[test]
fn test_fix_version_roundtrips_through_its_canonical_name() {
for version in FixVersion::ALL {
assert_eq!(version.as_str().parse(), Ok(version));
assert_eq!(version.to_string(), version.as_str());
}
}
#[test]
fn test_fix_version_names_are_unique() {
for (index, version) in FixVersion::ALL.into_iter().enumerate() {
let duplicates = FixVersion::ALL
.into_iter()
.enumerate()
.filter(|(other_index, other)| {
*other_index != index && other.as_str() == version.as_str()
})
.count();
assert_eq!(duplicates, 0, "{version:?} shares its name with another");
}
}
#[test]
fn test_fix_version_from_str_unknown_is_typed_error() {
match "FIX.9.9".parse::<FixVersion>() {
Err(err) => assert_eq!(err.value(), "FIX.9.9"),
Ok(version) => unreachable!("FIX.9.9 is not a version, got {version:?}"),
}
}
#[test]
fn test_fix_version_from_str_is_case_sensitive() {
assert!("fix.4.4".parse::<FixVersion>().is_err());
assert!("FIX.5.0sp2".parse::<FixVersion>().is_err());
}
#[test]
fn test_fix_version_only_fifty_family_carries_appl_ver_id() {
for version in FixVersion::ALL {
if version.appl_ver_id().is_some() {
assert!(
version.uses_fixt(),
"{version:?} stamps an ApplVerID but is not a FIXT session"
);
}
}
}
}