1use std::collections::{HashMap, HashSet};
10use std::sync::LazyLock;
11
12use dex_protocol::dex::{GetAttributesRequest, Kv, Value as ProtoValue};
13
14use super::{Client, invalid, map_physical_name};
15use crate::persistence::PersistenceDefinition;
16use crate::sdk_error::{FlowTargetRequirement, ServiceError};
17use crate::{Attribute, AttributeMapInstance, SdkError, SdkResult, Value, value_mapper};
18
19const GET_ATTRIBUTES_OPERATION: &str = "get_attributes";
20
21#[diagnostic::on_unimplemented(
41 message = "`{Self}` is not an Attribute read",
42 label = "pass an Attribute or `AttributeMap::instance(key)`",
43 note = "`AttributeMap::load` selects an instance for a handler Context, not for `Client::get_attributes`"
44)]
45pub trait AttributeRead: sealed::SealedAttributeRead {
46 type Value: Value;
48
49 #[doc(hidden)]
50 fn attribute_read_target(&self) -> sealed::AttributeReadTarget;
51}
52
53#[diagnostic::on_unimplemented(
64 message = "`{Self}` is not a tuple of 1 to 12 Attribute reads",
65 label = "pass a tuple such as `(&ORDER_STATUS, &NOTES.instance(\"n1\"))`",
66 note = "use a one-element tuple such as `(&ORDER_STATUS,)` to read one Attribute"
67)]
68pub trait AttributeReads: sealed::SealedAttributeReads {
69 type Values;
71
72 #[doc(hidden)]
73 fn attribute_read_targets(&self) -> Vec<sealed::AttributeReadTarget>;
74
75 #[doc(hidden)]
76 fn decode_attribute_values(
77 decoder: &mut sealed::AttributeValueDecoder,
78 ) -> SdkResult<Self::Values>;
79}
80
81mod sealed {
82 use super::*;
83
84 pub trait SealedAttributeRead {}
85
86 pub trait SealedAttributeReads {}
87
88 pub struct AttributeReadTarget {
89 pub(super) definition: PersistenceDefinition,
90 pub(super) instance: Option<String>,
91 }
92
93 pub struct AttributeValueDecoder {
94 pub(super) flow_id: String,
95 pub(super) values: std::vec::IntoIter<(String, Option<ProtoValue>)>,
96 }
97
98 impl AttributeValueDecoder {
99 pub(super) fn decode_next<T: Value>(&mut self) -> SdkResult<Option<T>> {
100 let (key, value) = self
101 .values
102 .next()
103 .expect("the decoder holds one value slot per read");
104 value
105 .map(|value| {
106 value_mapper::decode_without_value_detail(&value).map_err(|error| {
107 error.with_call_context(
108 GET_ATTRIBUTES_OPERATION,
109 &self.flow_id,
110 format!(
111 "get_attributes Flow {:?} Attribute {key:?} value cannot be \
112 decoded as {}",
113 self.flow_id,
114 std::any::type_name::<T>()
115 ),
116 )
117 })
118 })
119 .transpose()
120 }
121 }
122}
123
124impl<T: Value> sealed::SealedAttributeRead for Attribute<T> {}
125
126impl<T: Value> AttributeRead for Attribute<T> {
127 type Value = T;
128
129 fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
130 sealed::AttributeReadTarget {
131 definition: PersistenceDefinition::for_attribute(self),
132 instance: None,
133 }
134 }
135}
136
137impl<T: Value> sealed::SealedAttributeRead for AttributeMapInstance<T> {}
138
139impl<T: Value> AttributeRead for AttributeMapInstance<T> {
140 type Value = T;
141
142 fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
143 sealed::AttributeReadTarget {
144 definition: PersistenceDefinition::for_attribute_map(self.attribute_map()),
145 instance: Some(self.instance().to_string()),
146 }
147 }
148}
149
150impl<Read: AttributeRead> sealed::SealedAttributeRead for &Read {}
151
152#[diagnostic::do_not_recommend]
153impl<Read: AttributeRead> AttributeRead for &Read {
154 type Value = Read::Value;
155
156 fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
157 (**self).attribute_read_target()
158 }
159}
160
161impl<Read: AttributeRead, Init: FnOnce() -> Read> sealed::SealedAttributeRead
162 for LazyLock<Read, Init>
163{
164}
165
166#[diagnostic::do_not_recommend]
167impl<Read: AttributeRead, Init: FnOnce() -> Read> AttributeRead for LazyLock<Read, Init> {
168 type Value = Read::Value;
169
170 fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
171 LazyLock::force(self).attribute_read_target()
172 }
173}
174
175macro_rules! impl_attribute_reads {
176 ($(($index:tt, $read:ident)),+) => {
177 impl<$($read: AttributeRead),+> sealed::SealedAttributeReads for ($($read,)+) {}
178
179 #[diagnostic::do_not_recommend]
180 impl<$($read: AttributeRead),+> AttributeReads for ($($read,)+) {
181 type Values = ($(Option<$read::Value>,)+);
182
183 fn attribute_read_targets(&self) -> Vec<sealed::AttributeReadTarget> {
184 vec![$(self.$index.attribute_read_target()),+]
185 }
186
187 fn decode_attribute_values(
188 decoder: &mut sealed::AttributeValueDecoder,
189 ) -> SdkResult<Self::Values> {
190 Ok(($(decoder.decode_next::<$read::Value>()?,)+))
191 }
192 }
193 };
194}
195
196impl_attribute_reads!((0, Read0));
197impl_attribute_reads!((0, Read0), (1, Read1));
198impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2));
199impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2), (3, Read3));
200impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2), (3, Read3), (4, Read4));
201impl_attribute_reads!(
202 (0, Read0),
203 (1, Read1),
204 (2, Read2),
205 (3, Read3),
206 (4, Read4),
207 (5, Read5)
208);
209impl_attribute_reads!(
210 (0, Read0),
211 (1, Read1),
212 (2, Read2),
213 (3, Read3),
214 (4, Read4),
215 (5, Read5),
216 (6, Read6)
217);
218impl_attribute_reads!(
219 (0, Read0),
220 (1, Read1),
221 (2, Read2),
222 (3, Read3),
223 (4, Read4),
224 (5, Read5),
225 (6, Read6),
226 (7, Read7)
227);
228impl_attribute_reads!(
229 (0, Read0),
230 (1, Read1),
231 (2, Read2),
232 (3, Read3),
233 (4, Read4),
234 (5, Read5),
235 (6, Read6),
236 (7, Read7),
237 (8, Read8)
238);
239impl_attribute_reads!(
240 (0, Read0),
241 (1, Read1),
242 (2, Read2),
243 (3, Read3),
244 (4, Read4),
245 (5, Read5),
246 (6, Read6),
247 (7, Read7),
248 (8, Read8),
249 (9, Read9)
250);
251impl_attribute_reads!(
252 (0, Read0),
253 (1, Read1),
254 (2, Read2),
255 (3, Read3),
256 (4, Read4),
257 (5, Read5),
258 (6, Read6),
259 (7, Read7),
260 (8, Read8),
261 (9, Read9),
262 (10, Read10)
263);
264impl_attribute_reads!(
265 (0, Read0),
266 (1, Read1),
267 (2, Read2),
268 (3, Read3),
269 (4, Read4),
270 (5, Read5),
271 (6, Read6),
272 (7, Read7),
273 (8, Read8),
274 (9, Read9),
275 (10, Read10),
276 (11, Read11)
277);
278
279impl Client {
280 pub fn get_attributes<Reads: AttributeReads>(
385 &self,
386 flow_id: &str,
387 reads: Reads,
388 ) -> SdkResult<Reads::Values> {
389 let keys = self.validate_attribute_reads(flow_id, reads.attribute_read_targets())?;
390 let values = self.fetch_attribute_values(flow_id, &keys)?;
391 let mut decoder = sealed::AttributeValueDecoder {
392 flow_id: flow_id.to_string(),
393 values: keys.into_iter().zip(values).collect::<Vec<_>>().into_iter(),
394 };
395 Reads::decode_attribute_values(&mut decoder)
396 }
397
398 fn validate_attribute_reads(
400 &self,
401 flow_id: &str,
402 targets: Vec<sealed::AttributeReadTarget>,
403 ) -> SdkResult<Vec<String>> {
404 if flow_id.trim().is_empty() {
405 return Err(invalid("get_attributes requires a non-blank Flow ID"));
406 }
407 let mut keys = Vec::with_capacity(targets.len());
408 let mut seen_keys = HashSet::with_capacity(targets.len());
409 for target in targets {
410 let name = &target.definition.name;
411 let read_context = || format!("get_attributes Flow {flow_id:?} Attribute {name:?}");
412 self.registry
413 .require_registered_attribute(&target.definition)
414 .map_err(|error| {
415 error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, read_context())
416 })?;
417 let key = match &target.instance {
418 Some(instance) => map_physical_name(name, instance).map_err(|error| {
419 error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, read_context())
420 })?,
421 None => name.clone(),
422 };
423 if !seen_keys.insert(key.clone()) {
424 return Err(invalid(format!(
425 "get_attributes Flow {flow_id:?} has duplicate Attribute read {key:?}"
426 )));
427 }
428 keys.push(key);
429 }
430 Ok(keys)
431 }
432
433 fn fetch_attribute_values(
435 &self,
436 flow_id: &str,
437 keys: &[String],
438 ) -> SdkResult<Vec<Option<ProtoValue>>> {
439 let call_context = format!("get_attributes Flow {flow_id:?} Attributes {keys:?}");
440 let with_context = |error: SdkError| {
441 error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, call_context.clone())
442 };
443 let mut service = self.service.clone();
444 let response = self
445 .runtime
446 .block_on(service.get_attributes(GetAttributesRequest {
447 flow_id: flow_id.to_string(),
448 run_id: String::new(),
449 keys: keys.to_vec(),
450 all_keys: false,
451 }))
452 .map_err(|status| {
453 with_context(SdkError::from_status(
454 status,
455 GET_ATTRIBUTES_OPERATION,
456 Some(flow_id),
457 FlowTargetRequirement::Existing,
458 ))
459 })?
460 .into_inner();
461 let response_values =
462 index_response_values(keys, response.attributes).map_err(with_context)?;
463 let (set_keys, set_values): (Vec<String>, Vec<ProtoValue>) =
464 response_values.into_iter().unzip();
465 let hydrated_values = self
466 .runtime
467 .block_on(self.hydrator.hydrate_all(flow_id, set_values))
468 .map_err(|error| {
469 error
470 .with_flow_target_requirement(FlowTargetRequirement::Existing)
471 .with_call_context(
472 GET_ATTRIBUTES_OPERATION,
473 flow_id,
474 format!("{call_context} LoadBlobs"),
475 )
476 })?;
477 let mut hydrated_values_by_key = set_keys
478 .into_iter()
479 .zip(hydrated_values)
480 .collect::<HashMap<_, _>>();
481 Ok(keys
482 .iter()
483 .map(|key| hydrated_values_by_key.remove(key))
484 .collect())
485 }
486}
487
488fn index_response_values(
491 keys: &[String],
492 attributes: Vec<Kv>,
493) -> SdkResult<HashMap<String, ProtoValue>> {
494 let requested_keys = keys.iter().map(String::as_str).collect::<HashSet<_>>();
495 let mut response_values = HashMap::with_capacity(attributes.len());
496 for attribute in attributes {
497 let key = attribute.key;
498 let response_error = |problem: &str| SdkError::Service {
499 service: ServiceError::local(
500 GET_ATTRIBUTES_OPERATION,
501 format!("GetAttributes returned {problem} {key:?}"),
502 ),
503 };
504 if !requested_keys.contains(key.as_str()) {
505 return Err(response_error("unexpected Attribute"));
506 }
507 if response_values.contains_key(&key) {
508 return Err(response_error("duplicate Attribute"));
509 }
510 let Some(value) = attribute.value else {
511 return Err(response_error("no value for Attribute"));
512 };
513 response_values.insert(key, value);
514 }
515 Ok(response_values)
516}
517
518#[cfg(test)]
519mod tests {
520 use dex_protocol::dex::{EncodedObject, Kv, Value as ProtoValue, value};
521
522 use super::index_response_values;
523 use super::sealed::AttributeValueDecoder;
524 use crate::SdkError;
525
526 #[derive(Debug, serde::Deserialize, serde::Serialize)]
527 #[serde(deny_unknown_fields)]
528 struct Credential {
529 token: String,
530 }
531
532 #[test]
533 fn decode_errors_keep_value_free_detail_and_omit_the_stored_value() {
534 let mut decoder = decoder(vec![
535 string_value("sekrit"),
536 json_value(br#"{}"#),
537 json_value(br#"{"token":"sekrit","extra":"sekrit"}"#),
538 json_value(br#"{"token":"sekrit""#),
539 ]);
540
541 let invalid_type = decode_error(decoder.decode_next::<i64>());
542 assert!(invalid_type.contains("\"order-1\""), "{invalid_type}");
543 assert!(invalid_type.contains("\"key-0\""), "{invalid_type}");
544 assert!(invalid_type.contains("i64"), "{invalid_type}");
545 assert!(invalid_type.contains("data error"), "{invalid_type}");
546
547 let missing_field = decode_error(decoder.decode_next::<Credential>());
548 assert!(missing_field.contains("\"key-1\""), "{missing_field}");
549 assert!(
550 missing_field.contains("missing field `token`"),
551 "{missing_field}"
552 );
553
554 let unknown_field = decode_error(decoder.decode_next::<Credential>());
555 assert!(
556 unknown_field.contains("data error: unknown field"),
557 "{unknown_field}"
558 );
559 assert!(!unknown_field.contains("expected"), "{unknown_field}");
560 assert!(!unknown_field.contains("extra"), "{unknown_field}");
561
562 let syntax = decode_error(decoder.decode_next::<Credential>());
563 assert!(syntax.contains("unexpected end of input"), "{syntax}");
564 assert!(syntax.contains("at line 1 column"), "{syntax}");
565
566 for message in [invalid_type, missing_field, unknown_field, syntax] {
567 assert!(!message.contains("sekrit"), "{message}");
568 }
569 }
570
571 #[test]
572 fn response_values_are_indexed_by_requested_key() {
573 let keys = vec!["status".to_string(), "notes/n1".to_string()];
574
575 let values = index_response_values(&keys, vec![kv("notes/n1", Some(string_value("note")))])
576 .expect("a subset of the requested keys is valid");
577
578 assert_eq!(1, values.len());
579 assert_eq!(Some(&string_value("note")), values.get("notes/n1"));
580 }
581
582 #[test]
583 fn response_values_reject_unexpected_duplicate_and_valueless_keys() {
584 let keys = vec!["status".to_string()];
585 for (attributes, expected) in [
586 (
587 vec![kv("other", Some(string_value("x")))],
588 "GetAttributes returned unexpected Attribute \"other\"",
589 ),
590 (
591 vec![
592 kv("status", Some(string_value("x"))),
593 kv("status", Some(string_value("y"))),
594 ],
595 "GetAttributes returned duplicate Attribute \"status\"",
596 ),
597 (
598 vec![kv("status", None)],
599 "GetAttributes returned no value for Attribute \"status\"",
600 ),
601 ] {
602 let error = index_response_values(&keys, attributes)
603 .expect_err("an invalid response is rejected");
604 assert!(matches!(error, SdkError::Service { .. }));
605 assert_eq!(expected, error.to_string());
606 }
607 }
608
609 fn decoder(values: Vec<ProtoValue>) -> AttributeValueDecoder {
610 AttributeValueDecoder {
611 flow_id: "order-1".to_string(),
612 values: values
613 .into_iter()
614 .enumerate()
615 .map(|(index, value)| (format!("key-{index}"), Some(value)))
616 .collect::<Vec<_>>()
617 .into_iter(),
618 }
619 }
620
621 fn decode_error<T: std::fmt::Debug>(result: crate::SdkResult<Option<T>>) -> String {
622 let error = result.expect_err("the value does not decode");
623 assert!(matches!(error, SdkError::ValueMapping { .. }), "{error:?}");
624 error.to_string()
625 }
626
627 fn kv(key: &str, value: Option<ProtoValue>) -> Kv {
628 Kv {
629 key: key.to_string(),
630 value,
631 }
632 }
633
634 fn string_value(text: &str) -> ProtoValue {
635 ProtoValue {
636 kind: Some(value::Kind::StringValue(text.to_string())),
637 }
638 }
639
640 fn json_value(payload: &[u8]) -> ProtoValue {
641 ProtoValue {
642 kind: Some(value::Kind::ObjValue(EncodedObject {
643 encoding: "json".to_string(),
644 payload: payload.to_vec(),
645 })),
646 }
647 }
648}