Initial commit
This commit is contained in:
110
src/error.rs
Normal file
110
src/error.rs
Normal 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
9
src/export.rs
Normal 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
1046
src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
72
src/tags/README.md
Normal file
72
src/tags/README.md
Normal 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
127
src/tags/error.rs
Normal 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
49
src/tags/helpers.rs
Normal 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
407
src/tags/mod.rs
Normal 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(),),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/tags/predefined_types.rs
Normal file
23
src/tags/predefined_types.rs
Normal 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
233
src/tags/svc.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user