bit-roles
Granular role and permission management based on bit flags
Bit roles is a crate for managing granular roles and permissions using bit flags.
A permission structure that manages its own roles utilizes the BitRole derive attribute to automatically generate implementations at compile time. This ensures that all enum discriminants are strictly validated to be either zero or a power of two. This avoids the overhead of runtime validation and eliminates the possibility of overlapping flags.
Usage
Permissions are defined using the BitRole macro. The macro ensures at compile-time that the enum discriminants are valid:
use bit_roles::BitRole;
#[derive(Debug, BitRole, Copy, Clone)]
enum Permission {
None = 0,
SendMessage = 1,
EditMessage = 2,
DeleteMessage = 4,
}Implementations begin with an empty set, allowing permissions to be added or removed as needed:
fn main() {
let mut roles = Permission::empty();
roles.add_one(Permission::SendMessage);
if roles.has_one(Permission::SendMessage) {
println!("can send msg");
}
// serialized the value
let raw_value = roles.get_value();
// construct the instance from a raw value
let permissions = Permission::from_value(raw_value);
assert!(permissions.has_one(Permission::SendMessage));
}Bulk operations
When it is necessary to check or modify multiple permissions simultaneously, vectors can be utilized to handle them together in place of chained method calls:
let mut roles = Permission::empty();
roles.add_all(vec![Permission::SendMessage, Permission::EditMessage]);
// check if all specified roles are present
assert!(roles.has_all(vec![Permission::SendMessage, Permission::EditMessage]));
// check if any of the specified roles are present
assert!(roles.has_any(vec![Permission::EditMessage, Permission::DeleteMessage]));
roles.remove_all(vec![Permission::EditMessage]);Unchecked roles
While compile time validation is strictly enforced by default, there are situations where permissions must be managed dynamically, such as when dealing with composite enums that carry additional data. In these cases, the macro cannot verify the values at compile time.
For these design patterns, the BitRoleUnchecked macro is provided. It requires a manual implementation of Into<usize>, but provides the flexibility necessary to define complex, composite role structures:
use bit_roles::{BitRoleUnchecked, RoleValue};
#[derive(Debug, Copy, Clone)]
enum MessagePermission {
ToEveryone,
ToFriends,
}
#[derive(Debug, BitRoleUnchecked, Copy, Clone)]
enum Permission {
None,
SendMessage(MessagePermission),
EditMessage,
}
impl Into<usize> for Permission {
fn into(self) -> usize {
match self {
Permission::None => 0,
Permission::SendMessage(p) => match p {
MessagePermission::ToEveryone => 1,
MessagePermission::ToFriends => 2,
},
Permission::EditMessage => 4
}
}
}
fn main() {
let mut roles = Permission::empty();
roles.try_add_one(RoleValue::Role(Permission::SendMessage(MessagePermission::ToEveryone))).unwrap();
// adding raw values directly
roles.try_add_one(RoleValue::Raw(4)).unwrap();
assert!(roles.has_one(Permission::EditMessage));
}