Initial commit

This commit is contained in:
2024-11-02 13:37:44 +01:00
commit 8880918f1a
19 changed files with 4973 additions and 0 deletions

110
src/error.rs Normal file
View File

@@ -0,0 +1,110 @@
use std::{fmt, net, time::Duration};
use crate::tags::{ParseTagError, ParseTagsError};
#[derive(Debug)]
pub enum Error {
UnexpectedNoneValue {
entity: String,
},
SdkError(Box<dyn std::error::Error + Send>),
InvalidResponseError {
message: String,
},
MultipleMatches {
entity: String,
},
InvalidTag(ParseTagError),
InvalidTags(ParseTagsError),
RunInstancesEmptyResponse,
InstanceStopExceededMaxWait {
max_wait: Duration,
instance: super::InstanceId,
},
WaitError(Box<dyn std::error::Error + Send>),
RunInstanceNoCapacity,
InvalidTimestampError {
value: String,
message: String,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::InvalidTag(ref inner) => {
write!(f, "{inner}")
}
Self::InvalidTags(ref inner) => {
write!(f, "{inner}")
}
Self::UnexpectedNoneValue { ref entity } => {
write!(f, "entity \"{entity}\" was empty")
}
Self::SdkError(ref e) => write!(f, "sdk error: {e}"),
Self::InvalidResponseError { ref message } => {
write!(f, "invalid api response: {message}")
}
Self::MultipleMatches { ref entity } => {
write!(f, "multiple matches for {entity} found")
}
Self::RunInstancesEmptyResponse => {
write!(f, "empty instance response of RunInstances")
}
Self::InstanceStopExceededMaxWait {
ref max_wait,
ref instance,
} => {
write!(
f,
"instance {instance} did not wait in {} seconds",
max_wait.as_secs()
)
}
Self::WaitError(ref e) => write!(f, "waiter error: {e}"),
Self::RunInstanceNoCapacity => {
write!(f, "no capacity for rnu instance operation")
}
Self::InvalidTimestampError {
ref value,
ref message,
} => {
write!(f, "failed parsing \"{value}\" as timestamp: {message}")
}
}
}
}
impl std::error::Error for Error {}
impl<T: std::error::Error + Send + 'static> From<aws_sdk_ec2::error::SdkError<T>> for Error {
fn from(value: aws_sdk_ec2::error::SdkError<T>) -> Self {
Self::SdkError(Box::new(value))
}
}
impl From<aws_sdk_ec2::waiters::instance_stopped::WaitUntilInstanceStoppedError> for Error {
fn from(value: aws_sdk_ec2::waiters::instance_stopped::WaitUntilInstanceStoppedError) -> Self {
Self::WaitError(Box::new(value))
}
}
impl From<net::AddrParseError> for Error {
fn from(value: net::AddrParseError) -> Self {
Self::InvalidResponseError {
message: value.to_string(),
}
}
}
impl From<ParseTagError> for Error {
fn from(value: ParseTagError) -> Self {
Self::InvalidTag(value)
}
}
impl From<ParseTagsError> for Error {
fn from(value: ParseTagsError) -> Self {
Self::InvalidTags(value)
}
}

9
src/export.rs Normal file
View File

@@ -0,0 +1,9 @@
//! Re-exports selected elements of the EC2 SDK
pub mod ec2 {
#![expect(clippy::module_name_repetitions, reason = "error prefix is necessary")]
pub mod error {
pub use aws_sdk_ec2::error::SdkError;
}
pub use aws_sdk_ec2::types::{Filter, InstanceStateName, InstanceType, Tag};
}

1046
src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

72
src/tags/README.md Normal file
View File

@@ -0,0 +1,72 @@
Type-safe operations on AWS tags.
# Overview
This basic data structure of this module is the [`TagList`] struct. It contains
a list of untyped AWS tags. It implements `TryFrom` and `Into` for the relevant
`Tag` types in the `aws_sdk_*` crates. In itself, that is not too interesting,
as the tag values are still just `String`.
To get typed tags, there is a [`macro@Tags`] macro. Applied to a struct, it adds
methods to create a struct instance for an instance [`TagList`], and turn a
struct instance back into a [`TagList`]:
```rust
use aws::tags::{Tags, TagList, RawTag};
#[Tags]
struct MyTags {
tag1: String,
tag2: bool,
tag3: Option<bool>,
}
let tags = TagList::from_vec(vec![
RawTag::new("tag1".to_owned(), "foo".to_owned()),
RawTag::new("tag2".to_owned(), "true".to_owned()),
]);
let parsed = MyTags::from_tags(tags).unwrap();
assert!(parsed.tag1 == "foo");
assert!(parsed.tag2);
assert!(parsed.tag3.is_none());
```
## Using custom tag types
By default, encoding and decoding of tags is supported for `String` and `bool`
values.
- `String` is encoded as-is
- `bool` is encoded as `true` and `false`
In case you have your own type `T` you want to encode in a tag, there are two
strategies:
- `serde`, which requires `T` to implement `Serialize` and `Deserialize`.
- `manual`, which requires `T` to impelemnt two traits:
- `impl TryFrom<RawTagValue> for T`: How to (fallibly) create a `T` from the
value of an tag
- `impl From<T> for RawTagValue`: How to turn `T` back into the value of a
tag. This cannot fail.
There is a [`macro@Tag`] macro that selects the strategy, which can then be used
in a struct that is using `#[Tags]`:
```rust
use aws::tags::{Tag, Tags};
use serde::{Serialize, Deserialize};
#[Tag(translate = serde)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct MyTag {
foo: String,
bar: bool,
}
#[Tags]
struct MyTags {
foo: MyTag,
}
```

127
src/tags/error.rs Normal file
View File

@@ -0,0 +1,127 @@
#![expect(clippy::module_name_repetitions, reason = "error prefix is necessary")]
use std::{convert::Infallible, fmt};
use super::{RawTagValue, TagKey};
#[derive(Debug, Clone)]
pub enum ParseTagAwsError {
AwsKeyNone,
AwsValueNone { key: TagKey },
}
impl std::error::Error for ParseTagAwsError {}
impl fmt::Display for ParseTagAwsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::AwsKeyNone => write!(f, "aws responded with `None` value for tag key"),
Self::AwsValueNone { ref key } => write!(
f,
"aws responded with `None` value for tag value of tag \"{key}\""
),
}
}
}
#[derive(Debug, Clone)]
pub enum ParseTagValueError {
/// A generic error for type conversions of a tag value to some type `T`
InvalidValue {
value: RawTagValue,
message: String,
},
/// like `InvalidValue`, but specific for `bool`
InvalidBoolValue {
value: RawTagValue,
},
Aws(ParseTagAwsError),
}
impl std::error::Error for ParseTagValueError {}
impl fmt::Display for ParseTagValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::InvalidBoolValue { ref value } => {
write!(f, "invalid tag bool value \"{value}\"")
}
Self::InvalidValue {
ref value,
ref message,
} => write!(f, "invalid tag value \"{value}\": {message}"),
Self::Aws(ref inner) => write!(f, "aws error: {inner}"),
}
}
}
impl From<Infallible> for ParseTagValueError {
fn from(_value: Infallible) -> Self {
unreachable!()
}
}
#[derive(Debug, Clone)]
/// Like [`ParseTagValueError`], but contains potential additional information about the
/// tag *key*.
pub enum ParseTagError {
InvalidTagValue {
key: TagKey,
inner: ParseTagValueError,
},
Aws(ParseTagAwsError),
}
impl std::error::Error for ParseTagError {}
impl fmt::Display for ParseTagError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Aws(ref inner) => write!(f, "aws error: {inner}"),
Self::InvalidTagValue { ref key, ref inner } => {
write!(f, "failed parsing tag \"{key}\": {inner}")
}
}
}
}
/// this is required because `String` can be the inner value of a `Tag`, but is also the input
/// to the `parse()` function. `try_into()` from `String` to `String` returns `Infallible` as
/// the `Err` type.
impl From<Infallible> for ParseTagError {
fn from(_value: Infallible) -> Self {
unreachable!()
}
}
impl From<ParseTagAwsError> for ParseTagError {
fn from(value: ParseTagAwsError) -> Self {
Self::Aws(value)
}
}
#[derive(Debug, Clone)]
/// Errors that can happen when parsing a set of tags.
pub enum ParseTagsError {
/// A required tag was not found
TagNotFound { key: TagKey },
/// A single tag failed to parse
ParseTag(ParseTagError),
}
impl std::error::Error for ParseTagsError {}
impl fmt::Display for ParseTagsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::TagNotFound { ref key } => write!(f, "tag {key} not found in input"),
Self::ParseTag(ref err) => write!(f, "failed parsing tag: {err}"),
}
}
}
impl From<ParseTagError> for ParseTagsError {
fn from(value: ParseTagError) -> Self {
Self::ParseTag(value)
}
}

49
src/tags/helpers.rs Normal file
View File

@@ -0,0 +1,49 @@
macro_rules! impl_string_wrapper {
($name:ident) => {
impl $name {
pub const fn new(value: String) -> Self {
Self(value)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl From<String> for $name {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl From<$name> for String {
fn from(value: $name) -> Self {
value.into_string()
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl PartialEq<str> for $name {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for $name {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
};
}
pub(crate) use impl_string_wrapper;

407
src/tags/mod.rs Normal file
View File

@@ -0,0 +1,407 @@
#![doc = include_str!("README.md")]
use std::fmt::{self, Debug};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
mod error;
mod helpers;
mod predefined_types;
mod svc;
pub use aws_macros::{Tag, Tags};
pub use error::{ParseTagAwsError, ParseTagError, ParseTagValueError, ParseTagsError};
#[derive(Debug, PartialEq, Eq)]
struct InnerTagValue<T>(T)
where
T: Debug + Clone + PartialEq + Eq + Send;
impl<T> From<T> for InnerTagValue<T>
where
T: Debug + Clone + PartialEq + Eq + Send,
{
fn from(value: T) -> Self {
Self(value)
}
}
impl<T> From<InnerTagValue<T>> for String
where
T: Debug + Clone + PartialEq + Eq + Into<RawTagValue> + Send,
{
fn from(value: InnerTagValue<T>) -> Self {
value.0.into().0
}
}
impl<T> PartialEq<T> for InnerTagValue<T>
where
T: Debug + Clone + PartialEq + Eq + Into<RawTagValue> + Send,
{
fn eq(&self, other: &T) -> bool {
&self.0 == other
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct RawTagValue(String);
helpers::impl_string_wrapper!(RawTagValue);
pub struct TranslateSerde;
pub struct TranslateManual;
pub trait Translator<S: ?Sized, T> {
type Error;
fn from_raw_tag(value: RawTagValue) -> Result<T, Self::Error>;
fn into_raw_tag(value: T) -> RawTagValue;
}
pub trait TranslatableSerde: Serialize + DeserializeOwned {}
pub trait TranslatableManual:
TryFrom<RawTagValue, Error: Into<ParseTagValueError>> + Into<RawTagValue>
{
}
pub trait TagValue<V> {
type Error;
type Translator: Translator<Self, V, Error = Self::Error>;
fn from_raw_tag(value: RawTagValue) -> Result<V, Self::Error> {
Self::Translator::from_raw_tag(value)
}
fn into_raw_tag(value: V) -> RawTagValue {
Self::Translator::into_raw_tag(value)
}
}
impl<S, T> Translator<S, T> for TranslateSerde
where
T: TranslatableSerde,
{
type Error = ParseTagValueError;
fn from_raw_tag(value: RawTagValue) -> Result<T, Self::Error> {
serde_json::from_str::<T>(value.as_str()).map_err(|e| ParseTagValueError::InvalidValue {
value,
message: e.to_string(),
})
}
fn into_raw_tag(value: T) -> RawTagValue {
RawTagValue(serde_json::to_string(&value).expect("serialization always succeeds"))
}
}
impl<S, T> Translator<S, T> for TranslateManual
where
T: TranslatableManual,
{
type Error = ParseTagValueError;
fn from_raw_tag(value: RawTagValue) -> Result<T, Self::Error> {
match value.clone().try_into() {
Ok(r) => Ok(r),
Err(e) => Err(ParseTagValueError::InvalidValue {
value,
message: {
let e: ParseTagValueError = e.into();
e.to_string()
},
}),
}
}
fn into_raw_tag(value: T) -> RawTagValue {
value.into()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct RawTag {
key: TagKey,
value: RawTagValue,
}
impl RawTag {
pub fn new(key: impl Into<TagKey>, value: impl Into<RawTagValue>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
pub const fn key(&self) -> &TagKey {
&self.key
}
pub const fn value(&self) -> &RawTagValue {
&self.value
}
}
impl<T> From<Tag<T>> for RawTag
where
T: Debug + Clone + PartialEq + Eq + Send,
T: TagValue<T>,
InnerTagValue<T>: fmt::Display + Into<RawTagValue>,
{
fn from(tag: Tag<T>) -> Self {
Self {
key: tag.key,
value: tag.value.into(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct TagKey(String);
helpers::impl_string_wrapper!(TagKey);
/// A tag generic over it's containing value type.
///
/// There are two ways to construct a `Tag`:
///
/// * You already have a `T`: Just use [`new()`](Self::new())
/// ```rust
/// use aws::tags::Tag;
///
/// let tag = Tag::<bool>::new("foo".to_owned(), true);
/// ```
///
/// * You have something that may be able to be turned into a [`TagValue<T>`]. In that
/// case, either use [`parse()`](Self::parse()) or the `TryInto` implementation.
/// [`parse()`](Self::parse()) accepts an `impl Into<RawTagValue>`, which is implemented
/// for `String`:
///
/// ```rust
/// use aws::tags::Tag;
///
/// let tag = Tag::<bool>::parse("foo".to_owned(), "true".to_owned()).unwrap();
/// ```
///
/// Both the `TryInto` implementation and [`parse()`](Self::parse()) may return a
/// [`ParseTagValueError`] that contains more information about the parse failure.
#[derive(Debug, PartialEq, Eq)]
pub struct Tag<T>
where
T: Debug + Clone + PartialEq + Eq + Send,
T: TagValue<T>,
{
key: TagKey,
value: InnerTagValue<T>,
}
impl<T> TryFrom<RawTag> for Tag<T>
where
T: Clone + PartialEq + Eq + Debug + Send,
T: TagValue<T, Error = ParseTagValueError>,
{
type Error = ParseTagValueError;
fn try_from(tag: RawTag) -> Result<Self, Self::Error> {
Self::parse(tag.key, tag.value)
}
}
impl<T> Tag<T>
where
T: Debug + Clone + PartialEq + Eq + Send,
T: TagValue<T>,
{
pub fn new(key: impl Into<TagKey>, value: T) -> Self {
Self {
key: key.into(),
value: InnerTagValue(value),
}
}
pub fn parse(
key: impl Into<TagKey>,
value: impl Into<String>,
) -> Result<Self, ParseTagValueError>
where
T: TagValue<T, Error = ParseTagValueError>,
{
Ok(Self {
key: key.into(),
value: {
InnerTagValue(<T as TagValue<T>>::from_raw_tag(RawTagValue::new(
value.into(),
))?)
},
})
}
}
impl<T> Tag<T>
where
T: Debug + Clone + PartialEq + Eq + Send,
T: TagValue<T>,
{
fn into_parts(self) -> (TagKey, InnerTagValue<T>) {
(self.key, self.value)
}
pub const fn key(&self) -> &TagKey {
&self.key
}
pub const fn value(&self) -> &T {
&self.value.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct TagList(Vec<RawTag>);
impl TagList {
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn push(&mut self, tag: RawTag) {
self.0.push(tag);
}
pub fn extend(&mut self, tags: Vec<RawTag>) {
self.0.extend(tags);
}
pub fn join(&mut self, other: Self) {
self.0.extend(other.0);
}
pub const fn from_vec(value: Vec<RawTag>) -> Self {
Self(value)
}
pub fn get(&self, key: impl Into<TagKey>) -> Option<&RawTag> {
let key: TagKey = key.into();
self.0.iter().find(|tag| tag.key == key)
}
pub fn into_vec(self) -> Vec<RawTag> {
self.0
}
pub fn as_slice(&self) -> &[RawTag] {
&self.0
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use super::*;
#[Tag(translate = manual)]
#[derive(Debug, Clone, PartialEq, Eq)]
enum MyTag {
A,
B,
}
#[Tag(translate = serde)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct MyStructTag {
foo: String,
bar: bool,
}
impl TryFrom<RawTagValue> for MyTag {
type Error = ParseTagValueError;
fn try_from(value: RawTagValue) -> Result<Self, Self::Error> {
match value.as_str() {
"A" => Ok(Self::A),
"B" => Ok(Self::B),
_ => Err(ParseTagValueError::InvalidValue {
value,
message: "could not match to MyTag enum".to_owned(),
}),
}
}
}
impl From<MyTag> for RawTagValue {
fn from(value: MyTag) -> Self {
match value {
MyTag::A => Self("A".to_owned()),
MyTag::B => Self("B".to_owned()),
}
}
}
#[test]
fn use_tag_directly() {
let key = "Name";
let tag1 = Tag::<bool>::new(key.to_owned(), true);
assert!(*tag1.value());
let tag2 = Tag::<bool>::parse(key.to_owned(), "true".to_owned()).unwrap();
assert!(*tag2.value());
}
#[test]
fn use_attribute_macro() {
#[Tags]
struct MyWrappedTags {
tag1: String,
tag2: bool,
tag3: Option<bool>,
tag4: Option<bool>,
#[tag(key = "myname")]
tag5: MyTag,
#[tag(key = "anothername")]
tag6: Option<MyTag>,
tag7: Option<MyTag>,
tag8: MyStructTag,
tag9: Option<MyStructTag>,
}
let tags = TagList::from_vec(vec![
RawTag::new("tag1".to_owned(), "false".to_owned()),
RawTag::new("tag2".to_owned(), "true".to_owned()),
RawTag::new("tag3".to_owned(), "false".to_owned()),
RawTag::new("myname".to_owned(), "A".to_owned()),
RawTag::new("anothername".to_owned(), "B".to_owned()),
RawTag::new("tag8".to_owned(), r#"{"foo":"hi","bar":false}"#.to_owned()),
]);
let tags = MyWrappedTags::from_tags(tags).unwrap();
assert!(tags.tag1 == "false");
assert!(tags.tag2);
assert!(tags.tag3 == Some(false));
assert!(tags.tag4.is_none());
assert!(tags.tag5 == MyTag::A);
assert!(tags.tag6 == Some(MyTag::B));
assert!(tags.tag7.is_none());
assert!(
tags.tag8
== MyStructTag {
foo: "hi".to_owned(),
bar: false
}
);
assert!(tags.tag9.is_none());
let into_tags = tags.into_tags();
assert_eq!(
into_tags,
TagList::from_vec(vec![
RawTag::new("tag1".to_owned(), "false".to_owned()),
RawTag::new("tag2".to_owned(), "true".to_owned()),
RawTag::new("tag3".to_owned(), "false".to_owned()),
RawTag::new("myname".to_owned(), "A".to_owned()),
RawTag::new("anothername".to_owned(), "B".to_owned()),
RawTag::new("tag8".to_owned(), r#"{"foo":"hi","bar":false}"#.to_owned(),),
])
);
}
}

View File

@@ -0,0 +1,23 @@
use super::{
ParseTagValueError, TagValue, TranslatableManual, TranslatableSerde, TranslateManual,
TranslateSerde,
};
// Bools can just be handled by serde.
impl TranslatableSerde for bool {}
impl TagValue<Self> for bool {
type Error = ParseTagValueError;
type Translator = TranslateSerde;
}
// Due to quoting, we cannot use serde here. It would produce quoted
// strings. Instead, we just serialize/deserialize strings as-is.
// Note that we get the `String` conversion functions for free via the
// `impl_string_wrapper` macro application on `RawTagValue`.
impl TranslatableManual for String {}
impl TagValue<Self> for String {
type Error = ParseTagValueError;
type Translator = TranslateManual;
}

233
src/tags/svc.rs Normal file
View File

@@ -0,0 +1,233 @@
mod ec2 {
use std::fmt::Debug;
use super::super::{
error::ParseTagAwsError, ParseTagError, ParseTagsError, RawTag, RawTagValue, Tag, TagKey,
TagList, TagValue,
};
impl<T> From<Tag<T>> for aws_sdk_ec2::types::Tag
where
T: Debug + Clone + PartialEq + Eq + Into<String> + Send,
T: TagValue<T>,
{
fn from(tag: Tag<T>) -> Self {
let (key, value) = tag.into_parts();
Self::builder().key(key).value(value.0).build()
}
}
impl From<RawTag> for aws_sdk_ec2::types::Tag {
fn from(tag: RawTag) -> Self {
Self::builder().key(tag.key).value(tag.value.0).build()
}
}
impl TryFrom<Vec<aws_sdk_ec2::types::Tag>> for TagList {
type Error = ParseTagsError;
fn try_from(list: Vec<aws_sdk_ec2::types::Tag>) -> Result<Self, Self::Error> {
Ok(Self(
list.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, ParseTagError>>()?,
))
}
}
impl From<TagList> for Vec<aws_sdk_ec2::types::Tag> {
fn from(tags: TagList) -> Self {
tags.0.into_iter().map(Into::into).collect()
}
}
impl From<TagList> for Vec<aws_sdk_ec2::types::Filter> {
fn from(tags: TagList) -> Self {
tags.0
.into_iter()
.map(|tag| {
aws_sdk_ec2::types::Filter::builder()
.name(format!("tag:{}", tag.key))
.values(tag.value)
.build()
})
.collect()
}
}
impl TryFrom<aws_sdk_ec2::types::Tag> for RawTag {
type Error = ParseTagError;
fn try_from(tag: aws_sdk_ec2::types::Tag) -> Result<Self, Self::Error> {
let key = TagKey(tag.key.ok_or(ParseTagAwsError::AwsKeyNone)?);
let value = RawTagValue(
tag.value
.ok_or_else(|| ParseTagAwsError::AwsValueNone { key: key.clone() })?,
);
Ok(Self { key, value })
}
}
impl PartialEq<aws_sdk_ec2::types::Tag> for RawTag {
fn eq(&self, other: &aws_sdk_ec2::types::Tag) -> bool {
Some(&self.key.0) == other.key.as_ref() && Some(&self.value.0) == other.value.as_ref()
}
}
impl PartialEq<RawTag> for aws_sdk_ec2::types::Tag {
fn eq(&self, other: &RawTag) -> bool {
other.eq(self)
}
}
impl From<TagList> for aws_sdk_ec2::types::TagSpecification {
fn from(value: TagList) -> Self {
Self::builder()
.resource_type(aws_sdk_ec2::types::ResourceType::Instance)
.set_tags(Some(value.into()))
.build()
}
}
}
mod cloudformation {
use std::fmt::Debug;
use super::super::{
error::ParseTagAwsError, ParseTagError, ParseTagsError, RawTag, RawTagValue, Tag, TagKey,
TagList, TagValue,
};
impl<T> From<Tag<T>> for aws_sdk_cloudformation::types::Tag
where
T: Debug + Clone + PartialEq + Eq + Into<String> + Send,
T: TagValue<T>,
{
fn from(tag: Tag<T>) -> Self {
let (key, value) = tag.into_parts();
Self::builder().key(key).value(value.0).build()
}
}
impl From<RawTag> for aws_sdk_cloudformation::types::Tag {
fn from(tag: RawTag) -> Self {
Self::builder().key(tag.key).value(tag.value.0).build()
}
}
impl TryFrom<Vec<aws_sdk_cloudformation::types::Tag>> for TagList {
type Error = ParseTagsError;
fn try_from(list: Vec<aws_sdk_cloudformation::types::Tag>) -> Result<Self, Self::Error> {
Ok(Self(
list.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, ParseTagError>>()?,
))
}
}
impl From<TagList> for Vec<aws_sdk_cloudformation::types::Tag> {
fn from(tags: TagList) -> Self {
tags.0.into_iter().map(Into::into).collect()
}
}
impl TryFrom<aws_sdk_cloudformation::types::Tag> for RawTag {
type Error = ParseTagError;
fn try_from(tag: aws_sdk_cloudformation::types::Tag) -> Result<Self, Self::Error> {
let key = TagKey(tag.key.ok_or(ParseTagAwsError::AwsKeyNone)?);
let value = RawTagValue(
tag.value
.ok_or_else(|| ParseTagAwsError::AwsValueNone { key: key.clone() })?,
);
Ok(Self { key, value })
}
}
impl PartialEq<aws_sdk_cloudformation::types::Tag> for RawTag {
fn eq(&self, other: &aws_sdk_cloudformation::types::Tag) -> bool {
Some(&self.key.0) == other.key.as_ref() && Some(&self.value.0) == other.value.as_ref()
}
}
impl PartialEq<RawTag> for aws_sdk_cloudformation::types::Tag {
fn eq(&self, other: &RawTag) -> bool {
other.eq(self)
}
}
}
mod efs {
use std::fmt::Debug;
use super::super::{
ParseTagError, ParseTagsError, RawTag, RawTagValue, Tag, TagKey, TagList, TagValue,
};
impl<T> From<Tag<T>> for aws_sdk_efs::types::Tag
where
T: Debug + Clone + PartialEq + Eq + Into<String> + Send,
T: TagValue<T>,
{
fn from(tag: Tag<T>) -> Self {
let (key, value) = tag.into_parts();
Self::builder()
.key(key)
.value(value.0)
.build()
.expect("builder misused")
}
}
impl From<RawTag> for aws_sdk_efs::types::Tag {
fn from(tag: RawTag) -> Self {
Self::builder()
.key(tag.key)
.value(tag.value.0)
.build()
.expect("builder misused")
}
}
impl TryFrom<Vec<aws_sdk_efs::types::Tag>> for TagList {
type Error = ParseTagsError;
fn try_from(list: Vec<aws_sdk_efs::types::Tag>) -> Result<Self, Self::Error> {
Ok(Self(
list.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, ParseTagError>>()?,
))
}
}
impl From<TagList> for Vec<aws_sdk_efs::types::Tag> {
fn from(tags: TagList) -> Self {
tags.0.into_iter().map(Into::into).collect()
}
}
impl TryFrom<aws_sdk_efs::types::Tag> for RawTag {
type Error = ParseTagError;
fn try_from(tag: aws_sdk_efs::types::Tag) -> Result<Self, Self::Error> {
let key = TagKey(tag.key);
let value = RawTagValue(tag.value);
Ok(Self { key, value })
}
}
impl PartialEq<aws_sdk_efs::types::Tag> for RawTag {
fn eq(&self, other: &aws_sdk_efs::types::Tag) -> bool {
self.key.0 == other.key && self.value.0 == other.value
}
}
impl PartialEq<RawTag> for aws_sdk_efs::types::Tag {
fn eq(&self, other: &RawTag) -> bool {
other.eq(self)
}
}
}