use proc_macro2::{Ident, TokenStream};
#[path = "role_ops.rs"]
mod ops;
pub use ops::RoleBoundsChecker;
pub const MAX_ROLE_COUNT: u32 = 10_000;
pub const MAX_ROLE_INDEX: u32 = MAX_ROLE_COUNT - 1;
pub const MAX_RANGE_COUNT: u32 = 1_000;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RoleValidationError {
#[error("Role count {count} exceeds maximum allowed {max}")]
CountOverflow { count: u32, max: u32 },
#[error("Role index {index} exceeds maximum allowed {max}")]
IndexOverflow { index: u32, max: u32 },
#[error("Range size {size} exceeds maximum allowed {max}")]
RangeSizeOverflow { size: u32, max: u32 },
#[error("Invalid range: start {start} >= end {end}")]
InvalidRange { start: u32, end: u32 },
#[error("Runtime role count must be bounded for safety")]
UnboundedRuntime,
#[error("Symbolic parameter '{param}' cannot be validated without runtime context")]
SymbolicValidation { param: String },
}
pub type RoleValidationResult<T> = Result<T, RoleValidationError>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RoleParam {
Static(u32),
Symbolic(String),
Runtime,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RoleIndex {
Concrete(u32),
Symbolic(String),
Wildcard,
Range(RoleRange),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RoleRange {
pub start: RangeExpr,
pub end: RangeExpr,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RangeExpr {
Concrete(u32),
Symbolic(String),
}
#[derive(Debug, Clone)]
pub struct Role {
name: Ident,
param: Option<RoleParam>,
index: Option<RoleIndex>,
array_size: Option<TokenStream>,
}
impl PartialEq for Role {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.param == other.param
&& self.index == other.index
&& self
.array_size
.as_ref()
.map(std::string::ToString::to_string)
== other
.array_size
.as_ref()
.map(std::string::ToString::to_string)
}
}
impl Eq for Role {}
impl std::hash::Hash for Role {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.param.hash(state);
self.index.hash(state);
if let Some(size) = &self.array_size {
size.to_string().hash(state);
}
}
}
impl Role {
fn new_unchecked(
name: Ident,
param: Option<RoleParam>,
index: Option<RoleIndex>,
array_size: Option<TokenStream>,
) -> Self {
Role {
name,
param,
index,
array_size,
}
}
pub fn new(name: Ident) -> RoleValidationResult<Self> {
let role = Self::new_unchecked(name, None, None, None);
role.validate()?;
Ok(role)
}
pub fn with_param(name: Ident, param: RoleParam) -> RoleValidationResult<Self> {
let role = Self::new_unchecked(name, Some(param), None, None);
role.validate()?;
Ok(role)
}
pub fn with_index(name: Ident, index: RoleIndex) -> RoleValidationResult<Self> {
let role = Self::new_unchecked(name, None, Some(index), None);
role.validate()?;
Ok(role)
}
pub fn with_param_and_index(
name: Ident,
param: RoleParam,
index: RoleIndex,
) -> RoleValidationResult<Self> {
let role = Self::new_unchecked(name, Some(param), Some(index), None);
role.validate()?;
Ok(role)
}
pub fn indexed(name: Ident, index: usize) -> RoleValidationResult<Self> {
let role_index = RoleIndex::safe_concrete(u32::try_from(index).map_err(|_| {
RoleValidationError::IndexOverflow {
index: u32::MAX,
max: MAX_ROLE_INDEX,
}
})?)?;
let role = Self::new_unchecked(name, None, Some(role_index), None);
role.validate()?;
Ok(role)
}
pub fn parameterized(name: Ident, param: TokenStream) -> RoleValidationResult<Self> {
let role = Self::new_unchecked(
name,
Some(RoleParam::Symbolic(param.to_string())),
None,
Some(param),
);
role.validate()?;
Ok(role)
}
pub fn array(name: Ident, size: usize) -> RoleValidationResult<Self> {
let size_token = TokenStream::from(proc_macro2::TokenTree::Literal(
proc_macro2::Literal::usize_unsuffixed(size),
));
let role = Self::new_unchecked(
name,
Some(RoleParam::safe_static(u32::try_from(size).map_err(
|_| RoleValidationError::CountOverflow {
count: u32::MAX,
max: MAX_ROLE_COUNT,
},
)?)?),
None,
Some(size_token),
);
role.validate()?;
Ok(role)
}
#[must_use]
pub fn name(&self) -> &Ident {
&self.name
}
#[must_use]
pub fn param(&self) -> Option<&RoleParam> {
self.param.as_ref()
}
#[must_use]
pub fn index(&self) -> Option<&RoleIndex> {
self.index.as_ref()
}
#[must_use]
pub fn array_size(&self) -> Option<&TokenStream> {
self.array_size.as_ref()
}
#[must_use]
pub fn is_indexed(&self) -> bool {
self.index.is_some()
}
#[must_use]
pub fn to_ident(&self) -> Ident {
self.name.clone()
}
#[must_use]
pub fn is_parameterized(&self) -> bool {
self.index.is_some() || self.param.is_some()
}
#[must_use]
pub fn is_array(&self) -> bool {
self.array_size.is_some() || matches!(self.param, Some(RoleParam::Static(_)))
}
#[must_use]
pub fn is_dynamic(&self) -> bool {
matches!(self.param, Some(RoleParam::Runtime))
}
#[must_use]
pub fn is_symbolic(&self) -> bool {
matches!(self.param, Some(RoleParam::Symbolic(_)))
}
#[must_use]
pub fn is_wildcard(&self) -> bool {
matches!(self.index, Some(RoleIndex::Wildcard))
}
#[must_use]
pub fn is_range(&self) -> bool {
matches!(self.index, Some(RoleIndex::Range(_)))
}
#[must_use]
pub fn get_param(&self) -> Option<&RoleParam> {
self.param.as_ref()
}
#[must_use]
pub fn get_index(&self) -> Option<&RoleIndex> {
self.index.as_ref()
}
#[must_use]
pub fn get_static_count(&self) -> Option<u32> {
match &self.param {
Some(RoleParam::Static(count)) => Some(*count),
_ => None,
}
}
#[must_use]
pub fn get_symbolic_name(&self) -> Option<&str> {
match &self.param {
Some(RoleParam::Symbolic(name)) => Some(name),
_ => None,
}
}
#[must_use]
pub fn matches_family(&self, family: &Role) -> bool {
if self.name != family.name {
return false;
}
if family.is_array() {
return self.is_indexed() || self.param.is_some() || !self.is_array();
}
if family.param.is_some() && (self.is_indexed() || self.param.is_some()) {
return true;
}
self == family
}
pub fn validate(&self) -> RoleValidationResult<()> {
if let Some(param) = &self.param {
param.validate()?;
}
if let Some(index) = &self.index {
index.validate()?;
}
if let (Some(param), Some(index)) = (&self.param, &self.index) {
param.validate_with_index(index)?;
}
Ok(())
}
pub fn safe_static(name: Ident, count: u32) -> RoleValidationResult<Self> {
if count > MAX_ROLE_COUNT {
return Err(RoleValidationError::CountOverflow {
count,
max: MAX_ROLE_COUNT,
});
}
Role::with_param(name, RoleParam::Static(count))
}
pub fn safe_indexed(name: Ident, index: u32) -> RoleValidationResult<Self> {
if index > MAX_ROLE_INDEX {
return Err(RoleValidationError::IndexOverflow {
index,
max: MAX_ROLE_INDEX,
});
}
Role::with_index(name, RoleIndex::Concrete(index))
}
pub fn safe_range(name: Ident, start: u32, end: u32) -> RoleValidationResult<Self> {
let range = RoleRange {
start: RangeExpr::Concrete(start),
end: RangeExpr::Concrete(end),
};
range.validate()?;
Role::with_index(name, RoleIndex::Range(range))
}
}