Github CopilotでSolidityのコードをICP上のRustに変換する3

## はじめに
今回は、実際に[ContentGraph.sol](https://github.com/verify-media/contracts/blob/main/src/ContentGraph.sol)をICP上のRustに変換します。下記の続きになります。

- [Github CopilotでSolidityのコードをICP上のRustに変換する](https://qiita.com/higayasuo/items/73f5b90514d97eab8977)
- [Github CopilotでSolidityのコードをICP上のRustに変換する2](https://qiita.com/higayasuo/items/37d4df8b8fdbf1e77e2d)

## ContentGraph.solの変換
VS Codeで、[ContentGraph.sol](https://github.com/verify-media/contracts/blob/main/src/ContentGraph.sol)を開き、インラインチャットで下記のプロンプトを実行します。
```
ICP上で動くRustのCanisterに変換して
```

下記のコードが出力されたので、`content_graph.rs`に保存します。
```rust
use std::collections::HashMap;

use candid::Principal;

enum NodeType {
    ORG,
    REFERENCE,
    ASSET,
}

struct ContentNode {
    id: [u8; 32],
    node_type: NodeType,
    reference_of: [u8; 32],
    uri: String,
}

struct Node {
    token: u256,
    node_type: NodeType,
    id: [u8; 32],
    reference_of: [u8; 32],
    uri: String,
    access_auth: Principal,
    reference_auth: Principal,
}

struct ContentGraph {
    total_supply: u256,
    nodes: HashMap<[u8; 32], Node>,
    nodes_created: HashMap<Principal, u256>,
    token_to_id: HashMap<u256, [u8; 32]>,
    identity: IIdentityRegistry,
}

#[ic_cdk_macros::query]
fn get_total_supply() -> u256 {
    total_supply
}

#[ic_cdk_macros::query]
fn get_node(id: [u8; 32]) -> Option<Node> {
    let id_bytes = id.to_vec();
    let id_hash = Hash::from_slice(&id_bytes);
    nodes.get(&id_hash).cloned()
}

#[ic_cdk_macros::update]
fn publish_bulk(parent_id: [u8; 32], content: Vec<ContentNode>) {
    assert!(content.len() <= 100);
    let contents = content.len();
    for i in 0..contents {
        let node = content[i].clone();
        publish(parent_id, node);
    }
}

#[ic_cdk_macros::update]
fn publish(parent_id: [u8; 32], content: ContentNode) {
    assert_ne!(content.node_type, NodeType::ORG);
    create_node(
        content.id,
        parent_id,
        content.node_type,
        content.reference_of,
    );
    if content.node_type == NodeType::ASSET {
        // Perform additional logic for asset node
    }
}

#[ic_cdk_macros::update]
fn create_node(id: [u8; 32], parent_id: [u8; 32], node_type: NodeType, reference_of: [u8; 32]) {
    let owner = ic_cdk::caller();
    let id_hash = Hash::from_slice(&id);
    let parent_id_hash = Hash::from_slice(&parent_id);
    let reference_of_hash = Hash::from_slice(&reference_of);

    if nodes.contains_key(&id_hash) {
        // Handle node already exists error
    }

    if !nodes.contains_key(&parent_id_hash) {
        // Handle parent node does not exist error
    }

    if node_type == NodeType::ASSET {
        if id == [0u8; 32] || reference_of != [0u8; 32] {
            // Handle invalid params error
        }
    } else {
        // Handle other node types
    }

    let token_id = total_supply + 1;
    _safe_mint_with_parent(owner, nodes[&parent_id_hash].token, token_id);
    total_supply += 1;

    let node = Node {
        token: token_id,
        node_type,
        id: id_hash,
        reference_of: reference_of_hash,
        uri: String::new(),
        access_auth: ic_cdk::caller(),
        reference_auth: ic_cdk::caller(),
    };

    nodes.insert(id_hash, node);
    token_to_id.insert(token_id, id_hash);
    nodes_created
        .entry(owner)
        .and_modify(|e| *e += 1)
        .or_insert(1);
}

#[ic_cdk_macros::update]
fn move_node(id: [u8; 32], new_parent_id: [u8; 32]) {
    let id_hash = Hash::from_slice(&id);
    let new_parent_id_hash = Hash::from_slice(&new_parent_id);

    if !nodes.contains_key(&new_parent_id_hash) {
        // Handle new parent node does not exist error
    }

    let token = nodes[&id_hash].token;
    let new_parent = nodes[&new_parent_id_hash].token;
    let parent_id = token_to_id[&parent_of(token)];
    let parent = parent_of(token);
    let node_index = _index_in_children_array[&token];
    let children = _children_of[&parent].len();

    for i in node_index..children - 1 {
        // Move logic
    }

    _children_of.get_mut(&parent).unwrap().pop();
    _parent_of.insert(token, new_parent);
    _children_of.get_mut(&new_parent).unwrap().push(token);
    _index_in_children_array.insert(token, _children_of[&new_parent].len() - 1);
}

#[ic_cdk_macros::update]
fn set_access_auth(id: [u8; 32], access_auth: Principal) {
    let id_hash = Hash::from_slice(&id);
    if !nodes.contains_key(&id_hash) {
        // Handle node does not exist error
    }
    nodes.get_mut(&id_hash).unwrap().access_auth = access_auth;
}

#[ic_cdk_macros::update]
fn set_reference_auth(id: [u8; 32], reference_auth: Principal) {
    let id_hash = Hash::from_slice(&id);
    if !nodes.contains_key(&id_hash) {
        // Handle node does not exist error
    }
    let node_type = nodes[&id_hash].node_type;
    if node_type == NodeType::REFERENCE {
        // Handle invalid node type error
    }
    nodes.get_mut(&id_hash).unwrap().reference_auth = reference_auth;
}

#[ic_cdk_macros::update]
fn set_uri(id: [u8; 32], uri: String) {
    let id_hash = Hash::from_slice(&id);
    if !nodes.contains_key(&id_hash) {
        // Handle node does not exist error
    }
    let node_type = nodes[&id_hash].node_type;
    if node_type == NodeType::REFERENCE {
        // Handle invalid node type error
    }
    nodes.get_mut(&id_hash).unwrap().uri = uri;
}

#[ic_cdk_macros::query]
fn auth(id: [u8; 32], user: Principal) -> bool {
    let id_hash = Hash::from_slice(&id);
    if !nodes.contains_key(&id_hash) {
        // Handle node does not exist error
    }
    let is_authorized = true;
    if nodes[&id_hash].node_type == NodeType::REFERENCE {
        // Handle reference node authorization logic
    }
    _auth(id_hash, user, false) && is_authorized
}

fn _auth(id: Hash, user: Principal, rejected: bool) -> bool {
    let access_auth = nodes[&id].access_auth;
    let parent = token_to_id[&parent_of(nodes[&id].token)];
    if access_auth.is_none() {
        // Handle access auth not set error
    } else {
        // Handle other cases
    }
}

#[ic_cdk_macros::query]
fn get_node_by_token(token: u256) -> Option<Node> {
    let id = token_to_id[&token];
    nodes.get(&id).cloned()
}

```

## enum NodeType
```rust
enum NodeType {
    ORG,
    REFERENCE,
    ASSET,
}
```
`NodeType`は、等号比較で使うので、`PartialEQ`と`EQ`を実装しておきましょう。
また、Stable Structuresで使うので、`serde`をインストールして、マクロをいくつか追加します。
```bash
cargo add serde
```

`enum`の値を全部大文字にすると`clippy`に怒られるので、`Capitalize`しておきます。

修正後の`NodeType`
```rust
use candid::{CandidType, Decode, Deserialize, Encode, Principal};

#[derive(PartialEq, Eq, CandidType, Deserialize, Clone, Debug)]
enum NodeType {
    Org,
    Reference,
    Asset,
}
```

## struct ContentNode
`ContentNode`は、`Asset Node`もしくは`Reference Node`の中身を格納する構造体です。
```rust
struct ContentNode {
    id: [u8; 32],
    node_type: NodeType,
    reference_of: [u8; 32],
    uri: String,
}
```
`Asset Node`の場合は、`uri`が必須、`reference_of`は不要。
`Reference Node`の場合は、`uri`は不要、`reference_of`は必須。
なので、どちらも`Option`で定義しましょう。
また、関数の引数で使うので、`CandidType`と`Deserialize`を`derive`で指定します。
```rust
#[derive(CandidType, Deserialize)]
pub struct ContentNode {
    pub id: [u8; 32],
    pub node_type: NodeType,
    pub reference_of: Option<[u8; 32]>,
    pub uri: Option<String>,
}
```

## struct Node
```rust
struct Node {
    token: u256,
    node_type: NodeType,
    id: [u8; 32],
    reference_of: [u8; 32],
    uri: String,
    access_auth: Principal,
    reference_auth: Principal,
}
```
`Node`は、`StableBTreeMap`に`id`をキーにして格納するので、`id`フィールドは削除します。

`Solidity`の実装では、`Node`は`NFT`でしたが、`Rust`では、わざわざ`NFT`にする必要はないので、`token`フィールドは削除します。

認証に関する`access_auth`と`reference_auth`は、今の段階では要件が不明なので、一旦削除します。

`ContentNode`と同様に、`reference_of`と`uri`に`Option`をつけます。

修正後の`Node`
```rust
#[derive(CandidType, Deserialize, Clone, Debug)]
pub struct Node {
    pub node_type: NodeType,
    pub reference_of: Option<[u8; 32]>,
    pub uri: Option<String>,
}
```
`Node`は、`StableBTreeMap`に値として格納するので、下記のように`Storable`を実装します。
```rust
use std::{borrow::Cow, cell::RefCell};
use candid::{CandidType, Decode, Deserialize, Encode, Principal};
use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;

impl Storable for Node {
    fn to_bytes(&self) -> Cow<[u8]> {
        Cow::Owned(Encode!(self).unwrap())
    }

    fn from_bytes(bytes: Cow<[u8]>) -> Self {
        Decode!(bytes.as_ref(), Self).unwrap()
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 200,
        is_fixed_size: false,
    };
}
```

## Ids
ある`Node`の子供の`id: [u8; 32]`を管理するために`struct Ids`を定義します。
```rust
#[derive(CandidType, Deserialize)]
pub struct Ids(pub Vec<[u8; 32]>);
```
`Ids`は、`StableBTreeMap`に格納するので、`Storable`も実装します。
```rust
impl Storable for Ids {
    fn to_bytes(&self) -> Cow<[u8]> {
        Cow::Owned(Encode!(&self.0).unwrap())
    }

    fn from_bytes(bytes: Cow<[u8]>) -> Self {
        Self(Decode!(bytes.as_ref(), Vec<[u8; 32]>).unwrap())
    }

    const BOUND: Bound = Bound::Unbounded;
}
```

## Errors
下記の`Solidity`の例外定義が`Rust`で実装されていません。
```solidity
error NotAuthorized();
error InvalidParams();
error NodeDoesNotExist();
error NodeAlreadyExists();
```
これを`thiserror`を使って実装しましょう。`thiserror`をインストールします。
```
cargo add thiserror
```

`Solidity`のエラー定義を選択し、インラインチャットで下記のプロンプトを実行します。
```
/fix Rustのenumに変換して
```
下記のコードが出力されました。
```rust
pub enum ContentGraphError {
    NotAuthorized,
    InvalidParams,
    NodeDoesNotExist,
    NodeAlreadyExists,
}
```
このエラー定義を選択し、インラインチャットで下記のプロンプトを実行します。
```
/fix thiserrorを使って
```
下記のコードが出力されました。
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ContentGraphError {
    #[error("Not authorized")]
    NotAuthorized,
    #[error("Invalid parameters")]
    InvalidParams,
    #[error("Node does not exist")]
    NodeDoesNotExist,
    #[error("Node already exists")]
    NodeAlreadyExists,
}
```

## types.rs
`NodeType`、`ContentNode`、`Node`、`Ids`、`ContentGraphError`は、`types.rs`にまとめて定義することにしましょう。

```rust
use std::borrow::Cow;

use candid::{CandidType, Decode, Deserialize, Encode, Principal};
use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;
use thiserror::Error;

#[derive(PartialEq, Eq, CandidType, Deserialize, Clone, Debug)]
pub enum NodeType {
    Org,
    Reference,
    Asset,
}

#[derive(CandidType, Deserialize)]
pub struct ContentNode {
    pub id: [u8; 32],
    pub node_type: NodeType,
    pub reference_of: Option<[u8; 32]>,
    pub uri: Option<String>,
}

#[derive(CandidType, Deserialize, Clone, Debug)]
pub struct Node {
    pub node_type: NodeType,
    pub reference_of: Option<[u8; 32]>,
    pub uri: Option<String>,
}

impl Storable for Node {
    fn to_bytes(&self) -> Cow<[u8]> {
        Cow::Owned(Encode!(self).unwrap())
    }

    fn from_bytes(bytes: Cow<[u8]>) -> Self {
        Decode!(bytes.as_ref(), Self).unwrap()
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 200,
        is_fixed_size: false,
    };
}

#[derive(CandidType, Deserialize)]
pub struct Ids(pub Vec<[u8; 32]>);

impl Storable for Ids {
    fn to_bytes(&self) -> Cow<[u8]> {
        Cow::Owned(Encode!(&self.0).unwrap())
    }

    fn from_bytes(bytes: Cow<[u8]>) -> Self {
        Self(Decode!(bytes.as_ref(), Vec<[u8; 32]>).unwrap())
    }

    const BOUND: Bound = Bound::Unbounded;
}

#[derive(Error, Debug, CandidType, Deserialize)]
pub enum ContentGraphError {
    #[error("Not authorized")]
    NotAuthorized,
    #[error("Invalid parameters")]
    InvalidParams,
    #[error("Node does not exist")]
    NodeDoesNotExist,
    #[error("Node already exists")]
    NodeAlreadyExists,
}
```

## struct ContentGraph
```rust
struct ContentGraph {
    total_supply: u256,
    nodes: HashMap<[u8; 32], Node>,
    nodes_created: HashMap<Principal, u256>,
    token_to_id: HashMap<u256, [u8; 32]>,
    identity: IIdentityRegistry,
}
```
`ContentGraph`は、`Solidity`では、`contract`にマッピングされていたものです。ステート変数は、ICP上では、ThreadLocal変数にマッピングします。

`total_supply`と`token_to_id`も、`NFT`でないなら必要ないので、削除します。

`identity`は、`use crate::identity_registry`で置き換えます。

ノードの所有者、ノードの親、ノードの子供を格納するThreadLocal変数を追加します。

ThreadLocal変数に関しては、`identity_registry.rs`とコードが重複するので、`state.rs`にまとめて定義することにします。

`Github Copilot`に頼んで、ThreadLocal変数にアクセスするコードを自動生成してもらいました。

```rust
use std::cell::RefCell;

use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};

use crate::types::{Ids, Node};

thread_local! {
    static MEM_MGR: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static REGISTERED: RefCell<StableBTreeMap<Principal, bool, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(0))))
    );

    static INTERMEDIATE_TO_ROOT: RefCell<StableBTreeMap<Principal, Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(1))))
    );

    static REGISTRY_EXPIRY: RefCell<StableBTreeMap<(Principal, Principal), u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(2))))
    );

    static NODES: RefCell<StableBTreeMap<[u8; 32], Node, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(3))))
    );

    static NODES_CREATED: RefCell<StableBTreeMap<Principal, u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(4))))
    );

    static OWNERS: RefCell<StableBTreeMap<[u8; 32], Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(5))))
    );

    static PARENTS: RefCell<StableBTreeMap<[u8; 32], [u8; 32], VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(6))))
    );

    static CHILDRENS: RefCell<StableBTreeMap<[u8; 32], Ids, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(7))))
    );
}

pub fn is_registered(root: Principal) -> bool {
    REGISTERED.with_borrow(|registered| registered.contains_key(&root))
}

pub fn set_registered(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.insert(root, true));
}

pub fn remove_registered(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
}

pub fn get_intermediate_to_root(intermediate: Principal) -> Option<Principal> {
    INTERMEDIATE_TO_ROOT.with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
}

pub fn set_intermediate_to_root(intermediate: Principal, root: Principal) {
    INTERMEDIATE_TO_ROOT
        .with_borrow_mut(|intermediate_to_root| intermediate_to_root.insert(intermediate, root));
}

pub fn remove_intermediate_to_root(intermediate: Principal) {
    INTERMEDIATE_TO_ROOT
        .with_borrow_mut(|intermediate_to_root| intermediate_to_root.remove(&intermediate));
}

pub fn get_registry_expiry(root: Principal, intermediate: Principal) -> Option<u64> {
    REGISTRY_EXPIRY.with_borrow(|registry_expiry| registry_expiry.get(&(root, intermediate)))
}

pub fn set_registry_expiry(root: Principal, intermediate: Principal, expiry: u64) {
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}

pub fn remove_registry_expiry(root: Principal, intermediate: Principal) {
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.remove(&(root, intermediate)));
}

pub fn get_node(id: &[u8; 32]) -> Option<Node> {
    NODES.with_borrow(|nodes| nodes.get(id))
}

pub fn register_node(id: [u8; 32], node: Node) {
    NODES.with_borrow_mut(|nodes| nodes.insert(id, node));
}

pub fn get_nodes_created(owner: &Principal) -> u64 {
    NODES_CREATED.with_borrow(|nodes_created| nodes_created.get(owner).unwrap_or_default())
}

pub fn increment_nodes_created(owner: Principal) {
    NODES_CREATED.with_borrow_mut(|nodes_created| {
        let count = nodes_created.get(&owner).unwrap_or_default();
        nodes_created.insert(owner, count + 1);
    });
}

pub fn get_owner(id: &[u8; 32]) -> Option<Principal> {
    OWNERS.with_borrow(|owners| owners.get(id))
}

pub fn set_owner(id: [u8; 32], owner: Principal) {
    OWNERS.with_borrow_mut(|owners| owners.insert(id, owner));
}

pub fn get_parent(id: &[u8; 32]) -> Option<[u8; 32]> {
    PARENTS.with_borrow(|parents| parents.get(id))
}

pub fn set_parent(id: [u8; 32], parent: [u8; 32]) {
    PARENTS.with_borrow_mut(|parents| parents.insert(id, parent));
}

pub fn get_children(id: &[u8; 32]) -> Option<Ids> {
    CHILDRENS.with_borrow(|children| children.get(id))
}

pub fn set_children(id: [u8; 32], children: Ids) {
    CHILDRENS.with_borrow_mut(|childrens| childrens.insert(id, children));
}

```

これで、`content_graph.rs`としては次の3行で済むようになりました。
```rust
use crate::identity_registry;
use crate::state;
use crate::types::{ContentGraphError, ContentNode, Node, NodeType, Ids};
```

## fn require_node_not_exists
`Node`がまだ、存在しないことを確認する`require_node_not_exists`関数を作成しましょう。
`fn require_node_not`まで入力すると残りは、`Github Copilot`が作ってくれました。
```rust
fn require_node_not_exists(id: &[u8; 32]) -> Result<(), ContentGraphError> {
    if state::get_node(id).is_some() {
        Err(ContentGraphError::NodeAlreadyExists)
    } else {
        Ok(())
    }
}
```

## fn register_org_node
`Org Node`を登録する`register_org_node`関数を作成しましょう。
`fn register_org`まで入力すると、関数の中身を作ってくれたのですが、引数に`owner`が足りないので、`owner`は自分で追加すると残りは、`Github Copilot`が作ってくれました。
```rust
fn register_org_node(id: [u8; 32], owner: Principal) -> Result<(), ContentGraphError> {
    let node = Node {
        node_type: NodeType::Org,
        reference_of: None,
        uri: None,
    };
    state::register_node(id, node);
    state::increment_nodes_created(owner);
    Ok(())
}
```
惜しい。`require_node_not_exists()`が呼び出されていません。関数のボディの最初に、`re`まで入力したら、残りは`Github Copilot`が補完してくれました。
```rust
fn register_org_node(id: [u8; 32], owner: Principal) -> Result<(), ContentGraphError> {
    require_node_not_exists(&id)?;
    let node = Node {
        node_type: NodeType::Org,
        reference_of: None,
        uri: None,
    };
    state::register_node(id, node);
    state::increment_nodes_created(owner);
    Ok(())
}
```

## fn register_asset_node
`Asset Node`を登録する`register_asset_node`関数を作成しましょう。
`Github Copilot`の補完+自分入力で、関数のシグニチャまで入力し、残りを`Github Copilot`に補完してもらいました。
```rust
fn register_asset_node(
    parent: [u8; 32],
    id: [u8; 32],
    uri: String,
    owner: Principal,
) -> Result<(), ContentGraphError> {
    require_node_not_exists(&id)?;
    let node = Node {
        node_type: NodeType::Asset,
        reference_of: Some(parent),
        uri: Some(uri),
    };
    state::register_node(id, node);
    state::increment_nodes_created(owner);
    Ok(())
}
```
`reference_of`にセットするのは`None`なので、手動で修正します。
`state::set_parent()`の呼び出しがないので、手動で追加します。
```rust
fn register_asset_node(
    parent: [u8; 32],
    id: [u8; 32],
    uri: String,
    owner: Principal,
) -> Result<(), ContentGraphError> {
    require_node_not_exists(&id)?;
    let node = Node {
        node_type: NodeType::Asset,
        reference_of: None,
        uri: Some(uri),
    };
    state::register_node(id, node);
    state::increment_nodes_created(owner);
    state::set_parent(id, parent);
    Ok(())
}
```

## fn publish
いよいよ、コンテンツ全体を登録する`publish`関数を作成します。
`Github Copilot`の補完+自分入力で、関数のシグニチャまで入力し、残りを`Github Copilot`に補完してもらいました。
```rust
fn publish(parent: [u8; 32], content_nodes: Vec<ContentNode>) -> Result<(), ContentGraphError> {
    let owner = identity_registry::get_owner(&parent).ok_or(ContentGraphError::NodeNotFound)?;
    for content_node in content_nodes {
        match content_node.node_type {
            NodeType::Org => register_org_node(content_node.id, owner)?,
            NodeType::Asset => register_asset_node(parent, content_node.id, content_node.uri.unwrap(), owner)?,
            NodeType::Reference => register_reference_node(parent, content_node.id, content_node.reference_of.unwrap(), owner)?,
        }
    }
    Ok(())
}
```
まず、`let owner`の行を削除して、`let caller = ic_cdk`まで入力します。残りは`Github Copilot`が補完しました。
```rust
let caller = ic_cdk::caller();
```
次の行で`let`まで入力すると、残りは`Github Copilot`が補完してくれました。
```rust
let owner = identity_registry::who_is(caller).ok_or(ContentGraphError::NotRegistered)?;
```
例外の種類が間違っているので、`NotAuthorized`に変更します。
```rust
let owner = identity_registry::who_is(caller).ok_or(ContentGraphError::NotAuthorized)?;
```
次の行で`register`まで入力すると、残りは`Github Copilot`が補完してくれました。
```rust
register_org_node(parent, owner)?;
```

子供の`id`を保存するための`children`変数を用意します。
```rust
let mut children = Ids(vec![]);
```

`content_nodes`のループで、`NodeType::ORG`が出現することはないので、エラーを返します。
```rust
NodeType::ORG => return Err(ContentGraphError::InvalidParams),
```
子供の`id`を`children`に追加します。
```rust
NodeType::Asset => {
    register_asset_node(parent, content_node.id, content_node.uri.unwrap(), owner)?;
    children.0.push(content_node.id);
}
NodeType::Reference => {
    register_reference_node(
        parent,
        content_node.id,
        content_node.reference_of.unwrap(),
        owner,
    )?;
    children.0.push(content_node.id);
}
```

`content_node.uri.unwrap()`が雑なので、`unwrap`にカーソルを当てて、プロンプトで`ok_orを使って`とお願いして修正してもらいます。
同様に`reference_of`も修正します。
```rust
NodeType::Asset => {
    let uri = content_node.uri.ok_or(ContentGraphError::InvalidParams)?;
    register_asset_node(parent, content_node.id, uri, owner)?;
    children.0.push(content_node.id);
}
NodeType::Reference => {
    let reference_of = content_node.reference_of.ok_or(ContentGraphError::InvalidParams)?;
    register_reference_node(parent, content_node.id, reference_of, owner)?;
    children.0.push(content_node.id);
}
```
最後に`children`を保存します。
```rust
state::set_children(parent, children);
```

修正後のコード
```rust
#[ic_cdk::update]
fn publish(parent: [u8; 32], content_nodes: Vec<ContentNode>) -> Result<(), ContentGraphError> {
    let caller = ic_cdk::caller();
    let owner = identity_registry::who_is(caller).ok_or(ContentGraphError::NotAuthorized)?;
    register_org_node(parent, owner)?;
    let mut children = Ids(vec![]);
    for content_node in content_nodes {
        match content_node.node_type {
            NodeType::Org => return Err(ContentGraphError::InvalidParams),
            NodeType::Asset => {
                let uri = content_node.uri.ok_or(ContentGraphError::InvalidParams)?;
                register_asset_node(parent, content_node.id, uri, owner)?;
                children.0.push(content_node.id);
            }
            NodeType::Reference => {
                let reference_of = content_node
                    .reference_of
                    .ok_or(ContentGraphError::InvalidParams)?;
                register_reference_node(parent, content_node.id, reference_of, owner)?;
                children.0.push(content_node.id);
            }
        }
    }
    state::set_children(parent, children);
    Ok(())
}
```