1use std::marker::PhantomData;
10
11use dex_protocol::dex::{AttributeSyncConfig, IndexConfig};
12
13use crate::{Context, HandlerError, HandlerResult, Value};
14
15#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct AttributeMapLoad {
21 pub(crate) name: String,
22 pub(crate) instance: Option<String>,
23}
24
25pub struct Attribute<T> {
40 name: String,
41 index: Option<AttributeIndex>,
42 sync_to_attribute_store: bool,
43 marker: PhantomData<fn() -> T>,
44}
45
46impl<T> Attribute<T> {
47 pub fn new(name: impl Into<String>) -> Self {
51 Self {
52 name: name.into(),
53 index: None,
54 sync_to_attribute_store: false,
55 marker: PhantomData,
56 }
57 }
58
59 pub fn indexed(mut self, index: AttributeIndex) -> Self {
61 self.index = Some(index);
62 self
63 }
64
65 #[must_use]
71 pub fn sync_to_attribute_store(mut self) -> Self {
72 self.sync_to_attribute_store = true;
73 self
74 }
75
76 pub fn get(&self, context: &Context) -> HandlerResult<Option<T>>
84 where
85 T: Value,
86 {
87 context.get_attribute(self)
88 }
89
90 pub fn get_required(&self, context: &Context) -> HandlerResult<T>
96 where
97 T: Value,
98 {
99 self.get(context)?.ok_or_else(|| {
100 HandlerError::new(
101 "dex_sdk::HandlerError",
102 format!("attribute {} is missing", self.name),
103 )
104 })
105 }
106
107 pub fn set(&self, context: &mut Context, value: T) -> HandlerResult<()>
113 where
114 T: Value,
115 {
116 context.set_attribute(self, value)
117 }
118
119 pub fn delete(&self, context: &mut Context) -> HandlerResult<()> {
123 context.delete_attribute(self)
124 }
125
126 pub fn lock(&self) -> AttributeLock {
128 AttributeLock {
129 attribute: self.name.clone(),
130 instance: None,
131 }
132 }
133
134 pub(crate) fn name(&self) -> &str {
135 &self.name
136 }
137
138 pub(crate) fn index(&self) -> Option<&AttributeIndex> {
139 self.index.as_ref()
140 }
141
142 pub(crate) fn sync_config(&self) -> Option<AttributeSyncConfig> {
143 self.sync_to_attribute_store
144 .then_some(AttributeSyncConfig { enabled: true })
145 }
146
147 pub(crate) fn is_sync_to_attribute_store(&self) -> bool {
148 self.sync_to_attribute_store
149 }
150}
151
152impl<T> Clone for Attribute<T> {
153 fn clone(&self) -> Self {
154 Self {
155 name: self.name.clone(),
156 index: self.index.clone(),
157 sync_to_attribute_store: self.sync_to_attribute_store,
158 marker: PhantomData,
159 }
160 }
161}
162
163pub struct AttributeMap<T> {
169 name: String,
170 index: Option<AttributeIndex>,
171 sync_to_attribute_store: bool,
172 marker: PhantomData<fn() -> T>,
173}
174
175impl<T> AttributeMap<T> {
176 pub fn new(name: impl Into<String>) -> Self {
180 Self {
181 name: name.into(),
182 index: None,
183 sync_to_attribute_store: false,
184 marker: PhantomData,
185 }
186 }
187
188 pub fn indexed(mut self, index: AttributeIndex) -> Self {
190 self.index = Some(index);
191 self
192 }
193
194 #[must_use]
200 pub fn sync_to_attribute_store(mut self) -> Self {
201 self.sync_to_attribute_store = true;
202 self
203 }
204
205 pub fn load(&self, instance: impl Into<String>) -> AttributeMapLoad {
210 AttributeMapLoad {
211 name: self.name.clone(),
212 instance: Some(instance.into()),
213 }
214 }
215
216 pub fn get(&self, context: &Context, instance: &str) -> HandlerResult<Option<T>>
222 where
223 T: Value,
224 {
225 context.get_attribute_map(self, instance)
226 }
227
228 pub fn get_required(&self, context: &Context, instance: &str) -> HandlerResult<T>
234 where
235 T: Value,
236 {
237 self.get(context, instance)?.ok_or_else(|| {
238 HandlerError::new(
239 "dex_sdk::HandlerError",
240 format!("attribute {}[{instance}] is missing", self.name),
241 )
242 })
243 }
244
245 pub fn set(&self, context: &mut Context, instance: &str, value: T) -> HandlerResult<()>
251 where
252 T: Value,
253 {
254 context.set_attribute_map(self, instance, value)
255 }
256
257 pub fn delete(&self, context: &mut Context, instance: &str) -> HandlerResult<()> {
259 context.delete_attribute_map(self, instance)
260 }
261
262 pub fn map_size(&self, context: &Context) -> HandlerResult<usize> {
264 Ok(self.all_instance_keys(context)?.len())
265 }
266
267 pub fn all_instance_keys(&self, context: &Context) -> HandlerResult<Vec<String>> {
269 context.attribute_map_keys(self)
270 }
271
272 pub fn lock(&self, instance: impl Into<String>) -> AttributeLock {
274 let instance = instance.into();
275 crate::registry::assert_map_instance(&instance);
276 AttributeLock {
277 attribute: self.name.clone(),
278 instance: Some(instance),
279 }
280 }
281
282 pub(crate) fn name(&self) -> &str {
283 &self.name
284 }
285
286 pub(crate) fn index(&self) -> Option<&AttributeIndex> {
287 self.index.as_ref()
288 }
289
290 pub(crate) fn sync_config(&self) -> Option<AttributeSyncConfig> {
291 self.sync_to_attribute_store
292 .then_some(AttributeSyncConfig { enabled: true })
293 }
294
295 pub(crate) fn is_sync_to_attribute_store(&self) -> bool {
296 self.sync_to_attribute_store
297 }
298}
299
300impl<T> Clone for AttributeMap<T> {
301 fn clone(&self) -> Self {
302 Self {
303 name: self.name.clone(),
304 index: self.index.clone(),
305 sync_to_attribute_store: self.sync_to_attribute_store,
306 marker: PhantomData,
307 }
308 }
309}
310
311#[derive(Clone, Debug, Eq, PartialEq)]
312pub struct AttributeIndex {
317 kind: AttributeIndexKind,
318 key: Option<String>,
319}
320
321impl AttributeIndex {
322 fn new(kind: AttributeIndexKind) -> Self {
323 Self { kind, key: None }
324 }
325
326 pub fn keyword() -> Self {
328 Self::new(AttributeIndexKind::Keyword)
329 }
330
331 pub fn full_text() -> Self {
333 Self::new(AttributeIndexKind::FullText)
334 }
335
336 pub fn keyword_array() -> Self {
338 Self::new(AttributeIndexKind::KeywordArray)
339 }
340
341 pub fn int() -> Self {
343 Self::new(AttributeIndexKind::Int)
344 }
345
346 pub fn double() -> Self {
348 Self::new(AttributeIndexKind::Double)
349 }
350
351 pub fn bool() -> Self {
353 Self::new(AttributeIndexKind::Bool)
354 }
355
356 pub fn date_time() -> Self {
358 Self::new(AttributeIndexKind::DateTime)
359 }
360
361 pub fn with_key(mut self, key: impl Into<String>) -> Self {
363 self.key = Some(key.into());
364 self
365 }
366}
367
368#[derive(Clone, Copy, Debug, Eq, PartialEq)]
369pub(crate) enum AttributeIndexKind {
370 Keyword,
371 FullText,
372 KeywordArray,
373 Int,
374 Double,
375 Bool,
376 DateTime,
377}
378
379#[derive(Clone, Debug, Eq, PartialEq)]
380pub struct AttributeLock {
385 attribute: String,
386 instance: Option<String>,
387}
388
389impl AttributeIndex {
390 pub(crate) fn proto_config(&self, dynamic: bool) -> IndexConfig {
391 IndexConfig {
392 enable: true,
393 r#type: self.kind.proto_value(),
394 index_key: self
395 .key
396 .clone()
397 .or_else(|| dynamic.then(String::new))
398 .unwrap_or_default(),
399 }
400 }
401
402 pub(crate) fn key(&self) -> Option<&str> {
403 self.key.as_deref()
404 }
405
406 pub(crate) fn proto_value(&self) -> i32 {
407 self.kind.proto_value()
408 }
409}
410
411impl AttributeIndexKind {
412 pub(crate) fn proto_value(self) -> i32 {
413 use dex_protocol::dex::IndexType;
414
415 match self {
416 Self::Keyword => IndexType::Keyword.into(),
417 Self::FullText => IndexType::Text.into(),
418 Self::KeywordArray => IndexType::KeywordArray.into(),
419 Self::Int => IndexType::Int.into(),
420 Self::Double => IndexType::Double.into(),
421 Self::Bool => IndexType::Bool.into(),
422 Self::DateTime => IndexType::Datetime.into(),
423 }
424 }
425}
426
427impl AttributeLock {
428 pub(crate) fn physical_name(&self) -> String {
429 match self.instance.as_deref() {
430 Some(instance) => crate::registry::physical_name(&self.attribute, instance),
431 None => self.attribute.clone(),
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::{Attribute, AttributeMap};
439
440 #[test]
441 fn sync_configuration_is_opt_in_and_survives_clone() {
442 let plain = Attribute::<String>::new("plain");
443 let synced = Attribute::<String>::new("synced").sync_to_attribute_store();
444 let synced_map = AttributeMap::<String>::new("synced_map").sync_to_attribute_store();
445
446 assert!(plain.sync_config().is_none());
447 assert_eq!(
448 synced.sync_config().map(|config| config.enabled),
449 Some(true)
450 );
451 assert_eq!(
452 synced_map
453 .clone()
454 .sync_config()
455 .map(|config| config.enabled),
456 Some(true)
457 );
458 }
459}