1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct ScopeSet(BTreeSet<String>);
9
10impl ScopeSet {
11 pub fn new(scopes: impl IntoIterator<Item = String>) -> Self {
13 Self(
14 scopes
15 .into_iter()
16 .filter(|scope| !scope.is_empty())
17 .collect(),
18 )
19 }
20
21 pub fn contains(&self, scope: &str) -> bool {
23 self.0.contains(scope)
24 }
25
26 pub fn to_vec(&self) -> Vec<String> {
28 self.0.iter().cloned().collect()
29 }
30}
31
32impl<const N: usize> From<[&str; N]> for ScopeSet {
33 fn from(scopes: [&str; N]) -> Self {
34 Self::new(scopes.into_iter().map(ToOwned::to_owned))
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct Principal {
41 pub subject: String,
43 pub scopes: ScopeSet,
45 pub issuer: Option<String>,
47}
48
49impl Principal {
50 pub fn new(subject: impl Into<String>, scopes: ScopeSet) -> Self {
52 Self {
53 subject: subject.into(),
54 scopes,
55 issuer: None,
56 }
57 }
58
59 pub fn with_issuer(mut self, issuer: impl Into<String>) -> Self {
61 self.issuer = Some(issuer.into());
62 self
63 }
64
65 pub fn anonymous() -> Self {
67 Self::new("anonymous", ScopeSet::default())
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::ScopeSet;
74
75 #[test]
76 fn scope_sets_are_deduplicated_and_stable() {
77 let scopes = ScopeSet::new([
78 "soma:write".to_owned(),
79 "soma:read".to_owned(),
80 "soma:write".to_owned(),
81 ]);
82 assert_eq!(
83 scopes.to_vec(),
84 vec!["soma:read".to_owned(), "soma:write".to_owned()]
85 );
86 }
87}