シンプルなレコードの追加と検索
このチュートリアルでは、名前・説明・キーワードの配列で構成される、シンプルなプロフィールレコードを追加・取得するためのいくつかの基本的な関数を有する Dapp を書きます。
このプログラムは、以下の関数を有します:
-
update
関数は、name
、description
、keywords
からなるプロフィールを追加することができます。 -
getSelf
関数は、関数の呼び出し元に関連づけられたプリンシパルのプロフィールを返します。 -
get
関数は、渡されたname
の値にマッチするプロフィールを返す単純なクエリ関数です。 この関数では、指定された名前がname
フィールドと完全に一致する必要があります。 -
search
関数は、より複雑なクエリを実行して、任意のフィールドで指定されたテキストのすべてまたは一部に一致するプロフィールを返します。例えば、特定のキーワードを含むプロフィールや、名前や説明の一部のみにマッチするプロフィールを返すことができます。
このチュートリアルでは、Rust CDK のインターフェースとマクロを使用して、Rust で Internet Computer 用の Dapps を簡単に書けるようにするための簡単な例を紹介しています。
このチュートリアルでは以下を説明します:
-
Candid インターフェース記述言語を使った、少し複雑なデータ(
record
とキーワードのarray
)の表現の仕方。 -
部分一致する文字列を照合するシンプルな検索機能の書き方。
-
特定のプリンシパルとプロフィールの関連付け方。
はじめる前に
プロジェクトをはじめる前に、以下をご確認ください:
-
インターネットに接続しており、ローカルの macOS または Linux コンピュータでターミナルにアクセスできること。
-
Rust のインストール方法 にあるように、Rust プログラミング言語と Cargo が OS にダウンロードされ、インストールされていること。
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Rust のバージョンは 1.46.0 より新しい必要があります。
-
ダウンロードとインストールの説明に従って、DFINITY Canister Software Development Kit (SDK) パッケージのダウンロードとインストールが済んでいること。
-
cmake
のインストールが済んでいること。例えば、macOS では Homebrew を使って以下のコマンドを実行します:brew install cmake
Homebrew をインストールする方法については、Homebrew のドキュメントを参照してください。
-
コンピュータ上の ローカル実行環境 プロセスが停止していること。
ローカルコンピュータ上で新たなターミナルを開く方法や、ターミナルでコマンドを実行する方法や、インストールパッケージを確認する方法がわからない場合には、初めての人のための準備を参照してください。 既に必要な前提条件を満たしている場合には、新しいプロジェクトの作成 へと進んでください。
このチュートリアルは、完走に約 20 分かかります。
新しいプロジェクトの作成
新しいプロジェクトを作る手順は以下になります:
-
ローカル PC でターミナルを開きます。
-
以下のコマンドを実行して新しいプロジェクトを作成します。
dfx new --type=rust rust_profile
-
以下のコマンドで、プロジェクトディレクトリに移動します。
cd rust_profile
デフォルトプロジェクトの変更
Hello World! Rust CDK クイックスタートでは、Rust Canister のデフォルトプロジェクトのファイルを確認しました。
このチュートリアルを完了するには、以下の手順を踏みます。
デフォルト Dapp の置き換え
Rust Dapp 用のファイルが揃ったので、テンプレートの lib.rs
Dapp を Internet Computer にデプロイしたい Rust Dapp に置き換えていきます。
デフォルトのプログラムを置き換えるには、以下のようにします。
-
自分がプロジェクトのルートディレクトリにいることを確認します。
-
テキストエディタで
src/rust_profile/Cargo.toml
ファイルを開き、依存関係にserde
を追加します。[dependencies] ic-cdk = "0.3" ic-cdk-macros = "0.3" serde = "1.0"
-
src/rust_profile/lib.rs
ファイルをテキストエディタで開き、既存の内容を削除します。次に、
getSelf
、update
、get
、search
関数を Rust プログラムで実装していきます。 -
下のサンプルコードを
profile.rs
にコピー&ペーストしてください。use ic_cdk::{ call::{self, ManualReply}, export::{ candid::{CandidType, Deserialize}, Principal, }, }; use ic_cdk_macros::*; use std::cell::RefCell; use std::collections::BTreeMap; type IdStore = BTreeMap<String, Principal>; type ProfileStore = BTreeMap<Principal, Profile>; #[derive(Clone, Debug, Default, CandidType, Deserialize)] struct Profile { pub name: String, pub description: String, pub keywords: Vec<String>, } thread_local! { static PROFILE_STORE: RefCell<ProfileStore> = RefCell::default(); static ID_STORE: RefCell<IdStore> = RefCell::default(); } #[query(name = "getSelf")] fn get_self() -> Profile { let id = ic_cdk::api::caller(); PROFILE_STORE.with(|profile_store| { profile_store .borrow() .get(&id) .cloned() .unwrap_or_else(|| Profile::default()) }) } #[query] fn get(name: String) -> Profile { ID_STORE.with(|id_store| { PROFILE_STORE.with(|profile_store| { id_store .borrow() .get(&name) .and_then(|id| profile_store.borrow().get(id).cloned()) .unwrap_or_else(|| Profile::default()) }) }) } #[update] fn update(profile: Profile) { let principal_id = ic_cdk::api::caller(); ID_STORE.with(|id_store| { id_store .borrow_mut() .insert(profile.name.clone(), principal_id); }); PROFILE_STORE.with(|profile_store| { profile_store.borrow_mut().insert(principal_id, profile); }); } #[query(manual_reply = true)] fn search(text: String) -> ManualReply<Option<Profile>> { let text = text.to_lowercase(); PROFILE_STORE.with(|profile_store| { for (_, p) in profile_store.borrow().iter() { if p.name.to_lowercase().contains(&text) || p.description.to_lowercase().contains(&text) { return ManualReply::one(Some(p)); } for x in p.keywords.iter() { if x.to_lowercase() == text { return ManualReply::one(Some(p)); } } } }); ManualReply::one(None::<Profile>) }
-
変更を保存してファイルを閉じ、次に進みます。
インターフェイス記述ファイルの更新
Candid は、Internet Computer で動作する Canister と対話するためのインターフェース記述言語(IDL)です。 Candid ファイルは、Canister が定義する各関数の名前・引数・返し値のフォーマットやデータ型など、Canister のインターフェースを言語に依存しないように記述したものです。
Candid ファイルをプロジェクトに追加することで、Rust で定義されたデータが Internet Computer 上で安全に実行されるために適切に変換されることを保証します。
Candid インターフェース記述言語の構文の詳細は Candid ガイドか Candid クレートのドキュメントをご覧ください。
Candid ファイルを更新するには、以下のようにします:
-
自分がプロジェクトのルートディレクトリにいることを確認します。
-
src/rust_profile/rust_profile.did
ファイルをテキストエディタで開いてください。 -
getSelf
、update
、get
、search
関数のために以下のservice
の定義をコピー&ペーストして書き込んでください:type Profile_2 = record { "name": text; "description": text; "keywords": vec text; }; type Profile = Profile_2; service : { "getSelf": () -> (Profile_2) query; "get": (text) -> (Profile_2) query; "update": (Profile_2) -> (); "search": (text) -> (opt Profile_2) query; }
-
変更を保存してファイルを閉じ、次に進んでください。
ローカル実行環境 を立ち上げる
rust_profile
プロジェクトをビルドする前に、ローカル実行環境 か、Internet Computer メインネットに接続する必要があります。
ローカル実行環境 を立ち上げるには、以下のようにします:
-
自分がプロジェクトのルートディレクトリにいることを確認します。
-
ローカル実行環境 をバックグラウンドで立ち上げるために、以下のコマンドを実行します:
dfx start --background --clean
プラットフォームやローカルのセキュリティ設定によっては、警告が表示される場合があります。 ネットワーク接続を許可するかどうかの確認画面が表示された場合は、Allow をクリックします。
プロジェクトの登録・ビルド・デプロイ
開発環境で立ち上がっている ローカル実行環境 に接続した後に、プロジェクトの登録・ビルド・デプロイをローカル環境で行うことができます。
登録・ビルド・デプロイを行うためには、以下のようにします:
-
プロジェクトのルートディレクトリにいることを確認します。
-
dfx.json
に指定されている Canister を以下のコマンドで、登録・ビルド・デプロイします:dfx deploy
dfx deploy
コマンドを実行すると、以下のような実行結果に関しての情報が表示されます。Creating a wallet canister on the local network. The wallet canister on the "local" network for user "default" is "rwlgt-iiaaa-aaaaa-aaaaa-cai" Deploying all canisters. Creating canisters... Creating canister "rust_profile"... "rust_profile" canister created with canister id: "rrkah-fqaaa-aaaaa-aaaaq-cai" Creating canister "rust_profile_assets"... "rust_profile_assets" canister created with canister id: "ryjl3-tyaaa-aaaaa-aaaba-cai" Building canisters... Executing: "cargo" "build" "--target" "wasm32-unknown-unknown" "--release" "-p" "rust_profile" ... Finished release [optimized] target(s) in 6.31s Building frontend... Installing canisters... Creating UI canister on the local network. The UI canister on the "local" network is "r7inp-6aaaa-aaaaa-aaabq-cai" Installing code for canister rust_profile, with canister_id rrkah-fqaaa-aaaaa-aaaaq-cai ... Deployed canisters.
デプロイした Canister の関数を呼ぶ
Canister のデプロイが成功すると、Canister の関数を呼ぶことでテストできるようになります。
このチュートリアルでは、以下のようにします:
-
update
関数を呼び、プロフィールを追加する。 -
getSelf
関数を呼び、プリンシパル ID のプロフィールを表示する。 -
search
関数を呼び、キーワードを使ってプロフィールを検索する。
デプロイした Canister をテストするために:
-
update
関数を呼び、プロフィールレコードを作成するには以下のコマンドを実行します:dfx canister call rust_profile update '(record {name = "Luxi"; description = "mountain dog"; keywords = vec {"scars"; "toast"}})'
-
getSelf
関数を呼び、プロフィールレコードを取得するには以下のコマンドを実行します:dfx canister call rust_profile getSelf
このコマンドは、
update
関数を使って追加したプロフィールを返します。 例えば、以下のようになります:( record { name = "Luxi"; description = "mountain dog"; keywords = vec { "scars"; "toast" }; }, )
いまのところ、この Dapp は1つのプロフィールを保存して返すだけです。 次のコマンドを実行して、
update
関数を使って2つ目のプロフィールを追加すると、Luxi
のプロフィールがDupree
のプロフィールに置き換わります:dfx canister call rust_profile update '(record {name = "Dupree"; description = "black dog"; keywords = vec {"funny tail"; "white nose"}})'
get
、getSelf
、search
関数を使っても、Dupree
のプロフィールが返ってくるだけです。 -
search
関数を以下のように実行してみましょう。dfx canister call rust_profile search '("black")';
このコマンドは
description
との一致を見つけ、以下のプロフィールを返します:( opt record { name = "Dupree"; description = "black dog"; keywords = vec { "funny tail"; "white nose" }; },
新しい ID に対するプロフィールの追加
いまのところ、この Dapp はコマンドを実行したプリンシパルに関連付けられた1つのプロフィールしか保存しません。
関数 get
, getSelf
, search
が思った通りに動作することをテストするために、異なるプロフィールを持つことができる新しい ID を追加する必要があります。
テストのために ID を追加します:
-
以下のコマンドで新しいユーザー ID を作成します。
dfx identity new Miles
Creating identity: "Miles". Created identity: "Miles".
-
update
関数を呼び、新しい ID に対応したプロフィールを追加します。dfx --identity Miles canister call rust_profile update '(record {name = "Miles"; description = "Great Dane"; keywords = vec {"Boston"; "mantle"; "three-legged"}})'
-
getSelf
関数を呼び、default
のユーザー ID に紐づけられたプロフィールを確認します。dfx canister call rust_profile getSelf
このコマンドはデフォルトの ID に現在紐づけられているプロフィールを表示ます。この例では、Dupree のプロフィールです:
( record { name = "Dupree"; description = "black dog"; keywords = vec { "funny tail"; "white nose" }; }, )
-
Miles
のユーザー ID を使ってgetSelf
関数を呼ぶには以下のコマンドを実行します:dfx --identity Miles canister call rust_profile getSelf
このコマンドは Miles の ID に現在紐づいているプロフィールを表示します。この例では、以下のようになります:
( record { name = "Miles"; description = "Great Dane"; keywords = vec { "Boston"; "mantle"; "three-legged" }; }, )
-
正しいプロフィールが返されるかどうかをテストするために、説明の一部やキーワードを使って
search
関数を呼び出しましょう。例えば、
Miles
のプロフィールが返されることを検証するために、以下のコマンドを実行します:dfx canister call rust_profile search '("Great")'
このコマンドは
Miles
のプロフィールを返します:( opt record { name = "Miles"; description = "Great Dane"; keywords = vec { "Boston"; "mantle"; "three-legged" }; }, )
-
正しいプロフィールが返されるかどうかをさらにテストするために、
search
関数を呼びます。例えば、
Dupree
のプロフィールが返されることを検証するために、以下のコマンドを実行します:dfx canister call rust_profile search '("black")'
このコマンドは
Dupree
のプロフィールを返します:( opt record { name = "Dupree"; description = "black dog"; keywords = vec { "funny tail"; "white nose" }; }, )