Skip to main content

use_php_attribute/
lib.rs

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/// PHP attribute target metadata.
48#[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/// PHP attribute repeatability metadata.
82#[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/// Simple PHP attribute argument metadata.
95#[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/// PHP attribute reference metadata.
123#[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/// Error returned when PHP attribute metadata is invalid.
174#[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}