Rust でYouTube API Client を作成してみた

# はじめに

全力回避フラグちゃん! のチャンネルの情報を取得するのに，Python を使用してAPI にアクセスするクライアントプログラムを作成していましたが，最近Rust に興味がありRust で書いて見たいと思ったので，とりあえず書いてみました．

# プログラムの作成

HTTP Client にはreqwest を使用しました．
作成中に色々試行していたたため，Cargo.toml には不要なものまで書いてありますが，とりあえず書いておけばコンパイルは通ります．

API Key などセンシティブな情報をプログラム中にハードコーディングしておきたくないので，configparser を使用して別ファイルに保存されている情報を取得しています．

```toml:Cargo.toml
[package]
name = "proto"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = { version = "0.11.9", features = ["json", "blocking"] }
text-colorizer = "1"
regex = "1"
proconio = "0.3.6"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
configparser = "3.0.0"
```

API のレスポンス結果は，json 形式で返ってくるため，reqwest のjson() メソッドでjson 形式にして結果を受け取ります．この際，結果の型が推論できないため，型を指定しないといけません．serde_json::Value で受け取れますが，今回は練習のため結果の方をすべて構造体で定義して受け取っています．
[このページ](https://docs.rs/reqwest/0.10.0-alpha.2/reqwest/blocking/struct.Response.html)に書いてある以下のような書き方で書いています．

```rust
#[derive(Deserialize)]
struct Ip {
    origin: String,
}

let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
```

実際にAPI にアクセスしたり，API のドキュメントを読んで構造体を定義しました．
API のアクセス結果は，以下のようなjson レスポンスになります．

```json:APIレスポンス
{
  "kind": "youtube#playlistListResponse",
  "etag": "pi8uuHdFJzC2ByJy0bNodG6nMuA",
  "pageInfo": {
    "totalResults": 10,
    "resultsPerPage": 50
  },
  "items": [
    {
      "kind": "youtube#playlist",
      "etag": "u3mq8p7n4nwZwe_sMM70uYrrFSE",
      "id": "PLnN2g2346RAUZm1NOZ2IK4EGOv-MuRwtb",
      "snippet": {
        "publishedAt": "2022-01-06T10:01:11Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "失恋フラグちゃん",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "失恋フラグちゃん",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "D9204-uXkSD3J1w8xtksPlG2pAc",
      "id": "PLnN2g2346RAVpAFQqIBOA-wR73BEMS6rF",
      "snippet": {
        "publishedAt": "2021-08-13T06:12:32Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "コラボ動画",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/hqdefault.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "コラボ動画",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "BRdHsd8MxqkP4oAX9KYCzsb3sxg",
      "id": "PLnN2g2346RAXtwKCCfMDX4QQKV9dBg75f",
      "snippet": {
        "publishedAt": "2021-05-19T09:09:11Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "恋愛フラグ師匠",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "恋愛フラグ師匠",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "e0CO3AMFPpLqWLTzgx4NV-zfqZg",
      "id": "PLnN2g2346RAWysxlPBAFnBTts3jIgcIJg",
      "snippet": {
        "publishedAt": "2021-03-29T03:30:24Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "EDコーナー【フラグラ！】集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "EDコーナー【フラグラ！】集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "Jx4e-5tbMs5N5RZYzkIuquCHKu4",
      "id": "PLnN2g2346RAWelVUAE06Cx1x4wssLR9Zb",
      "snippet": {
        "publishedAt": "2020-06-08T04:21:29Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "短編集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "短編集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "wqKXDjopNolNb1I5sAX-rpqzeIs",
      "id": "PLnN2g2346RAXibP0efNEswg18ZJ-_nhlt",
      "snippet": {
        "publishedAt": "2020-05-21T10:47:15Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃん傑作選",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/sddefault.jpg",
            "width": 640,
            "height": 480
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃん傑作選",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "D4Blp1Gog2xJ0-wEkikORBVDffA",
      "id": "PLnN2g2346RAXExojgZcYGqOtwWi6B0sYH",
      "snippet": {
        "publishedAt": "2020-05-21T10:46:00Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "生存フラグさん",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "生存フラグさん",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "mKNV-knrEEIOgk5V6yGoOGZOeGk",
      "id": "PLnN2g2346RAWd0PryMMtRROzfOBOpHVJg",
      "snippet": {
        "publishedAt": "2020-05-21T10:45:26Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "長編集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "長編集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "-mMxsKnfifN1lSCDe-kZ6izNgk4",
      "id": "PLnN2g2346RAXJzpPY4A2-LUUdCYn64X9P",
      "snippet": {
        "publishedAt": "2020-02-28T08:59:53Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃんストーリー編",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃんストーリー編",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "2tJoid-K4sIXoxdHIy_apt6EH6g",
      "id": "PLnN2g2346RAUtrLI_UhEcdrGYxb2F1p_d",
      "snippet": {
        "publishedAt": "2019-11-01T14:14:06Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃん！全話",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃん！全話",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    }
  ]
}
```

プログラムは，「全力回避フラグちゃん!」というYouTube のチャンネルのプレイリストの一覧を取得するプログラムになっています．みなさんが使用する際は，URL やAPI Key 情報，チャンネルID 情報を変更して使用してください．

```rust:main.rs

use std::result::Result;
use std::error::Error;
use serde::{Deserialize, Serialize};
use configparser::ini::Ini;

// API Response のjson を構造体で定義
#[derive(Serialize, Deserialize, Debug)]
struct PlaylistResults {
    kind: String,
    etag: String,
    pageInfo: PageInfo,
    items: Vec<Item>
}

#[derive(Serialize, Deserialize, Debug)]
struct PageInfo {
    totalResults: u32,
    resultsPerPage: u32
}

#[derive(Serialize, Deserialize, Debug)]
struct Item {
    kind: String,
    etag: String,
    id: String,
    snippet: Snippet,
    channelTitle: Option<String>,
    localized: Option<Localized>,
    status: Option<Status>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Snippet {
    publishedAt: String,
    channelId: String,
    title: String,
    description: String,
    thumbnails: Thumbnails
}

#[derive(Serialize, Deserialize, Debug)]
struct Thumbnails {
    default: Option<Thumbnail>,
    medium: Option<Thumbnail>,
    high: Option<Thumbnail>,
    standard: Option<Thumbnail>,
    maxres: Option<Thumbnail>
}

#[derive(Serialize, Deserialize, Debug)]
struct Thumbnail {
    url: String,
    width: u32,
    height: u32,
}

#[derive(Serialize, Deserialize, Debug)]
struct Localized {
    title: String,
    description: String
}

#[derive(Serialize, Deserialize, Debug)]
struct Status {
    privacyStatus: String
}


// API Client の関数
fn get_access_api(url: &str) -> Result<Vec<Item>, Box<dyn Error>> {
    // configparser でAPI Key やチャンネルID 情報を取得
    let mut config = Ini::new();
    let _conf_map = config.load("conf/config.ini");
    let api_key = config.get("YouTube", "api_key").unwrap();
    let channel_id = config.get("YouTube", "channel_id").unwrap();
    let max_results = "50";

    // 上記の情報をGet メソッドのパラメータに指定
    let params = [("key", api_key), ("channelId", channel_id), 
                  ("part", "id".to_string()), ("part", "snippet".to_string()), 
                  ("part", "status".to_string()), ("maxResults", max_results.to_string())];
    let client = reqwest::blocking::Client::new();

    let response = client.get(url).query(&params).send()?;
        
    if !response.status().is_success() {
        println!("{}", response.url());
        Err(format!("{}", response.status()))?;
    }

    // レスポンス結果をjson として受け取る
    let response_json: PlaylistResults = response.json()?;
    let items = response_json.items;

    Ok(items)
}


fn main() {

    let playlists_url = "https://www.googleapis.com/youtube/v3/playlists";
    let results = get_access_api(playlists_url);
    match results {
        Ok(response) => println!("{:?}", response),
        Err(err) => eprintln!("error: {}", err),
    }
    
}
```

#実行結果

上記のプログラムを実行します．

```terminal:プログラム実行
cargo run
```

コンパイルの際，```convert the identifier to snake case: `channel_id``` というキャメルケースではなくスネークケースにしろ，という警告が出ますが，ここは結果のjson のキー名と同じにしておかないと実行エラーが出るので，警告が出ることを承知して実行します．実行した結果が下記になります．

```terminal:実行結果
[Item { kind: "youtube#playlist", etag: "u3mq8p7n4nwZwe_sMM70uYrrFSE", id: "PLnN2g2346RAUZm1NOZ2IK4EGOv-MuRwtb", snippet: Snippet { publishedAt: "2022-01-06T10:01:11Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "失恋フラグちゃん", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "D9204-uXkSD3J1w8xtksPlG2pAc", id: "PLnN2g2346RAVpAFQqIBOA-wR73BEMS6rF", snippet: Snippet { publishedAt: "2021-08-13T06:12:32Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "コラボ動画", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/hqdefault.jpg", width: 480, height: 360 }), standard: None, maxres: None } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "BRdHsd8MxqkP4oAX9KYCzsb3sxg", id: "PLnN2g2346RAXtwKCCfMDX4QQKV9dBg75f", snippet: Snippet { publishedAt: "2021-05-19T09:09:11Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "恋愛フラグ師匠", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "e0CO3AMFPpLqWLTzgx4NV-zfqZg", id: "PLnN2g2346RAWysxlPBAFnBTts3jIgcIJg", snippet: Snippet { publishedAt: "2021-03-29T03:30:24Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "EDコーナー【フラグラ！】集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "Jx4e-5tbMs5N5RZYzkIuquCHKu4", id: "PLnN2g2346RAWelVUAE06Cx1x4wssLR9Zb", snippet: Snippet { publishedAt: "2020-06-08T04:21:29Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "短編集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "wqKXDjopNolNb1I5sAX-rpqzeIs", id: "PLnN2g2346RAXibP0efNEswg18ZJ-_nhlt", snippet: Snippet { publishedAt: "2020-05-21T10:47:15Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃん傑作選", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/sddefault.jpg", width: 640, height: 480 }), maxres: None } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "D4Blp1Gog2xJ0-wEkikORBVDffA", id: "PLnN2g2346RAXExojgZcYGqOtwWi6B0sYH", snippet: Snippet { publishedAt: "2020-05-21T10:46:00Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "生存フラグさん", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "mKNV-knrEEIOgk5V6yGoOGZOeGk", id: "PLnN2g2346RAWd0PryMMtRROzfOBOpHVJg", snippet: Snippet { publishedAt: "2020-05-21T10:45:26Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "長編集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "-mMxsKnfifN1lSCDe-kZ6izNgk4", id: "PLnN2g2346RAXJzpPY4A2-LUUdCYn64X9P", snippet: Snippet { publishedAt: "2020-02-28T08:59:53Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃんストーリー編", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "2tJoid-K4sIXoxdHIy_apt6EH6g", id: "PLnN2g2346RAUtrLI_UhEcdrGYxb2F1p_d", snippet: Snippet { publishedAt: "2019-11-01T14:14:06Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃん！全話", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }]
```

定義した構造体が結果と一致したため，きちんと結果を構造体として受け取れている事がわかります．

# おわりに

Rust でAPI Client を作成した．書き方を少しだけ覚えられたので，今後も少しずつ勉強していく．次は，多くのリクエストを並行処理するプログラムをかけるようになりたい．

ここまで読んでいる人はいないと思いますが，もしいたらまずは，以下のリンクから<b>全力回避フラグちゃん!</b> チャンネルとフラグちゃんのTwitter をフォローしてください．この記事を読むより大切なことです．
大事なことなのでもう一度，<b>チャンネル登録</b>と<b>Twitter のフォロー</b>をよろしくお願いいたします．

# 参考文献
- [プログラミングRust 第2版．Jim Blandy、Jason Orendorff、Leonora F. S. Tindall　著、中田 秀基　訳
2022/01](https://www.oreilly.co.jp/books/9784873119076/)

# 関連リンク
- YouTube Data API Channels リソース  https://developers.google.com/youtube/v3/docs/channels?hl=ja
- 全力回避フラグちゃん! https://www.youtube.com/channel/UCo_nZN5yB0rmfoPBVjYRMmw/videos
- 株式会社Plott / Plott Inc. https://plott.tokyo/#top
- フラグちゃんのTwitter https://twitter.com/flag__chan
- Github  https://github.com/uky007/flag_analysis
- Reqwest  https://docs.rs/reqwest/0.10.0-alpha.2/reqwest/blocking/struct.Response.html