#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::string::String;
use core::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Deprecated {
pub sunset: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub link: Option<String>,
}
impl Deprecated {
#[must_use]
pub fn new(sunset: impl Into<String>) -> Self {
Self {
sunset: sunset.into(),
link: None,
}
}
#[must_use]
pub fn with_link(mut self, link: impl Into<String>) -> Self {
self.link = Some(link.into());
self
}
#[must_use]
pub fn deprecation_header_value(&self) -> &'static str {
"true"
}
#[must_use]
pub fn sunset_header_value(&self) -> &str {
&self.sunset
}
#[must_use]
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn link_header_value(&self) -> Option<String> {
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::format;
self.link
.as_deref()
.map(|url| format!("<{url}>; rel=\"successor-version\""))
}
#[cfg(feature = "http")]
pub fn inject_headers(
&self,
headers: &mut http::HeaderMap,
) -> Result<(), http::header::InvalidHeaderValue> {
use http::header::{HeaderName, HeaderValue};
headers.insert(
HeaderName::from_static("deprecation"),
HeaderValue::from_static("true"),
);
headers.insert(
HeaderName::from_static("sunset"),
HeaderValue::from_str(&self.sunset)?,
);
if let Some(link_val) = self.link_header_value() {
headers.insert(http::header::LINK, HeaderValue::from_str(&link_val)?);
}
Ok(())
}
}
impl fmt::Display for Deprecated {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Deprecated(sunset={})", self.sunset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_sets_sunset() {
let d = Deprecated::new("2025-12-31");
assert_eq!(d.sunset, "2025-12-31");
assert!(d.link.is_none());
}
#[test]
fn with_link() {
let d = Deprecated::new("2025-12-31").with_link("https://example.com/v2");
assert_eq!(d.link.as_deref(), Some("https://example.com/v2"));
}
#[test]
fn header_values() {
let d = Deprecated::new("2025-12-31");
assert_eq!(d.deprecation_header_value(), "true");
assert_eq!(d.sunset_header_value(), "2025-12-31");
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn link_header_value_format() {
let d = Deprecated::new("2025-12-31").with_link("https://example.com/v2");
assert_eq!(
d.link_header_value().as_deref(),
Some("<https://example.com/v2>; rel=\"successor-version\"")
);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn link_header_value_none() {
let d = Deprecated::new("2025-12-31");
assert!(d.link_header_value().is_none());
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip() {
let d = Deprecated::new("2025-12-31").with_link("https://example.com/v2");
let json = serde_json::to_value(&d).unwrap();
assert_eq!(json["sunset"], "2025-12-31");
assert_eq!(json["link"], "https://example.com/v2");
let back: Deprecated = serde_json::from_value(json).unwrap();
assert_eq!(back, d);
}
#[cfg(feature = "serde")]
#[test]
fn serde_omits_null_link() {
let d = Deprecated::new("2025-12-31");
let json = serde_json::to_value(&d).unwrap();
assert!(json.get("link").is_none());
}
#[test]
fn display_format() {
let d = Deprecated::new("2025-12-31");
assert_eq!(d.to_string(), "Deprecated(sunset=2025-12-31)");
}
#[cfg(feature = "http")]
#[test]
fn inject_headers_sets_deprecation_and_sunset() {
let d = Deprecated::new("Sat, 31 Dec 2025 00:00:00 GMT");
let mut headers = http::HeaderMap::new();
d.inject_headers(&mut headers).unwrap();
assert_eq!(headers["deprecation"], "true");
assert_eq!(headers["sunset"], "Sat, 31 Dec 2025 00:00:00 GMT");
assert!(headers.get(http::header::LINK).is_none());
}
#[cfg(feature = "http")]
#[test]
fn inject_headers_with_link() {
let d = Deprecated::new("2025-12-31").with_link("https://example.com/v2");
let mut headers = http::HeaderMap::new();
d.inject_headers(&mut headers).unwrap();
assert_eq!(headers["deprecation"], "true");
let link = headers[http::header::LINK].to_str().unwrap();
assert!(link.contains("https://example.com/v2"));
assert!(link.contains("successor-version"));
}
}