Skip to main content

uv_pypi_types/
project_status.rs

1//! PEP 792 project status marker types.
2//!
3//! See the living Project Status Markers specification:
4//! <https://packaging.python.org/en/latest/specifications/project-status-markers/#project-status-markers>
5
6use std::borrow::Cow;
7
8use serde::Deserialize;
9use tracing::info;
10use uv_small_str::SmallString;
11
12/// The status marker for a project.
13#[derive(
14    Clone, Copy, Debug, Default, Eq, PartialEq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize,
15)]
16#[rkyv(derive(Debug))]
17pub enum Status {
18    #[default]
19    Active,
20    Archived,
21    Quarantined,
22    Deprecated,
23}
24
25impl Status {
26    pub fn new(status: &str) -> Option<Self> {
27        match status {
28            "active" => Some(Self::Active),
29            "archived" => Some(Self::Archived),
30            "quarantined" => Some(Self::Quarantined),
31            "deprecated" => Some(Self::Deprecated),
32            _ => {
33                info!("Unknown project status: '{status}'");
34                None
35            }
36        }
37    }
38}
39
40impl<'de> Deserialize<'de> for Status {
41    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42    where
43        D: serde::Deserializer<'de>,
44    {
45        let s = <Cow<'_, str>>::deserialize(deserializer)?;
46        // If we don't recognize the status, default to Active.
47        Ok(Self::new(&s).unwrap_or_default())
48    }
49}
50
51/// The project status information.
52///
53/// This includes a status marker and an optional reason for the status.
54#[derive(Clone, Debug, Default, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
55#[rkyv(derive(Debug))]
56pub struct ProjectStatus {
57    #[serde(default)]
58    pub status: Status,
59    #[serde(default)]
60    pub reason: Option<SmallString>,
61}
62
63#[cfg(test)]
64mod tests {
65    use uv_small_str::SmallString;
66
67    use crate::{ProjectStatus, Status};
68
69    #[test]
70    fn test_status() {
71        assert_eq!(Status::new("active"), Some(Status::Active));
72        assert_eq!(Status::new("archived"), Some(Status::Archived));
73        assert_eq!(Status::new("quarantined"), Some(Status::Quarantined));
74        assert_eq!(Status::new("deprecated"), Some(Status::Deprecated));
75        assert_eq!(Status::new("unknown"), None);
76        assert_eq!(Status::new("ACTIVE"), None);
77        assert_eq!(Status::new("acTiVe"), None);
78    }
79
80    /// An empty project status should default to Active with no reason.
81    #[test]
82    fn test_deserialize_empty() {
83        let json = "{}";
84
85        let project_status: ProjectStatus = serde_json::from_str(json).unwrap();
86        assert_eq!(project_status.status, Status::Active);
87        assert_eq!(project_status.reason, None);
88    }
89
90    /// A project status with explicitly null reason should default to Active with no reason.
91    #[test]
92    fn test_deserialize_null() {
93        let json = r#"
94        {
95            "reason": null
96        }
97        "#;
98
99        let project_status: ProjectStatus = serde_json::from_str(json).unwrap();
100        assert_eq!(project_status.status, Status::Active);
101        assert_eq!(project_status.reason, None);
102    }
103
104    /// A project status with known status and reason.
105    #[test]
106    fn test_deserialize() {
107        let json = r#"
108        {
109            "status": "archived",
110            "reason": "This project is no longer maintained."
111        }
112        "#;
113
114        let project_status: ProjectStatus = serde_json::from_str(json).unwrap();
115        assert_eq!(project_status.status, Status::Archived);
116        assert_eq!(
117            project_status.reason,
118            Some(SmallString::from("This project is no longer maintained."))
119        );
120    }
121
122    /// A project status with an unknown status should default to Active.
123    #[test]
124    fn test_deserialize_unknown_status() {
125        let json = r#"
126        {
127            "status": "unknown",
128            "reason": "This project has an unrecognized status."
129        }
130        "#;
131
132        let project_status: ProjectStatus = serde_json::from_str(json).unwrap();
133        assert_eq!(project_status.status, Status::Active);
134        assert_eq!(
135            project_status.reason,
136            Some(SmallString::from(
137                "This project has an unrecognized status."
138            ))
139        );
140    }
141}