use std::collections::BTreeSet;
pub fn bot_all_scopes() -> Vec<String> {
vec![
"channels:history",
"channels:join",
"channels:manage",
"channels:read",
"channels:write.invites",
"channels:write.topic",
"chat:write",
"chat:write.customize",
"chat:write.public",
"commands",
"conversations.connect:manage",
"conversations.connect:read",
"conversations.connect:write",
"dnd:read",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"groups:write",
"groups:write.invites",
"groups:write.topic",
"im:history",
"im:read",
"im:write",
"im:write.topic",
"links:read",
"links:write",
"mpim:history",
"mpim:read",
"mpim:write",
"mpim:write.topic",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"reminders:read",
"reminders:write",
"team:read",
"usergroups:read",
"usergroups:write",
"users.profile:read",
"users:read",
"users:read.email",
"users:write",
]
.iter()
.map(|s| s.to_string())
.collect()
}
pub fn user_all_scopes() -> Vec<String> {
vec![
"channels:history",
"channels:read",
"channels:write",
"chat:write",
"dnd:read",
"dnd:write",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"groups:write",
"identify",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"reminders:read",
"reminders:write",
"search:read",
"stars:read",
"stars:write",
"team:read",
"usergroups:read",
"usergroups:write",
"users.profile:read",
"users.profile:write",
"users:read",
"users:read.email",
]
.iter()
.map(|s| s.to_string())
.collect()
}
pub fn all_scopes() -> Vec<String> {
bot_all_scopes()
}
#[allow(dead_code)]
pub fn bot_scopes() -> Vec<String> {
vec![
"channels:history",
"channels:read",
"channels:write",
"chat:write",
"conversations.connect:read",
"conversations.connect:write",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"groups:write",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"reminders:read",
"reminders:write",
"usergroups:read",
"usergroups:write",
"users:read",
]
.iter()
.map(|s| s.to_string())
.collect()
}
#[allow(dead_code)]
pub fn user_scopes() -> Vec<String> {
vec![
"channels:history",
"channels:read",
"channels:write",
"chat:write",
"dnd:read",
"dnd:write",
"files:read",
"files:write",
"groups:history",
"groups:read",
"groups:write",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"reminders:read",
"reminders:write",
"search:read",
"stars:read",
"stars:write",
"team:read",
"users.profile:read",
"users.profile:write",
"users:read",
]
.iter()
.map(|s| s.to_string())
.collect()
}
pub fn expand_scopes(input_scopes: &[String]) -> Vec<String> {
let mut expanded = BTreeSet::new();
for scope in input_scopes {
let normalized = scope.trim().to_lowercase();
match normalized.as_str() {
"all" => {
for preset_scope in all_scopes() {
expanded.insert(preset_scope);
}
}
"bot:all" => {
for preset_scope in bot_all_scopes() {
expanded.insert(preset_scope);
}
}
"user:all" => {
for preset_scope in user_all_scopes() {
expanded.insert(preset_scope);
}
}
_ => {
expanded.insert(scope.trim().to_string());
}
}
}
expanded.into_iter().collect()
}
pub fn expand_scopes_with_context(input_scopes: &[String], is_bot_context: bool) -> Vec<String> {
let mut expanded = BTreeSet::new();
for scope in input_scopes {
let normalized = scope.trim().to_lowercase();
match normalized.as_str() {
"all" => {
let preset = if is_bot_context {
bot_all_scopes()
} else {
user_all_scopes()
};
for preset_scope in preset {
expanded.insert(preset_scope);
}
}
"bot:all" => {
for preset_scope in bot_all_scopes() {
expanded.insert(preset_scope);
}
}
"user:all" => {
for preset_scope in user_all_scopes() {
expanded.insert(preset_scope);
}
}
_ => {
expanded.insert(scope.trim().to_string());
}
}
}
expanded.into_iter().collect()
}
#[allow(dead_code)]
pub fn expand_bot_scopes(input_scopes: &[String]) -> Vec<String> {
let mut expanded = BTreeSet::new();
for scope in input_scopes {
let normalized = scope.trim().to_lowercase();
if normalized == "all" || normalized == "bot:all" {
for preset_scope in bot_scopes() {
expanded.insert(preset_scope);
}
} else if normalized == "user:all" {
for preset_scope in user_scopes() {
expanded.insert(preset_scope);
}
} else {
expanded.insert(scope.trim().to_string());
}
}
expanded.into_iter().collect()
}
#[allow(dead_code)]
pub fn expand_user_scopes(input_scopes: &[String]) -> Vec<String> {
let mut expanded = BTreeSet::new();
for scope in input_scopes {
let normalized = scope.trim().to_lowercase();
if normalized == "all" || normalized == "user:all" {
for preset_scope in user_scopes() {
expanded.insert(preset_scope);
}
} else if normalized == "bot:all" {
for preset_scope in bot_scopes() {
expanded.insert(preset_scope);
}
} else {
expanded.insert(scope.trim().to_string());
}
}
expanded.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_scopes_returns_stable_list() {
let scopes1 = all_scopes();
let scopes2 = all_scopes();
assert_eq!(scopes1, scopes2, "all_scopes() should return a stable list");
assert!(!scopes1.is_empty(), "all_scopes() should not be empty");
}
#[test]
fn test_all_scopes_is_sorted() {
let scopes = all_scopes();
let mut sorted = scopes.clone();
sorted.sort();
assert_eq!(
scopes, sorted,
"all_scopes() should be alphabetically sorted"
);
}
#[test]
fn test_all_scopes_contains_expected_scopes() {
let scopes = all_scopes();
assert!(scopes.contains(&"chat:write".to_string()));
assert!(scopes.contains(&"users:read".to_string()));
assert!(scopes.contains(&"channels:read".to_string()));
}
#[test]
fn test_expand_scopes_with_all_only() {
let input = vec!["all".to_string()];
let result = expand_scopes(&input);
assert!(result.contains(&"chat:write".to_string()));
assert!(result.contains(&"users:read".to_string()));
assert!(result.len() > 10, "all should expand to many scopes");
}
#[test]
fn test_expand_scopes_case_insensitive() {
let input1 = vec!["all".to_string()];
let input2 = vec!["ALL".to_string()];
let input3 = vec!["AlL".to_string()];
let result1 = expand_scopes(&input1);
let result2 = expand_scopes(&input2);
let result3 = expand_scopes(&input3);
assert_eq!(result1, result2);
assert_eq!(result2, result3);
}
#[test]
fn test_expand_scopes_mixed_with_duplicates() {
let input = vec![
"all".to_string(),
"chat:write".to_string(), "custom:scope".to_string(),
];
let result = expand_scopes(&input);
assert!(result.contains(&"chat:write".to_string()));
assert!(result.contains(&"users:read".to_string()));
assert!(result.contains(&"custom:scope".to_string()));
assert_eq!(
result.iter().filter(|s| *s == "chat:write").count(),
1,
"chat:write should appear only once"
);
}
#[test]
fn test_expand_scopes_no_presets() {
let input = vec!["chat:write".to_string(), "users:read".to_string()];
let result = expand_scopes(&input);
assert_eq!(result.len(), 2);
assert!(result.contains(&"chat:write".to_string()));
assert!(result.contains(&"users:read".to_string()));
}
#[test]
fn test_expand_scopes_empty() {
let input: Vec<String> = vec![];
let result = expand_scopes(&input);
assert!(result.is_empty());
}
#[test]
fn test_expand_scopes_preserves_case_for_non_presets() {
let input = vec!["Custom:Scope".to_string()];
let result = expand_scopes(&input);
assert_eq!(result.len(), 1);
assert_eq!(result[0], "Custom:Scope");
}
#[test]
fn test_expand_scopes_trims_whitespace() {
let input = vec![" chat:write ".to_string(), " all ".to_string()];
let result = expand_scopes(&input);
assert!(result.contains(&"chat:write".to_string()));
assert!(result.contains(&"users:read".to_string())); }
#[test]
fn test_expand_scopes_with_bot_all_preset() {
let input = vec!["bot:all".to_string()];
let result = expand_scopes(&input);
assert!(result.contains(&"chat:write".to_string()));
assert!(result.contains(&"channels:read".to_string()));
assert!(!result.contains(&"search:read".to_string())); }
#[test]
fn test_expand_scopes_with_user_all_preset() {
let input = vec!["user:all".to_string()];
let result = expand_scopes(&input);
assert!(result.contains(&"search:read".to_string()));
assert!(result.contains(&"users:read".to_string()));
assert!(result.contains(&"chat:write".to_string()));
}
#[test]
fn test_expand_scopes_with_context_bot() {
let input = vec!["all".to_string()];
let result = expand_scopes_with_context(&input, true);
assert!(result.contains(&"chat:write".to_string()));
assert!(!result.contains(&"search:read".to_string())); }
#[test]
fn test_expand_scopes_with_context_user() {
let input = vec!["all".to_string()];
let result = expand_scopes_with_context(&input, false);
assert!(result.contains(&"search:read".to_string()));
assert!(result.contains(&"users:read".to_string()));
}
#[test]
fn test_bot_all_scopes_no_user_only_scopes() {
let scopes = bot_all_scopes();
assert!(!scopes.contains(&"search:read".to_string()));
assert!(!scopes.contains(&"stars:read".to_string()));
}
#[test]
fn test_user_all_scopes_includes_user_scopes() {
let scopes = user_all_scopes();
assert!(scopes.contains(&"search:read".to_string()));
assert!(scopes.contains(&"stars:read".to_string()));
assert!(scopes.contains(&"users:read".to_string()));
}
}