use paft_core::__utils::{Canonical, MAX_CANONICAL_TOKEN_LEN, StringCode};
use paft_core::PaftError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Side {
Buy,
Sell,
}
paft_core::string_enum_closed_with_code!(
Side,
"Side",
{ "BUY" => Side::Buy, "SELL" => Side::Sell },
{ "B" => Side::Buy, "S" => Side::Sell }
);
paft_core::impl_display_via_code!(Side);
#[test]
fn closed_code_returns_canonical_token() {
assert_eq!(Side::Buy.code(), "BUY");
assert_eq!(Side::Sell.code(), "SELL");
}
#[test]
fn closed_display_matches_code() {
assert_eq!(Side::Buy.to_string(), "BUY");
assert_eq!(format!("{}", Side::Sell), "SELL");
}
#[test]
fn closed_from_str_round_trip() {
let parsed: Side = "buy".parse().unwrap();
assert_eq!(parsed, Side::Buy);
assert_eq!(parsed.to_string().parse::<Side>().unwrap(), parsed);
assert_eq!(" Sell ".parse::<Side>().unwrap(), Side::Sell);
}
#[test]
fn closed_aliases_resolve_to_canonical_variants() {
assert_eq!("b".parse::<Side>().unwrap(), Side::Buy);
assert_eq!("S".parse::<Side>().unwrap(), Side::Sell);
}
#[test]
fn closed_try_from_string_and_into_string() {
let parsed = Side::try_from(String::from("sell")).unwrap();
assert_eq!(parsed, Side::Sell);
let s: String = Side::Buy.into();
assert_eq!(s, "BUY");
}
#[test]
fn closed_unknown_value_is_rejected() {
let err = "hold".parse::<Side>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Side" && value == "hold"
));
}
#[test]
fn closed_rejects_malformed_input_that_canonicalizes_to_known_value() {
for input in ["$BUY", "---SELL", "BUY!"] {
let err = input.parse::<Side>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Side" && value == input
));
}
assert!(serde_json::from_str::<Side>("\"SELL!\"").is_err());
}
#[test]
fn closed_serde_round_trip_json() {
let json = serde_json::to_string(&Side::Sell).unwrap();
assert_eq!(json, "\"SELL\"");
let back: Side = serde_json::from_str(&json).unwrap();
assert_eq!(back, Side::Sell);
let from_lower: Side = serde_json::from_str("\"buy\"").unwrap();
assert_eq!(from_lower, Side::Buy);
}
#[test]
fn closed_code_is_const_fn() {
const BUY: &str = Side::Buy.code();
const SELL: &str = Side::Sell.code();
assert_eq!(BUY, "BUY");
assert_eq!(SELL, "SELL");
}
#[test]
fn closed_string_code_trait_impl() {
fn code_of<T: StringCode>(t: &T) -> &str {
t.code()
}
assert_eq!(code_of(&Side::Buy), "BUY");
assert!(StringCode::is_canonical(&Side::Buy));
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Venue {
Nasdaq,
Nyse,
Other(Canonical),
}
paft_core::string_enum_with_code!(
Venue,
Other,
"Venue",
{ "NASDAQ" => Venue::Nasdaq, "NYSE" => Venue::Nyse },
{ "BIG_BOARD" => Venue::Nyse }
);
paft_core::impl_display_via_code!(Venue);
#[test]
fn open_canonical_variants_round_trip() {
assert_eq!(Venue::Nasdaq.code(), "NASDAQ");
assert_eq!(Venue::Nyse.to_string(), "NYSE");
let parsed: Venue = "nyse".parse().unwrap();
assert_eq!(parsed, Venue::Nyse);
assert!(parsed.is_canonical());
}
#[test]
fn open_alias_resolves_to_canonical_variant() {
let parsed: Venue = "Big Board".parse().unwrap();
assert_eq!(parsed, Venue::Nyse);
assert!(parsed.is_canonical());
}
#[test]
fn open_unknown_input_becomes_other_with_canonical_token() {
let parsed: Venue = "tsxv".parse().unwrap();
let Venue::Other(canon) = parsed.clone() else {
panic!("expected Other for unknown input, got {parsed:?}");
};
assert_eq!(canon.as_str(), "TSXV");
assert_eq!(parsed.to_string(), "TSXV");
assert!(!parsed.is_canonical());
}
#[test]
fn open_two_equivalent_unknown_inputs_normalise_to_same_other() {
let a: Venue = "foo bar".parse().unwrap();
let b: Venue = "foo_bar".parse().unwrap();
let c: Venue = " FOO-BAR ".parse().unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(a.to_string(), "FOO_BAR");
}
#[test]
fn open_serde_round_trip_json_canonical_and_other() {
let json = serde_json::to_string(&Venue::Nasdaq).unwrap();
assert_eq!(json, "\"NASDAQ\"");
let back: Venue = serde_json::from_str(&json).unwrap();
assert_eq!(back, Venue::Nasdaq);
let other: Venue = "moex".parse().unwrap();
let json = serde_json::to_string(&other).unwrap();
assert_eq!(json, "\"MOEX\"");
let back: Venue = serde_json::from_str(&json).unwrap();
assert_eq!(back, other);
let from_provider: Venue = serde_json::from_str("\"Some Venue\"").unwrap();
assert_eq!(from_provider.to_string(), "SOME_VENUE");
}
#[test]
fn open_empty_input_is_rejected() {
let err = "".parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value.is_empty()
));
let err = " ".parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value == " "
));
}
#[test]
fn open_rejects_malformed_input_that_canonicalizes_to_known_value() {
for input in ["$NASDAQ", "---NYSE", "BIG_BOARD!"] {
let err = input.parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value == input
));
}
assert!(serde_json::from_str::<Venue>("\"NYSE!\"").is_err());
}
#[test]
fn open_input_canonicalising_to_empty_is_rejected_not_other() {
let err = "!!!".parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value == "!!!"
));
let err = "---".parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value == "---"
));
}
#[test]
fn open_unknown_input_over_token_cap_is_rejected_not_other() {
let input = "x".repeat(MAX_CANONICAL_TOKEN_LEN + 1);
let err = input.parse::<Venue>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Venue" && value == input
));
assert!(serde_json::from_str::<Venue>(&format!("\"{input}\"")).is_err());
}
#[test]
fn open_try_from_string_and_into_string() {
let v = Venue::try_from(String::from("nasdaq")).unwrap();
assert_eq!(v, Venue::Nasdaq);
let s: String = Venue::Nyse.into();
assert_eq!(s, "NYSE");
let other = Venue::try_from(String::from("custom venue")).unwrap();
let s: String = other.into();
assert_eq!(s, "CUSTOM_VENUE");
}
#[test]
fn open_string_code_trait_reports_canonical_correctly() {
fn is_canon<T: StringCode>(t: &T) -> bool {
t.is_canonical()
}
assert!(is_canon(&Venue::Nasdaq));
let other: Venue = "weird".parse().unwrap();
assert!(!is_canon(&other));
}
paft_core::other_string_code_type!(
struct OtherListingVenue for ListingVenue;
type Error = PaftError;
parse(input) => input.parse::<ListingVenue>();
invalid(input) => PaftError::InvalidEnumValue {
enum_name: "ListingVenue",
value: input.to_string(),
};
);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ListingVenue {
Primary,
Other(OtherListingVenue),
}
paft_core::string_enum_with_code!(
ListingVenue, Other(OtherListingVenue), "ListingVenue",
{ "PRIMARY" => ListingVenue::Primary },
{ "MAIN" => ListingVenue::Primary }
);
paft_core::impl_display_via_code!(ListingVenue);
#[test]
fn other_string_code_wrapper_serde_uses_checked_constructor() {
let other = OtherListingVenue::new("dark pool").unwrap();
assert_eq!(serde_json::to_string(&other).unwrap(), "\"DARK_POOL\"");
let back: OtherListingVenue = serde_json::from_str("\"dark pool\"").unwrap();
assert_eq!(back, other);
assert!(serde_json::from_str::<OtherListingVenue>("\"PRIMARY\"").is_err());
assert!(serde_json::from_str::<OtherListingVenue>("\"main\"").is_err());
}
#[test]
fn other_string_code_wrapper_rejects_over_token_cap() {
let input = "x".repeat(MAX_CANONICAL_TOKEN_LEN + 1);
assert!(OtherListingVenue::new(&input).is_err());
assert!(serde_json::from_str::<OtherListingVenue>(&format!("\"{input}\"")).is_err());
}
mod hygiene_no_paft_utils_in_scope {
use paft_core::PaftError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Color {
Red,
Other(paft_core::__utils::Canonical),
}
paft_core::string_enum_with_code!(
Color,
Other,
"Color",
{ "RED" => Color::Red }
);
paft_core::impl_display_via_code!(Color);
#[test]
fn macro_resolves_paft_utils_via_crate_re_export() {
assert_eq!(Color::Red.code(), "RED");
assert_eq!(Color::Red.to_string(), "RED");
let parsed: Color = " red ".parse().unwrap();
assert_eq!(parsed, Color::Red);
let unknown: Color = "Forest Green".parse().unwrap();
let Color::Other(c) = unknown.clone() else {
panic!("expected Other for unknown input");
};
assert_eq!(c.as_str(), "FOREST_GREEN");
assert_eq!(unknown.to_string(), "FOREST_GREEN");
let err = "...".parse::<Color>().unwrap_err();
assert!(matches!(
err,
PaftError::InvalidEnumValue { enum_name, value }
if enum_name == "Color" && value == "..."
));
}
}