1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7macro_rules! extension_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, PhpExtensionError> {
14 let trimmed = input.trim();
15 if trimmed.is_empty() {
16 Err(PhpExtensionError::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 = PhpExtensionError;
35
36 fn from_str(input: &str) -> Result<Self, Self::Err> {
37 Self::new(input)
38 }
39 }
40 };
41}
42
43extension_text_newtype!(PhpExtensionName);
44extension_text_newtype!(PhpVersionConstraint);
45
46#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub enum PhpExtensionKind {
49 Core,
50 Bundled,
51 Pecl,
52 Zend,
53 Userland,
54 Unknown,
55}
56
57impl PhpExtensionKind {
58 pub const fn as_str(self) -> &'static str {
59 match self {
60 Self::Core => "core",
61 Self::Bundled => "bundled",
62 Self::Pecl => "pecl",
63 Self::Zend => "zend",
64 Self::Userland => "userland",
65 Self::Unknown => "unknown",
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub enum PhpExtensionRequirementKind {
73 Required,
74 Optional,
75 Conflict,
76}
77
78impl PhpExtensionRequirementKind {
79 pub const fn as_str(self) -> &'static str {
80 match self {
81 Self::Required => "required",
82 Self::Optional => "optional",
83 Self::Conflict => "conflict",
84 }
85 }
86}
87
88#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct PhpExtensionRequirement {
91 name: PhpExtensionName,
92 requirement_kind: PhpExtensionRequirementKind,
93 extension_kind: PhpExtensionKind,
94 version: Option<PhpVersionConstraint>,
95}
96
97impl PhpExtensionRequirement {
98 pub const fn required(name: PhpExtensionName) -> Self {
99 Self {
100 name,
101 requirement_kind: PhpExtensionRequirementKind::Required,
102 extension_kind: PhpExtensionKind::Unknown,
103 version: None,
104 }
105 }
106
107 pub const fn optional(name: PhpExtensionName) -> Self {
108 Self {
109 name,
110 requirement_kind: PhpExtensionRequirementKind::Optional,
111 extension_kind: PhpExtensionKind::Unknown,
112 version: None,
113 }
114 }
115
116 pub const fn with_kind(mut self, extension_kind: PhpExtensionKind) -> Self {
117 self.extension_kind = extension_kind;
118 self
119 }
120
121 pub fn with_version(mut self, version: PhpVersionConstraint) -> Self {
122 self.version = Some(version);
123 self
124 }
125
126 pub const fn name(&self) -> &PhpExtensionName {
127 &self.name
128 }
129
130 pub const fn requirement_kind(&self) -> PhpExtensionRequirementKind {
131 self.requirement_kind
132 }
133
134 pub const fn extension_kind(&self) -> PhpExtensionKind {
135 self.extension_kind
136 }
137
138 pub const fn version(&self) -> Option<&PhpVersionConstraint> {
139 self.version.as_ref()
140 }
141
142 pub const fn is_required(&self) -> bool {
143 matches!(self.requirement_kind, PhpExtensionRequirementKind::Required)
144 }
145}
146
147#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub enum PhpExtensionError {
150 Empty,
151}
152
153impl fmt::Display for PhpExtensionError {
154 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155 formatter.write_str("PHP extension metadata cannot be empty")
156 }
157}
158
159impl Error for PhpExtensionError {}
160
161#[cfg(test)]
162mod tests {
163 use super::{
164 PhpExtensionError, PhpExtensionKind, PhpExtensionName, PhpExtensionRequirement,
165 PhpVersionConstraint,
166 };
167
168 #[test]
169 fn builds_extension_requirement() -> Result<(), PhpExtensionError> {
170 let requirement = PhpExtensionRequirement::required(PhpExtensionName::new("mbstring")?)
171 .with_kind(PhpExtensionKind::Bundled)
172 .with_version(PhpVersionConstraint::new("*")?);
173
174 assert_eq!(requirement.name().as_str(), "mbstring");
175 assert!(requirement.is_required());
176 assert_eq!(requirement.extension_kind(), PhpExtensionKind::Bundled);
177 Ok(())
178 }
179}