use crate::model::{
reference::RefOr,
response::{Response, Responses, status::StatusPattern},
};
const WILDCARDS: &[StatusPattern] = &[
StatusPattern::Informational,
StatusPattern::Success,
StatusPattern::Redirection,
StatusPattern::ClientError,
StatusPattern::ServerError,
];
fn spelling(pattern: StatusPattern) -> Option<&'static str> {
match pattern {
StatusPattern::Informational => Some("1XX"),
StatusPattern::Success => Some("2XX"),
StatusPattern::Redirection => Some("3XX"),
StatusPattern::ClientError => Some("4XX"),
StatusPattern::ServerError => Some("5XX"),
StatusPattern::Code(_) => None,
}
}
#[test]
fn each_wildcard_parses_and_renders_as_the_specification_spells_it() {
for &pattern in WILDCARDS {
let spelled = spelling(pattern).expect("a wildcard has a spelling");
assert_eq!(
spelled.parse::<StatusPattern>().expect("a legal key"),
pattern,
"parsing {spelled}"
);
assert_eq!(pattern.to_string(), spelled, "rendering {pattern:?}");
}
}
#[test]
fn an_exact_code_parses_and_renders_as_itself() {
for code in 100..=599u16 {
let text = code.to_string();
assert_eq!(
text.parse::<StatusPattern>().expect("a status in range"),
StatusPattern::Code(code)
);
assert_eq!(StatusPattern::Code(code).to_string(), text);
}
for code in [0u16, 99, 600, 999] {
assert!(
code.to_string().parse::<StatusPattern>().is_err(),
"{code} is not a status a response can carry"
);
}
}
#[test]
fn only_the_five_documented_wildcards_are_accepted() {
assert!("6XX".parse::<StatusPattern>().is_err());
assert!("2xx".parse::<StatusPattern>().is_err());
assert!("20X".parse::<StatusPattern>().is_err());
assert!("XX".parse::<StatusPattern>().is_err());
}
fn covered_class(pattern: StatusPattern) -> Option<std::ops::RangeInclusive<u16>> {
match pattern {
StatusPattern::Informational => Some(100..=199),
StatusPattern::Success => Some(200..=299),
StatusPattern::Redirection => Some(300..=399),
StatusPattern::ClientError => Some(400..=499),
StatusPattern::ServerError => Some(500..=599),
StatusPattern::Code(_) => None,
}
}
#[test]
fn wildcards_cover_their_class_and_nothing_else() {
for &pattern in WILDCARDS {
let class = covered_class(pattern).expect("a wildcard covers a class");
for code in 100..=599u16 {
assert_eq!(
pattern.matches(code),
class.contains(&code),
"{pattern:?} against {code}"
);
}
}
}
#[test]
fn an_exact_code_matches_only_itself() {
for code in [200u16, 404, 500] {
let pattern = StatusPattern::Code(code);
assert!(covered_class(pattern).is_none());
for other in 100..=599u16 {
assert_eq!(
pattern.matches(other),
other == code,
"{code} against {other}"
);
}
}
}
#[test]
fn responses_serialize_default_alongside_status_keys() {
let responses = Responses::new()
.with(200, Response::new("ok"))
.with_default(Response::new("unexpected error"));
let json = serde_json::to_string(&responses).expect("ok");
assert!(json.contains(r#""default""#));
assert!(json.contains(r#""200""#));
}
#[test]
fn responses_round_trip() {
let responses = Responses::new().with(201, Response::new("created"));
let json = serde_json::to_string(&responses).expect("ok");
let parsed: Responses = serde_json::from_str(&json).expect("ok");
assert_eq!(parsed, responses);
}
#[test]
fn a_malformed_status_key_is_a_parse_error() {
let result = serde_json::from_str::<Responses>(r#"{"okay":{"description":"x"}}"#);
assert!(result.is_err());
}
#[test]
fn extensions_survive_a_round_trip() {
let parsed: Responses =
serde_json::from_str(r#"{"200":{"description":"ok"},"x-note":"hi"}"#).expect("ok");
assert_eq!(
parsed.extensions.get("x-note").and_then(|v| v.as_str()),
Some("hi")
);
assert_eq!(parsed.responses.len(), 1);
}
#[test]
fn merging_keeps_the_existing_entry_on_conflict() {
let mut base = Responses::new().with(200, Response::new("mine"));
let other = Responses::new()
.with(200, Response::new("theirs"))
.with(429, Response::new("too many requests"));
base.merge_from(&other);
assert_eq!(base.responses.len(), 2);
let two_hundred = base.get(200).and_then(RefOr::as_item).expect("present");
assert_eq!(two_hundred.description.as_deref(), Some("mine"));
}
#[cfg(feature = "openapi32")]
#[test]
fn a_response_stating_only_a_summary_parses() {
let parsed: Response =
serde_json::from_str(r#"{"summary":"The order"}"#).expect("a legal 3.2 Response Object");
assert_eq!(parsed.summary.as_deref(), Some("The order"));
assert_eq!(
serde_json::to_string(&parsed).expect("serializable"),
r#"{"summary":"The order"}"#,
"and writes back what it read, without inventing a description"
);
}