JSONPlaceholder(https://jsonplaceholder.typicode.com/)は、RESTful インターフェースの動作検証に特化した無料のモック API サービスです。本稿では、Rust を用いてこのサービスと連携し、CRUD 操作を非同期で実行する方法を解説します。
対象エンドポイントとデータ構造
以下のエンドポイントを対象とします:
GET /posts/1— 単一投稿の取得GET /posts— 投稿一覧の取得(上限2件)POST /posts— 新規投稿の作成PUT /posts/1— 投稿の完全更新DELETE /posts/1— 投稿の削除
レスポンスはすべて JSON 形式で、各投稿オブジェクトは以下のようなスキーマに従います:
{
"userId": 1,
"id": 1,
"title": "example title",
"body": "example content"
}
依存関係の設定
Cargo.toml に以下の依存を追加します:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
tokio = { version = "1.0", features = ["full"] }
型定義とクライアント実装
データのシリアライズ/デシリアライズに対応した構造体を定義します:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Article {
#[serde(rename = "userId")]
author_id: u32,
id: u32,
title: String,
content: String,
}
HTTP 通信を担う非同期関数群を実装します。各関数はエラーを anyhow::Error で一元管理し、より堅牢なエラーハンドリングを提供します:
use anyhow::Result;
use reqwest::Client;
const API_ROOT: &str = "https://jsonplaceholder.typicode.com";
async fn fetch_single_article() -> Result<String> {
let response = Client::new()
.get(format!("{}/posts/1", API_ROOT))
.send()
.await?;
Ok(response.text().await?)
}
async fn fetch_article_object() -> Result<Article> {
let response = Client::new()
.get(format!("{}/posts/1", API_ROOT))
.send()
.await?;
Ok(response.json().await?)
}
async fn fetch_first_two_articles() -> Result<Vec<Article>> {
let response = Client::new()
.get(format!("{}/posts", API_ROOT))
.send()
.await?;
let all: Vec<Article> = response.json().await?;
Ok(all.into_iter().take(2).collect())
}
async fn submit_new_article() -> Result<String> {
let client = Client::new();
let payload = Article {
author_id: 10,
id: 0,
title: "Rust Integration Test".to_owned(),
content: "This post was generated via reqwest.".to_owned(),
};
let response = client
.post(format!("{}/posts", API_ROOT))
.json(&payload)
.send()
.await?;
Ok(response.text().await?)
}
async fn replace_article() -> Result<String> {
let client = Client::new();
let updated = Article {
author_id: 1,
id: 1,
title: "Updated Title".to_owned(),
content: "Revised content using Rust.".to_owned(),
};
let response = client
.put(format!("{}/posts/1", API_ROOT))
.json(&updated)
.send()
.await?;
Ok(response.text().await?)
}
async fn remove_article() -> Result<String> {
let client = Client::new();
let response = client
.delete(format!("{}/posts/1", API_ROOT))
.send()
.await?;
Ok(response.text().await?)
}
#[tokio::main]
async fn main() -> Result<()> {
println!("=== Raw Response ===");
println!("{}", fetch_single_article().await?);
println!("\n=== Deserialized Object ===");
println!("{:#?}", fetch_article_object().await?);
println!("\n=== First Two Articles ===");
println!("{:#?}", fetch_first_two_articles().await?);
println!("\n=== New Article Creation ===");
println!("{}", submit_new_article().await?);
println!("\n=== Article Update ===");
println!("{}", replace_article().await?);
println!("\n=== Deletion Confirmation ===");
println!("{}", remove_article().await?);
Ok(())
}
上記コードでは、reqwest::Client の再利用や anyhow を用いたエラー抽象化、tokio::main マクロによるランタイム統合を採用しています。また、rustls-tls 機能を有効化することで、OpenSSL に依存しない安全な TLS 通信が可能になります。