
serdeを使ってAWSのリージョン名をrusoto_coreのenum Regionにserialize/deserializeする
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部の夏目です。
RustでAWSのCLIツールを書いているのですが、設定ファイルに書いたリージョン名をrusoto_coreのenumRegionとして扱う方法について共有します。
特に何もしないでserializeしてみる
use rusoto_core::Region;
use serde::{Deserialize, Serialize};
fn main() {
    let data = Bucket {
        name: "test bucket".to_string(),
        region: Region::ApNortheast1,
    };
    println!("debug       : {:?}", data);
    let json = serde_json::to_string(&data).unwrap();
    println!("serialized  : {}", json);
    let deserialized: Bucket = serde_json::from_str(&json).unwrap();
    println!("deserialized: {:?}", deserialized);
}
#[derive(Debug, Serialize, Deserialize)]
struct Bucket {
    name: String,
    region: Region,
}
/Users/natsume.yuta/.cargo/bin/cargo run --color=always
   Compiling serde_region v0.1.0 (/Users/natsume.yuta/workspace/rust/serde_region)
    Finished dev [unoptimized + debuginfo] target(s) in 5.49s
     Running `target/debug/serde_region`
debug       : Bucket { name: "test bucket", region: ApNortheast1 }
serialized  : {"name":"test bucket","region":["ap-northeast-1",null]}
deserialized: Bucket { name: "test bucket", region: ApNortheast1 }
rusoto_coreのenum Regionを特に何もせずにserializeしてみる。
Region::ApNortheast1が["ap-northeast-1", null]にserializeされている。
deserializeする場合も同様の表現にする必要があるので、このままだと設定ファイルなどに直接使うのは難しい。
"ap-northeast-1"をRegion::ApNortheast1として扱うようにしてみる
use rusoto_core::Region;
use serde::de::Unexpected;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;
fn main() {
    let data = Bucket {
        name: "test bucket".to_string(),
        region: Region::ApNortheast1,
    };
    println!("debug       : {:?}", data);
    let json = serde_json::to_string(&data).unwrap();
    println!("serialized  : {}", json);
    let deserialized: Bucket = serde_json::from_str(&json).unwrap();
    println!("deserialized: {:?}", deserialized);
}
#[derive(Debug, Serialize, Deserialize)]
struct Bucket {
    name: String,
    #[serde(
        serialize_with = "custom_serialize",
        deserialize_with = "custom_deserialize"
    )]
    region: Region,
}
fn custom_serialize<S>(value: &Region, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_str(value.name())
}
fn custom_deserialize<'de, D>(deserializer: D) -> Result<Region, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    match Region::from_str(&s) {
        Ok(region) => Ok(region),
        Err(_) => Err(Error::invalid_value(
            Unexpected::Str(&s),
            &"invalid region name",
        )),
    }
}
/Users/natsume.yuta/.cargo/bin/cargo run --color=always
    Finished dev [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/serde_region`
debug       : Bucket { name: "test bucket", region: ApNortheast1 }
serialized  : {"name":"test bucket","region":"ap-northeast-1"}
deserialized: Bucket { name: "test bucket", region: ApNortheast1 }
serializeとdeserializeを自分で実装して、リージョン名を直接enum Regionと解釈するようにしてみた。
crate serdeではAttributeに#[serde(serialize_with = "$path")]や#[serde(deserialize_with = "$path")]を追記することでserializeやdeserializeを任意の関数にすることができます。
渡す関数は次のようにする必要があります (Tはserialize/deserializeしたい値の型)。
- serialize_withでは
fn<S>(value: &T, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer
-  deserialize_withでは
fn<'de, D>(deserializer: D) -> Result<T, D::Error> where D: serde::Deserializer
まとめ
以上リージョン名を直接rusoto_coreのenum Regionにserialize/deserializeする方法でした。
なにかの役に立てば幸いです。
今回のコードで使用したCargo.tomlの[dependencies]
[dependencies]
rusoto_core = "0.45"
serde = {version = "1", features = ["derive"]}
serde_json = "1"












