Skip to main content

hydracache_db/
profiles.rs

1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum DimensionProfile {
5    TenantScoped,
6    PermissionScoped,
7    TenantPermissionScoped,
8    TenantPermissionSearch,
9    PagedSearch,
10    CursorList,
11    LocaleRegionScoped,
12    FeatureFlagScoped,
13    Custom(CustomProfile),
14}
15
16impl DimensionProfile {
17    pub fn name(&self) -> &str {
18        match self {
19            Self::TenantScoped => "tenant_scoped",
20            Self::PermissionScoped => "permission_scoped",
21            Self::TenantPermissionScoped => "tenant_permission_scoped",
22            Self::TenantPermissionSearch => "tenant_permission_search",
23            Self::PagedSearch => "paged_search",
24            Self::CursorList => "cursor_list",
25            Self::LocaleRegionScoped => "locale_region_scoped",
26            Self::FeatureFlagScoped => "feature_flag_scoped",
27            Self::Custom(profile) => profile.name(),
28        }
29    }
30
31    pub fn requirements(&self) -> Vec<DimensionRequirement> {
32        match self {
33            Self::TenantScoped => vec![DimensionRequirement::linked("tenant")],
34            Self::PermissionScoped => vec![DimensionRequirement::linked("permission")],
35            Self::TenantPermissionScoped => vec![
36                DimensionRequirement::linked("tenant"),
37                DimensionRequirement::linked("permission"),
38            ],
39            Self::TenantPermissionSearch => vec![
40                DimensionRequirement::linked("tenant"),
41                DimensionRequirement::linked("permission"),
42                DimensionRequirement::linked("q"),
43                DimensionRequirement::linked("page"),
44                DimensionRequirement::linked("sort"),
45            ],
46            Self::PagedSearch => vec![
47                DimensionRequirement::linked("q"),
48                DimensionRequirement::linked("page"),
49                DimensionRequirement::linked("sort"),
50            ],
51            Self::CursorList => vec![DimensionRequirement::linked("cursor")],
52            Self::LocaleRegionScoped => vec![
53                DimensionRequirement::linked("locale"),
54                DimensionRequirement::linked("region"),
55            ],
56            Self::FeatureFlagScoped => vec![DimensionRequirement::linked("feature")],
57            Self::Custom(profile) => profile.requirements().to_vec(),
58        }
59    }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct CustomProfile {
64    name: String,
65    requirements: Vec<DimensionRequirement>,
66}
67
68impl CustomProfile {
69    pub fn new(
70        name: impl Into<String>,
71        requirements: impl IntoIterator<Item = DimensionRequirement>,
72    ) -> Self {
73        Self {
74            name: name.into(),
75            requirements: requirements.into_iter().collect(),
76        }
77    }
78
79    pub fn name(&self) -> &str {
80        &self.name
81    }
82
83    pub fn requirements(&self) -> &[DimensionRequirement] {
84        &self.requirements
85    }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct DimensionRequirement {
90    label: String,
91    require_key_tag_link: bool,
92}
93
94impl DimensionRequirement {
95    pub fn linked(label: impl Into<String>) -> Self {
96        Self {
97            label: label.into(),
98            require_key_tag_link: true,
99        }
100    }
101
102    pub fn key_only(label: impl Into<String>) -> Self {
103        Self {
104            label: label.into(),
105            require_key_tag_link: false,
106        }
107    }
108
109    pub fn label(&self) -> &str {
110        &self.label
111    }
112
113    pub fn require_key_tag_link(&self) -> bool {
114        self.require_key_tag_link
115    }
116}
117
118#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
119pub enum DimensionValidationMode {
120    #[default]
121    Warn,
122    Deny,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub enum ProfileValidation {
127    Pass,
128    MissingDimensions(Vec<String>),
129    UnlinkedDimensions(Vec<String>),
130    Allowed {
131        status: Box<ProfileValidation>,
132        reason: String,
133    },
134}
135
136impl ProfileValidation {
137    pub fn is_pass(&self) -> bool {
138        matches!(self, Self::Pass | Self::Allowed { .. })
139    }
140}
141
142impl fmt::Display for ProfileValidation {
143    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            Self::Pass => formatter.write_str("pass"),
146            Self::MissingDimensions(labels) => {
147                write!(formatter, "missing dimensions: {}", labels.join(", "))
148            }
149            Self::UnlinkedDimensions(labels) => {
150                write!(formatter, "unlinked dimensions: {}", labels.join(", "))
151            }
152            Self::Allowed { status, reason } => {
153                write!(formatter, "allowed {status} because {reason}")
154            }
155        }
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct DimensionAllow {
161    label: String,
162    reason: String,
163}
164
165impl DimensionAllow {
166    pub fn new(
167        label: impl Into<String>,
168        reason: impl Into<String>,
169    ) -> Result<Self, DimensionAllowError> {
170        let reason = reason.into();
171        if reason.trim().is_empty() {
172            return Err(DimensionAllowError::EmptyReason);
173        }
174        Ok(Self {
175            label: label.into(),
176            reason,
177        })
178    }
179
180    pub fn label(&self) -> &str {
181        &self.label
182    }
183
184    pub fn reason(&self) -> &str {
185        &self.reason
186    }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum DimensionAllowError {
191    EmptyReason,
192}
193
194impl fmt::Display for DimensionAllowError {
195    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Self::EmptyReason => formatter.write_str("dimension allow reason cannot be empty"),
198        }
199    }
200}
201
202impl std::error::Error for DimensionAllowError {}