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