1use proc_macro2::{Ident, TokenStream};
4
5#[path = "role_ops.rs"]
6mod ops;
7pub use ops::RoleBoundsChecker;
8
9pub const MAX_ROLE_COUNT: u32 = 10_000;
11
12pub const MAX_ROLE_INDEX: u32 = MAX_ROLE_COUNT - 1;
14
15pub const MAX_RANGE_COUNT: u32 = 1_000;
17
18#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
20pub enum RoleValidationError {
21 #[error("Role count {count} exceeds maximum allowed {max}")]
22 CountOverflow { count: u32, max: u32 },
23
24 #[error("Role index {index} exceeds maximum allowed {max}")]
25 IndexOverflow { index: u32, max: u32 },
26
27 #[error("Range size {size} exceeds maximum allowed {max}")]
28 RangeSizeOverflow { size: u32, max: u32 },
29
30 #[error("Invalid range: start {start} >= end {end}")]
31 InvalidRange { start: u32, end: u32 },
32
33 #[error("Runtime role count must be bounded for safety")]
34 UnboundedRuntime,
35
36 #[error("Symbolic parameter '{param}' cannot be validated without runtime context")]
37 SymbolicValidation { param: String },
38}
39
40pub type RoleValidationResult<T> = Result<T, RoleValidationError>;
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub enum RoleParam {
46 Static(u32),
48 Symbolic(String),
50 Runtime,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub enum RoleIndex {
57 Concrete(u32),
59 Symbolic(String),
61 Wildcard,
63 Range(RoleRange),
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub struct RoleRange {
70 pub start: RangeExpr,
72 pub end: RangeExpr,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub enum RangeExpr {
79 Concrete(u32),
81 Symbolic(String),
83}
84
85#[derive(Debug, Clone)]
107pub struct Role {
108 name: Ident,
110 param: Option<RoleParam>,
112 index: Option<RoleIndex>,
114 array_size: Option<TokenStream>,
116}
117
118impl PartialEq for Role {
120 fn eq(&self, other: &Self) -> bool {
121 self.name == other.name
122 && self.param == other.param
123 && self.index == other.index
124 && self
125 .array_size
126 .as_ref()
127 .map(std::string::ToString::to_string)
128 == other
129 .array_size
130 .as_ref()
131 .map(std::string::ToString::to_string)
132 }
133}
134
135impl Eq for Role {}
136
137impl std::hash::Hash for Role {
138 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
139 self.name.hash(state);
140 self.param.hash(state);
141 self.index.hash(state);
142 if let Some(size) = &self.array_size {
143 size.to_string().hash(state);
144 }
145 }
146}
147
148impl Role {
149 fn new_unchecked(
150 name: Ident,
151 param: Option<RoleParam>,
152 index: Option<RoleIndex>,
153 array_size: Option<TokenStream>,
154 ) -> Self {
155 Role {
156 name,
157 param,
158 index,
159 array_size,
160 }
161 }
162
163 pub fn new(name: Ident) -> RoleValidationResult<Self> {
165 let role = Self::new_unchecked(name, None, None, None);
166 role.validate()?;
167 Ok(role)
168 }
169
170 pub fn with_param(name: Ident, param: RoleParam) -> RoleValidationResult<Self> {
172 let role = Self::new_unchecked(name, Some(param), None, None);
173 role.validate()?;
174 Ok(role)
175 }
176
177 pub fn with_index(name: Ident, index: RoleIndex) -> RoleValidationResult<Self> {
179 let role = Self::new_unchecked(name, None, Some(index), None);
180 role.validate()?;
181 Ok(role)
182 }
183
184 pub fn with_param_and_index(
186 name: Ident,
187 param: RoleParam,
188 index: RoleIndex,
189 ) -> RoleValidationResult<Self> {
190 let role = Self::new_unchecked(name, Some(param), Some(index), None);
191 role.validate()?;
192 Ok(role)
193 }
194
195 pub fn indexed(name: Ident, index: usize) -> RoleValidationResult<Self> {
197 let role_index = RoleIndex::safe_concrete(u32::try_from(index).map_err(|_| {
198 RoleValidationError::IndexOverflow {
199 index: u32::MAX,
200 max: MAX_ROLE_INDEX,
201 }
202 })?)?;
203 let role = Self::new_unchecked(name, None, Some(role_index), None);
204 role.validate()?;
205 Ok(role)
206 }
207
208 pub fn parameterized(name: Ident, param: TokenStream) -> RoleValidationResult<Self> {
210 let role = Self::new_unchecked(
211 name,
212 Some(RoleParam::Symbolic(param.to_string())),
213 None,
214 Some(param),
215 );
216 role.validate()?;
217 Ok(role)
218 }
219
220 pub fn array(name: Ident, size: usize) -> RoleValidationResult<Self> {
222 let size_token: TokenStream = size.to_string().parse().unwrap();
223 let role = Self::new_unchecked(
224 name,
225 Some(RoleParam::safe_static(u32::try_from(size).map_err(
226 |_| RoleValidationError::CountOverflow {
227 count: u32::MAX,
228 max: MAX_ROLE_COUNT,
229 },
230 )?)?),
231 None,
232 Some(size_token),
233 );
234 role.validate()?;
235 Ok(role)
236 }
237
238 #[must_use]
240 pub fn name(&self) -> &Ident {
241 &self.name
242 }
243
244 #[must_use]
246 pub fn param(&self) -> Option<&RoleParam> {
247 self.param.as_ref()
248 }
249
250 #[must_use]
252 pub fn index(&self) -> Option<&RoleIndex> {
253 self.index.as_ref()
254 }
255
256 #[must_use]
258 pub fn array_size(&self) -> Option<&TokenStream> {
259 self.array_size.as_ref()
260 }
261
262 #[must_use]
264 pub fn is_indexed(&self) -> bool {
265 self.index.is_some()
266 }
267
268 #[must_use]
270 pub fn to_ident(&self) -> Ident {
271 self.name.clone()
272 }
273
274 #[must_use]
276 pub fn is_parameterized(&self) -> bool {
277 self.index.is_some() || self.param.is_some()
278 }
279
280 #[must_use]
282 pub fn is_array(&self) -> bool {
283 self.array_size.is_some() || matches!(self.param, Some(RoleParam::Static(_)))
284 }
285
286 #[must_use]
288 pub fn is_dynamic(&self) -> bool {
289 matches!(self.param, Some(RoleParam::Runtime))
290 }
291
292 #[must_use]
294 pub fn is_symbolic(&self) -> bool {
295 matches!(self.param, Some(RoleParam::Symbolic(_)))
296 }
297
298 #[must_use]
300 pub fn is_wildcard(&self) -> bool {
301 matches!(self.index, Some(RoleIndex::Wildcard))
302 }
303
304 #[must_use]
306 pub fn is_range(&self) -> bool {
307 matches!(self.index, Some(RoleIndex::Range(_)))
308 }
309
310 #[must_use]
312 pub fn get_param(&self) -> Option<&RoleParam> {
313 self.param.as_ref()
314 }
315
316 #[must_use]
318 pub fn get_index(&self) -> Option<&RoleIndex> {
319 self.index.as_ref()
320 }
321
322 #[must_use]
324 pub fn get_static_count(&self) -> Option<u32> {
325 match &self.param {
326 Some(RoleParam::Static(count)) => Some(*count),
327 _ => None,
328 }
329 }
330
331 #[must_use]
333 pub fn get_symbolic_name(&self) -> Option<&str> {
334 match &self.param {
335 Some(RoleParam::Symbolic(name)) => Some(name),
336 _ => None,
337 }
338 }
339
340 #[must_use]
349 pub fn matches_family(&self, family: &Role) -> bool {
350 if self.name != family.name {
352 return false;
353 }
354
355 if family.is_array() {
357 return self.is_indexed() || self.param.is_some() || !self.is_array();
359 }
360
361 if family.param.is_some() && (self.is_indexed() || self.param.is_some()) {
363 return true;
364 }
365
366 self == family
368 }
369
370 pub fn validate(&self) -> RoleValidationResult<()> {
372 if let Some(param) = &self.param {
374 param.validate()?;
375 }
376
377 if let Some(index) = &self.index {
379 index.validate()?;
380 }
381
382 if let (Some(param), Some(index)) = (&self.param, &self.index) {
384 param.validate_with_index(index)?;
385 }
386
387 Ok(())
388 }
389
390 pub fn safe_static(name: Ident, count: u32) -> RoleValidationResult<Self> {
392 if count > MAX_ROLE_COUNT {
393 return Err(RoleValidationError::CountOverflow {
394 count,
395 max: MAX_ROLE_COUNT,
396 });
397 }
398
399 Role::with_param(name, RoleParam::Static(count))
400 }
401
402 pub fn safe_indexed(name: Ident, index: u32) -> RoleValidationResult<Self> {
404 if index > MAX_ROLE_INDEX {
405 return Err(RoleValidationError::IndexOverflow {
406 index,
407 max: MAX_ROLE_INDEX,
408 });
409 }
410
411 Role::with_index(name, RoleIndex::Concrete(index))
412 }
413
414 pub fn safe_range(name: Ident, start: u32, end: u32) -> RoleValidationResult<Self> {
416 let range = RoleRange {
417 start: RangeExpr::Concrete(start),
418 end: RangeExpr::Concrete(end),
419 };
420 range.validate()?;
421
422 Role::with_index(name, RoleIndex::Range(range))
423 }
424}