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 self.require_server_protocol(GET_ATTRIBUTES_OPERATION)?;
444 let mut service = self.service.clone();
445 let response = self
446 .runtime
447 .block_on(service.get_attributes(GetAttributesRequest {
448 flow_id: flow_id.to_string(),
449 run_id: String::new(),
450 keys: keys.to_vec(),
451 all_keys: false,
452 }))
453 .map_err(|status| {
454 with_context(SdkError::from_status(
455 status,
456 GET_ATTRIBUTES_OPERATION,
457 Some(flow_id),
458 FlowTargetRequirement::Existing,
459 ))
460 })?
461 .into_inner();
462 let response_values =
463 index_response_values(keys, response.attributes).map_err(with_context)?;
464 let (set_keys, set_values): (Vec<String>, Vec<ProtoValue>) =
465 response_values.into_iter().unzip();
466 let hydrated_values = self
467 .runtime
468 .block_on(self.hydrator.hydrate_all(flow_id, set_values))
469 .map_err(|error| {
470 error
471 .with_flow_target_requirement(FlowTargetRequirement::Existing)
472 .with_call_context(
473 GET_ATTRIBUTES_OPERATION,
474 flow_id,
475 format!("{call_context} LoadBlobs"),
476 )
477 })?;
478 let mut hydrated_values_by_key = set_keys
479 .into_iter()
480 .zip(hydrated_values)
481 .collect::<HashMap<_, _>>();
482 Ok(keys
483 .iter()
484 .map(|key| hydrated_values_by_key.remove(key))
485 .collect())
486 }
487}
488
489fn index_response_values(
492 keys: &[String],
493 attributes: Vec<Kv>,
494) -> SdkResult<HashMap<String, ProtoValue>> {
495 let requested_keys = keys.iter().map(String::as_str).collect::<HashSet<_>>();
496 let mut response_values = HashMap::with_capacity(attributes.len());
497 for attribute in attributes {
498 let key = attribute.key;
499 let response_error = |problem: &str| SdkError::Service {
500 service: ServiceError::local(
501 GET_ATTRIBUTES_OPERATION,
502 format!("GetAttributes returned {problem} {key:?}"),
503 ),
504 };
505 if !requested_keys.contains(key.as_str()) {
506 return Err(response_error("unexpected Attribute"));
507 }
508 if response_values.contains_key(&key) {
509 return Err(response_error("duplicate Attribute"));
510 }
511 let Some(value) = attribute.value else {
512 return Err(response_error("no value for Attribute"));
513 };
514 response_values.insert(key, value);
515 }
516 Ok(response_values)
517}
518
519#[cfg(test)]
520mod tests {
521 use dex_protocol::dex::{EncodedObject, Kv, Value as ProtoValue, value};
522
523 use super::index_response_values;
524 use super::sealed::AttributeValueDecoder;
525 use crate::SdkError;
526
527 #[derive(Debug, serde::Deserialize, serde::Serialize)]
528 #[serde(deny_unknown_fields)]
529 struct Credential {
530 token: String,
531 }
532
533 #[test]
534 fn decode_errors_keep_value_free_detail_and_omit_the_stored_value() {
535 let mut decoder = decoder(vec![
536 string_value("sekrit"),
537 json_value(br#"{}"#),
538 json_value(br#"{"token":"sekrit","extra":"sekrit"}"#),
539 json_value(br#"{"token":"sekrit""#),
540 ]);
541
542 let invalid_type = decode_error(decoder.decode_next::<i64>());
543 assert!(invalid_type.contains("\"order-1\""), "{invalid_type}");
544 assert!(invalid_type.contains("\"key-0\""), "{invalid_type}");
545 assert!(invalid_type.contains("i64"), "{invalid_type}");
546 assert!(invalid_type.contains("data error"), "{invalid_type}");
547
548 let missing_field = decode_error(decoder.decode_next::<Credential>());
549 assert!(missing_field.contains("\"key-1\""), "{missing_field}");
550 assert!(
551 missing_field.contains("missing field `token`"),
552 "{missing_field}"
553 );
554
555 let unknown_field = decode_error(decoder.decode_next::<Credential>());
556 assert!(
557 unknown_field.contains("data error: unknown field"),
558 "{unknown_field}"
559 );
560 assert!(!unknown_field.contains("expected"), "{unknown_field}");
561 assert!(!unknown_field.contains("extra"), "{unknown_field}");
562
563 let syntax = decode_error(decoder.decode_next::<Credential>());
564 assert!(syntax.contains("unexpected end of input"), "{syntax}");
565 assert!(syntax.contains("at line 1 column"), "{syntax}");
566
567 for message in [invalid_type, missing_field, unknown_field, syntax] {
568 assert!(!message.contains("sekrit"), "{message}");
569 }
570 }
571
572 #[test]
573 fn response_values_are_indexed_by_requested_key() {
574 let keys = vec!["status".to_string(), "notes/n1".to_string()];
575
576 let values = index_response_values(&keys, vec![kv("notes/n1", Some(string_value("note")))])
577 .expect("a subset of the requested keys is valid");
578
579 assert_eq!(1, values.len());
580 assert_eq!(Some(&string_value("note")), values.get("notes/n1"));
581 }
582
583 #[test]
584 fn response_values_reject_unexpected_duplicate_and_valueless_keys() {
585 let keys = vec!["status".to_string()];
586 for (attributes, expected) in [
587 (
588 vec![kv("other", Some(string_value("x")))],
589 "GetAttributes returned unexpected Attribute \"other\"",
590 ),
591 (
592 vec![
593 kv("status", Some(string_value("x"))),
594 kv("status", Some(string_value("y"))),
595 ],
596 "GetAttributes returned duplicate Attribute \"status\"",
597 ),
598 (
599 vec![kv("status", None)],
600 "GetAttributes returned no value for Attribute \"status\"",
601 ),
602 ] {
603 let error = index_response_values(&keys, attributes)
604 .expect_err("an invalid response is rejected");
605 assert!(matches!(error, SdkError::Service { .. }));
606 assert_eq!(expected, error.to_string());
607 }
608 }
609
610 fn decoder(values: Vec<ProtoValue>) -> AttributeValueDecoder {
611 AttributeValueDecoder {
612 flow_id: "order-1".to_string(),
613 values: values
614 .into_iter()
615 .enumerate()
616 .map(|(index, value)| (format!("key-{index}"), Some(value)))
617 .collect::<Vec<_>>()
618 .into_iter(),
619 }
620 }
621
622 fn decode_error<T: std::fmt::Debug>(result: crate::SdkResult<Option<T>>) -> String {
623 let error = result.expect_err("the value does not decode");
624 assert!(matches!(error, SdkError::ValueMapping { .. }), "{error:?}");
625 error.to_string()
626 }
627
628 fn kv(key: &str, value: Option<ProtoValue>) -> Kv {
629 Kv {
630 key: key.to_string(),
631 value,
632 }
633 }
634
635 fn string_value(text: &str) -> ProtoValue {
636 ProtoValue {
637 kind: Some(value::Kind::StringValue(text.to_string())),
638 }
639 }
640
641 fn json_value(payload: &[u8]) -> ProtoValue {
642 ProtoValue {
643 kind: Some(value::Kind::ObjValue(EncodedObject {
644 encoding: "json".to_string(),
645 payload: payload.to_vec(),
646 })),
647 }
648 }
649}