1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7macro_rules! text_newtype {
8 ($name:ident) => {
9 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10 pub struct $name(String);
11
12 impl $name {
13 pub fn new(input: &str) -> Result<Self, PhpAttributeError> {
14 let trimmed = input.trim();
15 if trimmed.is_empty() {
16 Err(PhpAttributeError::Empty)
17 } else {
18 Ok(Self(trimmed.to_string()))
19 }
20 }
21
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25 }
26
27 impl fmt::Display for $name {
28 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29 formatter.write_str(self.as_str())
30 }
31 }
32
33 impl FromStr for $name {
34 type Err = PhpAttributeError;
35
36 fn from_str(input: &str) -> Result<Self, Self::Err> {
37 Self::new(input)
38 }
39 }
40 };
41}
42
43text_newtype!(PhpAttributeName);
44text_newtype!(PhpAttributeArgumentName);
45text_newtype!(PhpAttributeArgumentValue);
46
47#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49pub enum PhpAttributeTarget {
50 Class,
51 Function,
52 Method,
53 Property,
54 ClassConstant,
55 Parameter,
56 EnumCase,
57 All,
58}
59
60impl PhpAttributeTarget {
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::Class => "class",
64 Self::Function => "function",
65 Self::Method => "method",
66 Self::Property => "property",
67 Self::ClassConstant => "class-constant",
68 Self::Parameter => "parameter",
69 Self::EnumCase => "enum-case",
70 Self::All => "all",
71 }
72 }
73}
74
75impl fmt::Display for PhpAttributeTarget {
76 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77 formatter.write_str(self.as_str())
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub enum PhpAttributeRepeatability {
84 Single,
85 Repeatable,
86}
87
88impl PhpAttributeRepeatability {
89 pub const fn is_repeatable(self) -> bool {
90 matches!(self, Self::Repeatable)
91 }
92}
93
94#[derive(Clone, Debug, Eq, PartialEq)]
96pub struct PhpAttributeArgument {
97 name: Option<PhpAttributeArgumentName>,
98 value: PhpAttributeArgumentValue,
99}
100
101impl PhpAttributeArgument {
102 pub const fn positional(value: PhpAttributeArgumentValue) -> Self {
103 Self { name: None, value }
104 }
105
106 pub const fn named(name: PhpAttributeArgumentName, value: PhpAttributeArgumentValue) -> Self {
107 Self {
108 name: Some(name),
109 value,
110 }
111 }
112
113 pub const fn name(&self) -> Option<&PhpAttributeArgumentName> {
114 self.name.as_ref()
115 }
116
117 pub const fn value(&self) -> &PhpAttributeArgumentValue {
118 &self.value
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct PhpAttributeReference {
125 name: PhpAttributeName,
126 targets: Vec<PhpAttributeTarget>,
127 arguments: Vec<PhpAttributeArgument>,
128 repeatability: PhpAttributeRepeatability,
129}
130
131impl PhpAttributeReference {
132 pub fn new(name: PhpAttributeName) -> Self {
133 Self {
134 name,
135 targets: Vec::new(),
136 arguments: Vec::new(),
137 repeatability: PhpAttributeRepeatability::Single,
138 }
139 }
140
141 pub fn with_target(mut self, target: PhpAttributeTarget) -> Self {
142 self.targets.push(target);
143 self
144 }
145
146 pub fn with_argument(mut self, argument: PhpAttributeArgument) -> Self {
147 self.arguments.push(argument);
148 self
149 }
150
151 pub const fn with_repeatability(mut self, repeatability: PhpAttributeRepeatability) -> Self {
152 self.repeatability = repeatability;
153 self
154 }
155
156 pub const fn name(&self) -> &PhpAttributeName {
157 &self.name
158 }
159
160 pub fn targets(&self) -> &[PhpAttributeTarget] {
161 &self.targets
162 }
163
164 pub fn arguments(&self) -> &[PhpAttributeArgument] {
165 &self.arguments
166 }
167
168 pub const fn repeatability(&self) -> PhpAttributeRepeatability {
169 self.repeatability
170 }
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
175pub enum PhpAttributeError {
176 Empty,
177}
178
179impl fmt::Display for PhpAttributeError {
180 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
181 formatter.write_str("PHP attribute metadata cannot be empty")
182 }
183}
184
185impl Error for PhpAttributeError {}
186
187#[cfg(test)]
188mod tests {
189 use super::{
190 PhpAttributeArgument, PhpAttributeArgumentValue, PhpAttributeError, PhpAttributeName,
191 PhpAttributeReference, PhpAttributeRepeatability, PhpAttributeTarget,
192 };
193
194 #[test]
195 fn builds_attribute_reference() -> Result<(), PhpAttributeError> {
196 let reference = PhpAttributeReference::new(PhpAttributeName::new("App\\Route")?)
197 .with_target(PhpAttributeTarget::Method)
198 .with_argument(PhpAttributeArgument::positional(
199 PhpAttributeArgumentValue::new("/home")?,
200 ))
201 .with_repeatability(PhpAttributeRepeatability::Repeatable);
202
203 assert_eq!(reference.name().as_str(), "App\\Route");
204 assert_eq!(reference.targets(), &[PhpAttributeTarget::Method]);
205 assert!(reference.repeatability().is_repeatable());
206 Ok(())
207 }
208}