1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct PsrNumber(u16);
10
11impl PsrNumber {
12 pub const fn new(value: u16) -> Result<Self, PsrError> {
13 if value == 0 {
14 Err(PsrError::InvalidNumber)
15 } else {
16 Ok(Self(value))
17 }
18 }
19
20 pub const fn get(self) -> u16 {
21 self.0
22 }
23}
24
25impl fmt::Display for PsrNumber {
26 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(formatter, "PSR-{}", self.0)
28 }
29}
30
31#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub struct PsrTitle(String);
34
35impl PsrTitle {
36 pub fn new(input: &str) -> Result<Self, PsrError> {
37 let trimmed = input.trim();
38 if trimmed.is_empty() {
39 Err(PsrError::Empty)
40 } else {
41 Ok(Self(trimmed.to_string()))
42 }
43 }
44
45 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
52pub enum PsrStatus {
53 Draft,
54 Accepted,
55 Deprecated,
56 Abandoned,
57 Unknown,
58}
59
60impl PsrStatus {
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::Draft => "draft",
64 Self::Accepted => "accepted",
65 Self::Deprecated => "deprecated",
66 Self::Abandoned => "abandoned",
67 Self::Unknown => "unknown",
68 }
69 }
70}
71
72#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
74pub enum PsrCategory {
75 Autoloading,
76 CodingStyle,
77 Http,
78 Logging,
79 Cache,
80 Container,
81 Events,
82 Security,
83 Other,
84}
85
86impl PsrCategory {
87 pub const fn as_str(self) -> &'static str {
88 match self {
89 Self::Autoloading => "autoloading",
90 Self::CodingStyle => "coding-style",
91 Self::Http => "http",
92 Self::Logging => "logging",
93 Self::Cache => "cache",
94 Self::Container => "container",
95 Self::Events => "events",
96 Self::Security => "security",
97 Self::Other => "other",
98 }
99 }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct PsrMetadata {
105 number: PsrNumber,
106 title: PsrTitle,
107 status: PsrStatus,
108 category: PsrCategory,
109}
110
111impl PsrMetadata {
112 pub const fn new(
113 number: PsrNumber,
114 title: PsrTitle,
115 status: PsrStatus,
116 category: PsrCategory,
117 ) -> Self {
118 Self {
119 number,
120 title,
121 status,
122 category,
123 }
124 }
125
126 pub const fn number(&self) -> PsrNumber {
127 self.number
128 }
129
130 pub const fn title(&self) -> &PsrTitle {
131 &self.title
132 }
133
134 pub const fn status(&self) -> PsrStatus {
135 self.status
136 }
137
138 pub const fn category(&self) -> PsrCategory {
139 self.category
140 }
141
142 pub fn identifier(&self) -> String {
143 self.number.to_string()
144 }
145}
146
147#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub enum PsrError {
150 Empty,
151 InvalidNumber,
152}
153
154impl fmt::Display for PsrError {
155 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
156 match self {
157 Self::Empty => formatter.write_str("PSR metadata cannot be empty"),
158 Self::InvalidNumber => formatter.write_str("PSR number must be non-zero"),
159 }
160 }
161}
162
163impl Error for PsrError {}
164
165#[cfg(test)]
166mod tests {
167 use super::{PsrCategory, PsrError, PsrMetadata, PsrNumber, PsrStatus, PsrTitle};
168
169 #[test]
170 fn builds_psr_metadata() -> Result<(), PsrError> {
171 let metadata = PsrMetadata::new(
172 PsrNumber::new(4)?,
173 PsrTitle::new("Autoloading Standard")?,
174 PsrStatus::Accepted,
175 PsrCategory::Autoloading,
176 );
177
178 assert_eq!(metadata.identifier(), "PSR-4");
179 assert_eq!(metadata.category().as_str(), "autoloading");
180 Ok(())
181 }
182}