use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParsedRight {
Call,
Put,
Both,
}
impl ParsedRight {
#[must_use]
pub fn as_mdds_str(self) -> &'static str {
match self {
Self::Call => "call",
Self::Put => "put",
Self::Both => "both",
}
}
#[must_use]
pub fn as_short_str(self) -> Option<&'static str> {
match self {
Self::Call => Some("C"),
Self::Put => Some("P"),
Self::Both => None,
}
}
#[must_use]
pub fn as_is_call(self) -> Option<bool> {
match self {
Self::Call => Some(true),
Self::Put => Some(false),
Self::Both => None,
}
}
#[must_use]
pub fn as_wire_byte(self) -> Option<i32> {
match self {
Self::Call => Some(67),
Self::Put => Some(80),
Self::Both => None,
}
}
#[must_use]
pub const fn from_wire_byte(byte: i32) -> Option<Self> {
match byte {
67 => Some(Self::Call),
80 => Some(Self::Put),
_ => None,
}
}
}
pub fn parse_right(input: &str) -> Result<ParsedRight, Error> {
if input == "*" {
return Ok(ParsedRight::Both);
}
match input.to_ascii_lowercase().as_str() {
"c" | "call" => Ok(ParsedRight::Call),
"p" | "put" => Ok(ParsedRight::Put),
"both" => Ok(ParsedRight::Both),
_ => Err(Error::Config(format!(
"invalid option right: '{input}' (expected one of: 'call', 'put', 'both', 'C', 'P', '*' -- case-insensitive)"
))),
}
}
pub fn parse_right_strict(input: &str) -> Result<ParsedRight, Error> {
let parsed = parse_right(input)?;
if matches!(parsed, ParsedRight::Both) {
return Err(Error::Config(format!(
"option right '{input}' resolves to 'both' but this endpoint requires a single side (call or put)"
)));
}
Ok(parsed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_call_all_cases() {
for form in ["call", "CALL", "Call", "CaLl", "C", "c"] {
assert_eq!(
parse_right(form).unwrap(),
ParsedRight::Call,
"failed on {form}"
);
}
}
#[test]
fn accepts_put_all_cases() {
for form in ["put", "PUT", "Put", "PuT", "P", "p"] {
assert_eq!(
parse_right(form).unwrap(),
ParsedRight::Put,
"failed on {form}"
);
}
}
#[test]
fn accepts_both_and_wildcard() {
assert_eq!(parse_right("both").unwrap(), ParsedRight::Both);
assert_eq!(parse_right("BOTH").unwrap(), ParsedRight::Both);
assert_eq!(parse_right("Both").unwrap(), ParsedRight::Both);
assert_eq!(parse_right("*").unwrap(), ParsedRight::Both);
}
#[test]
fn rejects_garbage() {
for bad in ["xyz", "", " ", "calls", "p ", "0", "67", "**"] {
let err = parse_right(bad).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("invalid option right"),
"expected a descriptive error for '{bad}', got: {msg}"
);
}
}
#[test]
fn mdds_projection_matches_upstream_vocabulary() {
assert_eq!(parse_right("C").unwrap().as_mdds_str(), "call");
assert_eq!(parse_right("p").unwrap().as_mdds_str(), "put");
assert_eq!(parse_right("*").unwrap().as_mdds_str(), "both");
}
#[test]
fn short_form_projection() {
assert_eq!(parse_right("call").unwrap().as_short_str(), Some("C"));
assert_eq!(parse_right("PUT").unwrap().as_short_str(), Some("P"));
assert_eq!(parse_right("both").unwrap().as_short_str(), None);
}
#[test]
fn fpss_bool_projection() {
assert_eq!(parse_right("C").unwrap().as_is_call(), Some(true));
assert_eq!(parse_right("P").unwrap().as_is_call(), Some(false));
assert_eq!(parse_right("both").unwrap().as_is_call(), None);
}
#[test]
fn fpss_wire_byte_projection() {
assert_eq!(parse_right("call").unwrap().as_wire_byte(), Some(67));
assert_eq!(parse_right("put").unwrap().as_wire_byte(), Some(80));
assert_eq!(parse_right("*").unwrap().as_wire_byte(), None);
}
#[test]
fn strict_rejects_both() {
assert_eq!(parse_right_strict("C").unwrap(), ParsedRight::Call);
assert_eq!(parse_right_strict("put").unwrap(), ParsedRight::Put);
let err = parse_right_strict("both").unwrap_err();
assert!(format!("{err}").contains("resolves to 'both'"));
let err = parse_right_strict("*").unwrap_err();
assert!(format!("{err}").contains("resolves to 'both'"));
let err = parse_right_strict("xyz").unwrap_err();
assert!(format!("{err}").contains("invalid option right"));
}
#[test]
fn from_wire_byte_decodes_call_and_put() {
assert_eq!(ParsedRight::from_wire_byte(67), Some(ParsedRight::Call));
assert_eq!(ParsedRight::from_wire_byte(80), Some(ParsedRight::Put));
}
#[test]
fn from_wire_byte_rejects_unknown_bytes() {
for byte in [
0_i32,
1,
65,
66,
68,
79,
81,
100,
256,
-1,
i32::MIN,
i32::MAX,
] {
assert!(
ParsedRight::from_wire_byte(byte).is_none(),
"byte {byte} should not decode"
);
}
}
#[test]
fn wire_byte_round_trips_through_inverse() {
for variant in [ParsedRight::Call, ParsedRight::Put, ParsedRight::Both] {
match variant.as_wire_byte() {
Some(byte) => {
assert_eq!(
ParsedRight::from_wire_byte(byte),
Some(variant),
"round-trip failed for {variant:?}"
);
}
None => {
assert_eq!(variant, ParsedRight::Both);
}
}
}
}
}