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::from(proc_macro2::TokenTree::Literal(
223 proc_macro2::Literal::usize_unsuffixed(size),
224 ));
225 let role = Self::new_unchecked(
226 name,
227 Some(RoleParam::safe_static(u32::try_from(size).map_err(
228 |_| RoleValidationError::CountOverflow {
229 count: u32::MAX,
230 max: MAX_ROLE_COUNT,
231 },
232 )?)?),
233 None,
234 Some(size_token),
235 );
236 role.validate()?;
237 Ok(role)
238 }
239
240 #[must_use]
242 pub fn name(&self) -> &Ident {
243 &self.name
244 }
245
246 #[must_use]
248 pub fn param(&self) -> Option<&RoleParam> {
249 self.param.as_ref()
250 }
251
252 #[must_use]
254 pub fn index(&self) -> Option<&RoleIndex> {
255 self.index.as_ref()
256 }
257
258 #[must_use]
260 pub fn array_size(&self) -> Option<&TokenStream> {
261 self.array_size.as_ref()
262 }
263
264 #[must_use]
266 pub fn is_indexed(&self) -> bool {
267 self.index.is_some()
268 }
269
270 #[must_use]
272 pub fn to_ident(&self) -> Ident {
273 self.name.clone()
274 }
275
276 #[must_use]
278 pub fn is_parameterized(&self) -> bool {
279 self.index.is_some() || self.param.is_some()
280 }
281
282 #[must_use]
284 pub fn is_array(&self) -> bool {
285 self.array_size.is_some() || matches!(self.param, Some(RoleParam::Static(_)))
286 }
287
288 #[must_use]
290 pub fn is_dynamic(&self) -> bool {
291 matches!(self.param, Some(RoleParam::Runtime))
292 }
293
294 #[must_use]
296 pub fn is_symbolic(&self) -> bool {
297 matches!(self.param, Some(RoleParam::Symbolic(_)))
298 }
299
300 #[must_use]
302 pub fn is_wildcard(&self) -> bool {
303 matches!(self.index, Some(RoleIndex::Wildcard))
304 }
305
306 #[must_use]
308 pub fn is_range(&self) -> bool {
309 matches!(self.index, Some(RoleIndex::Range(_)))
310 }
311
312 #[must_use]
314 pub fn get_param(&self) -> Option<&RoleParam> {
315 self.param.as_ref()
316 }
317
318 #[must_use]
320 pub fn get_index(&self) -> Option<&RoleIndex> {
321 self.index.as_ref()
322 }
323
324 #[must_use]
326 pub fn get_static_count(&self) -> Option<u32> {
327 match &self.param {
328 Some(RoleParam::Static(count)) => Some(*count),
329 _ => None,
330 }
331 }
332
333 #[must_use]
335 pub fn get_symbolic_name(&self) -> Option<&str> {
336 match &self.param {
337 Some(RoleParam::Symbolic(name)) => Some(name),
338 _ => None,
339 }
340 }
341
342 #[must_use]
351 pub fn matches_family(&self, family: &Role) -> bool {
352 if self.name != family.name {
354 return false;
355 }
356
357 if family.is_array() {
359 return self.is_indexed() || self.param.is_some() || !self.is_array();
361 }
362
363 if family.param.is_some() && (self.is_indexed() || self.param.is_some()) {
365 return true;
366 }
367
368 self == family
370 }
371
372 pub fn validate(&self) -> RoleValidationResult<()> {
374 if let Some(param) = &self.param {
376 param.validate()?;
377 }
378
379 if let Some(index) = &self.index {
381 index.validate()?;
382 }
383
384 if let (Some(param), Some(index)) = (&self.param, &self.index) {
386 param.validate_with_index(index)?;
387 }
388
389 Ok(())
390 }
391
392 pub fn safe_static(name: Ident, count: u32) -> RoleValidationResult<Self> {
394 if count > MAX_ROLE_COUNT {
395 return Err(RoleValidationError::CountOverflow {
396 count,
397 max: MAX_ROLE_COUNT,
398 });
399 }
400
401 Role::with_param(name, RoleParam::Static(count))
402 }
403
404 pub fn safe_indexed(name: Ident, index: u32) -> RoleValidationResult<Self> {
406 if index > MAX_ROLE_INDEX {
407 return Err(RoleValidationError::IndexOverflow {
408 index,
409 max: MAX_ROLE_INDEX,
410 });
411 }
412
413 Role::with_index(name, RoleIndex::Concrete(index))
414 }
415
416 pub fn safe_range(name: Ident, start: u32, end: u32) -> RoleValidationResult<Self> {
418 let range = RoleRange {
419 start: RangeExpr::Concrete(start),
420 end: RangeExpr::Concrete(end),
421 };
422 range.validate()?;
423
424 Role::with_index(name, RoleIndex::Range(range))
425 }
426}