|
1 | 1 | use crate::traits::ReadConversionError;
|
2 | 2 | use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue};
|
3 |
| -use std::collections::HashMap; |
| 3 | +use std::{ |
| 4 | + collections::{BTreeMap, HashMap}, |
| 5 | + str::FromStr, |
| 6 | +}; |
4 | 7 |
|
5 | 8 | // FIXME: Clean this up
|
6 | 9 | //#[skip_serializing_none]
|
@@ -398,6 +401,84 @@ impl From<AttributeValue> for TableAttribute {
|
398 | 401 | }
|
399 | 402 | }
|
400 | 403 |
|
| 404 | +impl<K, V> From<HashMap<K, V>> for TableAttribute |
| 405 | +where |
| 406 | + K: ToString, |
| 407 | + V: Into<TableAttribute>, |
| 408 | +{ |
| 409 | + fn from(map: HashMap<K, V>) -> Self { |
| 410 | + TableAttribute::Map( |
| 411 | + map.into_iter() |
| 412 | + .map(|(k, v)| (k.to_string(), v.into())) |
| 413 | + .collect(), |
| 414 | + ) |
| 415 | + } |
| 416 | +} |
| 417 | + |
| 418 | +impl<K, V> TryFromTableAttr for HashMap<K, V> |
| 419 | +where |
| 420 | + K: FromStr + std::hash::Hash + std::cmp::Eq, |
| 421 | + V: TryFromTableAttr, |
| 422 | +{ |
| 423 | + fn try_from_table_attr(value: TableAttribute) -> Result<Self, ReadConversionError> { |
| 424 | + let TableAttribute::Map(map) = value else { |
| 425 | + return Err(ReadConversionError::ConversionFailed( |
| 426 | + std::any::type_name::<Self>().to_string(), |
| 427 | + )); |
| 428 | + }; |
| 429 | + |
| 430 | + map.into_iter() |
| 431 | + .map(|(k, v)| { |
| 432 | + let k = k.parse().map_err(|_| { |
| 433 | + ReadConversionError::ConversionFailed(std::any::type_name::<Self>().to_string()) |
| 434 | + })?; |
| 435 | + let v = V::try_from_table_attr(v)?; |
| 436 | + |
| 437 | + Ok((k, v)) |
| 438 | + }) |
| 439 | + .collect() |
| 440 | + } |
| 441 | +} |
| 442 | + |
| 443 | +impl<K, V> From<BTreeMap<K, V>> for TableAttribute |
| 444 | +where |
| 445 | + K: ToString, |
| 446 | + V: Into<TableAttribute>, |
| 447 | +{ |
| 448 | + fn from(map: BTreeMap<K, V>) -> Self { |
| 449 | + TableAttribute::Map( |
| 450 | + map.into_iter() |
| 451 | + .map(|(k, v)| (k.to_string(), v.into())) |
| 452 | + .collect(), |
| 453 | + ) |
| 454 | + } |
| 455 | +} |
| 456 | + |
| 457 | +impl<K, V> TryFromTableAttr for BTreeMap<K, V> |
| 458 | +where |
| 459 | + K: FromStr + std::cmp::Ord, |
| 460 | + V: TryFromTableAttr, |
| 461 | +{ |
| 462 | + fn try_from_table_attr(value: TableAttribute) -> Result<Self, ReadConversionError> { |
| 463 | + let TableAttribute::Map(map) = value else { |
| 464 | + return Err(ReadConversionError::ConversionFailed( |
| 465 | + std::any::type_name::<Self>().to_string(), |
| 466 | + )); |
| 467 | + }; |
| 468 | + |
| 469 | + map.into_iter() |
| 470 | + .map(|(k, v)| { |
| 471 | + let k = k.parse().map_err(|_| { |
| 472 | + ReadConversionError::ConversionFailed(std::any::type_name::<Self>().to_string()) |
| 473 | + })?; |
| 474 | + let v = V::try_from_table_attr(v)?; |
| 475 | + |
| 476 | + Ok((k, v)) |
| 477 | + }) |
| 478 | + .collect() |
| 479 | + } |
| 480 | +} |
| 481 | + |
401 | 482 | #[cfg(test)]
|
402 | 483 | mod test {
|
403 | 484 | use super::*;
|
@@ -430,6 +511,38 @@ mod test {
|
430 | 511 | }
|
431 | 512 | }
|
432 | 513 |
|
| 514 | + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 515 | + enum MapKeys { |
| 516 | + A, |
| 517 | + B, |
| 518 | + C, |
| 519 | + } |
| 520 | + |
| 521 | + impl std::fmt::Display for MapKeys { |
| 522 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 523 | + let c = match self { |
| 524 | + MapKeys::A => "A", |
| 525 | + MapKeys::B => "B", |
| 526 | + MapKeys::C => "C", |
| 527 | + }; |
| 528 | + |
| 529 | + write!(f, "{c}") |
| 530 | + } |
| 531 | + } |
| 532 | + |
| 533 | + impl FromStr for MapKeys { |
| 534 | + type Err = (); |
| 535 | + |
| 536 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 537 | + match s { |
| 538 | + "A" => Ok(MapKeys::A), |
| 539 | + "B" => Ok(MapKeys::B), |
| 540 | + "C" => Ok(MapKeys::C), |
| 541 | + _ => Err(()), |
| 542 | + } |
| 543 | + } |
| 544 | + } |
| 545 | + |
433 | 546 | #[test]
|
434 | 547 | fn test_to_and_from_list() {
|
435 | 548 | let test_vec = vec![
|
@@ -503,4 +616,50 @@ mod test {
|
503 | 616 |
|
504 | 617 | assert_eq!(original, test_vec);
|
505 | 618 | }
|
| 619 | + |
| 620 | + #[test] |
| 621 | + fn test_hashmap() { |
| 622 | + let map = [ |
| 623 | + (MapKeys::A, "Something in A".to_string()), |
| 624 | + (MapKeys::A, "Something in B".to_string()), |
| 625 | + (MapKeys::A, "Something in C".to_string()), |
| 626 | + ] |
| 627 | + .into_iter() |
| 628 | + .collect::<HashMap<_, _>>(); |
| 629 | + |
| 630 | + let table_attribute = TableAttribute::from(map.clone()); |
| 631 | + |
| 632 | + assert!(matches!( |
| 633 | + &table_attribute, |
| 634 | + TableAttribute::Map(x) |
| 635 | + if x.len() == map.len() |
| 636 | + )); |
| 637 | + |
| 638 | + let original = HashMap::<MapKeys, String>::try_from_table_attr(table_attribute).unwrap(); |
| 639 | + |
| 640 | + assert_eq!(original, map); |
| 641 | + } |
| 642 | + |
| 643 | + #[test] |
| 644 | + fn test_btreemap() { |
| 645 | + let map = [ |
| 646 | + (MapKeys::A, "Something in A".to_string()), |
| 647 | + (MapKeys::A, "Something in B".to_string()), |
| 648 | + (MapKeys::A, "Something in C".to_string()), |
| 649 | + ] |
| 650 | + .into_iter() |
| 651 | + .collect::<BTreeMap<_, _>>(); |
| 652 | + |
| 653 | + let table_attribute = TableAttribute::from(map.clone()); |
| 654 | + |
| 655 | + assert!(matches!( |
| 656 | + &table_attribute, |
| 657 | + TableAttribute::Map(x) |
| 658 | + if x.len() == map.len() |
| 659 | + )); |
| 660 | + |
| 661 | + let original = BTreeMap::<MapKeys, String>::try_from_table_attr(table_attribute).unwrap(); |
| 662 | + |
| 663 | + assert_eq!(original, map); |
| 664 | + } |
506 | 665 | }
|
0 commit comments