1use std::marker::PhantomData;
10
11use dex_protocol::dex::{AttributeSyncConfig, IndexConfig};
12
13use crate::{Context, HandlerError, HandlerResult, Value};
14
15pub struct Attribute<T> {
30 name: String,
31 index: Option<AttributeIndex>,
32 sync_to_attribute_store: bool,
33 marker: PhantomData<fn() -> T>,
34}
35
36impl<T> Attribute<T> {
37 pub fn new(name: impl Into<String>) -> Self {
39 Self {
40 name: name.into(),
41 index: None,
42 sync_to_attribute_store: false,
43 marker: PhantomData,
44 }
45 }
46
47 pub fn indexed(mut self, index: AttributeIndex) -> Self {
49 self.index = Some(index);
50 self
51 }
52
53 #[must_use]
59 pub fn sync_to_attribute_store(mut self) -> Self {
60 self.sync_to_attribute_store = true;
61 self
62 }
63
64 pub fn get(&self, context: &Context) -> HandlerResult<Option<T>>
72 where
73 T: Value,
74 {
75 context.get_attribute(self)
76 }
77
78 pub fn get_required(&self, context: &Context) -> HandlerResult<T>
84 where
85 T: Value,
86 {
87 self.get(context)?.ok_or_else(|| {
88 HandlerError::new(
89 "dex_sdk::HandlerError",
90 format!("attribute {} is missing", self.name),
91 )
92 })
93 }
94
95 pub fn set(&self, context: &mut Context, value: T) -> HandlerResult<()>
101 where
102 T: Value,
103 {
104 context.set_attribute(self, value)
105 }
106
107 pub fn delete(&self, context: &mut Context) -> HandlerResult<()> {
111 context.delete_attribute(self)
112 }
113
114 pub fn lock(&self) -> AttributeLock {
116 AttributeLock {
117 attribute: self.name.clone(),
118 instance: None,
119 }
120 }
121
122 pub(crate) fn name(&self) -> &str {
123 &self.name
124 }
125
126 pub(crate) fn index(&self) -> Option<&AttributeIndex> {
127 self.index.as_ref()
128 }
129
130 pub(crate) fn sync_config(&self) -> Option<AttributeSyncConfig> {
131 self.sync_to_attribute_store
132 .then_some(AttributeSyncConfig { enabled: true })
133 }
134
135 pub(crate) fn is_sync_to_attribute_store(&self) -> bool {
136 self.sync_to_attribute_store
137 }
138}
139
140impl<T> Clone for Attribute<T> {
141 fn clone(&self) -> Self {
142 Self {
143 name: self.name.clone(),
144 index: self.index.clone(),
145 sync_to_attribute_store: self.sync_to_attribute_store,
146 marker: PhantomData,
147 }
148 }
149}
150
151pub struct AttributeMap<T> {
156 name: String,
157 index: Option<AttributeIndex>,
158 sync_to_attribute_store: bool,
159 marker: PhantomData<fn() -> T>,
160}
161
162impl<T> AttributeMap<T> {
163 pub fn new(name: impl Into<String>) -> Self {
165 Self {
166 name: name.into(),
167 index: None,
168 sync_to_attribute_store: false,
169 marker: PhantomData,
170 }
171 }
172
173 pub fn indexed(mut self, index: AttributeIndex) -> Self {
175 self.index = Some(index);
176 self
177 }
178
179 #[must_use]
185 pub fn sync_to_attribute_store(mut self) -> Self {
186 self.sync_to_attribute_store = true;
187 self
188 }
189
190 pub fn get(&self, context: &Context, instance: &str) -> HandlerResult<Option<T>>
196 where
197 T: Value,
198 {
199 context.get_attribute_map(self, instance)
200 }
201
202 pub fn get_required(&self, context: &Context, instance: &str) -> HandlerResult<T>
208 where
209 T: Value,
210 {
211 self.get(context, instance)?.ok_or_else(|| {
212 HandlerError::new(
213 "dex_sdk::HandlerError",
214 format!("attribute {}[{instance}] is missing", self.name),
215 )
216 })
217 }
218
219 pub fn set(&self, context: &mut Context, instance: &str, value: T) -> HandlerResult<()>
225 where
226 T: Value,
227 {
228 context.set_attribute_map(self, instance, value)
229 }
230
231 pub fn delete(&self, context: &mut Context, instance: &str) -> HandlerResult<()> {
233 context.delete_attribute_map(self, instance)
234 }
235
236 pub fn map_size(&self, context: &Context) -> HandlerResult<usize> {
238 Ok(self.all_instance_keys(context)?.len())
239 }
240
241 pub fn all_instance_keys(&self, context: &Context) -> HandlerResult<Vec<String>> {
243 context.attribute_map_keys(self)
244 }
245
246 pub fn lock(&self, instance: impl Into<String>) -> AttributeLock {
248 AttributeLock {
249 attribute: self.name.clone(),
250 instance: Some(instance.into()),
251 }
252 }
253
254 pub(crate) fn name(&self) -> &str {
255 &self.name
256 }
257
258 pub(crate) fn index(&self) -> Option<&AttributeIndex> {
259 self.index.as_ref()
260 }
261
262 pub(crate) fn sync_config(&self) -> Option<AttributeSyncConfig> {
263 self.sync_to_attribute_store
264 .then_some(AttributeSyncConfig { enabled: true })
265 }
266
267 pub(crate) fn is_sync_to_attribute_store(&self) -> bool {
268 self.sync_to_attribute_store
269 }
270}
271
272impl<T> Clone for AttributeMap<T> {
273 fn clone(&self) -> Self {
274 Self {
275 name: self.name.clone(),
276 index: self.index.clone(),
277 sync_to_attribute_store: self.sync_to_attribute_store,
278 marker: PhantomData,
279 }
280 }
281}
282
283#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct AttributeIndex {
289 kind: AttributeIndexKind,
290 key: Option<String>,
291}
292
293impl AttributeIndex {
294 fn new(kind: AttributeIndexKind) -> Self {
295 Self { kind, key: None }
296 }
297
298 pub fn keyword() -> Self {
300 Self::new(AttributeIndexKind::Keyword)
301 }
302
303 pub fn full_text() -> Self {
305 Self::new(AttributeIndexKind::FullText)
306 }
307
308 pub fn keyword_array() -> Self {
310 Self::new(AttributeIndexKind::KeywordArray)
311 }
312
313 pub fn int() -> Self {
315 Self::new(AttributeIndexKind::Int)
316 }
317
318 pub fn double() -> Self {
320 Self::new(AttributeIndexKind::Double)
321 }
322
323 pub fn bool() -> Self {
325 Self::new(AttributeIndexKind::Bool)
326 }
327
328 pub fn date_time() -> Self {
330 Self::new(AttributeIndexKind::DateTime)
331 }
332
333 pub fn with_key(mut self, key: impl Into<String>) -> Self {
335 self.key = Some(key.into());
336 self
337 }
338}
339
340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
341pub(crate) enum AttributeIndexKind {
342 Keyword,
343 FullText,
344 KeywordArray,
345 Int,
346 Double,
347 Bool,
348 DateTime,
349}
350
351#[derive(Clone, Debug, Eq, PartialEq)]
352pub struct AttributeLock {
357 attribute: String,
358 instance: Option<String>,
359}
360
361impl AttributeIndex {
362 pub(crate) fn proto_config(&self, dynamic: bool) -> IndexConfig {
363 IndexConfig {
364 enable: true,
365 r#type: self.kind.proto_value(),
366 index_key: self
367 .key
368 .clone()
369 .or_else(|| dynamic.then(String::new))
370 .unwrap_or_default(),
371 }
372 }
373
374 pub(crate) fn key(&self) -> Option<&str> {
375 self.key.as_deref()
376 }
377
378 pub(crate) fn proto_value(&self) -> i32 {
379 self.kind.proto_value()
380 }
381}
382
383impl AttributeIndexKind {
384 pub(crate) fn proto_value(self) -> i32 {
385 use dex_protocol::dex::IndexType;
386
387 match self {
388 Self::Keyword => IndexType::Keyword.into(),
389 Self::FullText => IndexType::Text.into(),
390 Self::KeywordArray => IndexType::KeywordArray.into(),
391 Self::Int => IndexType::Int.into(),
392 Self::Double => IndexType::Double.into(),
393 Self::Bool => IndexType::Bool.into(),
394 Self::DateTime => IndexType::Datetime.into(),
395 }
396 }
397}
398
399impl AttributeLock {
400 pub(crate) fn physical_name(&self) -> String {
401 match self.instance.as_deref() {
402 Some(instance) => crate::registry::physical_name(&self.attribute, instance),
403 None => self.attribute.clone(),
404 }
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::{Attribute, AttributeMap};
411
412 #[test]
413 fn sync_configuration_is_opt_in_and_survives_clone() {
414 let plain = Attribute::<String>::new("plain");
415 let synced = Attribute::<String>::new("synced").sync_to_attribute_store();
416 let synced_map = AttributeMap::<String>::new("synced_map").sync_to_attribute_store();
417
418 assert!(plain.sync_config().is_none());
419 assert_eq!(
420 synced.sync_config().map(|config| config.enabled),
421 Some(true)
422 );
423 assert_eq!(
424 synced_map
425 .clone()
426 .sync_config()
427 .map(|config| config.enabled),
428 Some(true)
429 );
430 }
431}