LoginSignup
4
1

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

Posted at

はじめに

今回は、実際にContentGraph.solをICP上のRustに変換します。下記の続きになります。

ContentGraph.solの変換

VS Codeで、ContentGraph.solを開き、インラインチャットで下記のプロンプトを実行します。

ICP上で動くRustのCanisterに変換して

下記のコードが出力されたので、content_graph.rsに保存します。

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

enum NodeType {
    ORG,
    REFERENCE,
    ASSET,
}

NodeTypeは、等号比較で使うので、PartialEQEQを実装しておきましょう。
また、Stable Structuresで使うので、serdeをインストールして、マクロをいくつか追加します。

cargo add serde

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

修正後のNodeType

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の中身を格納する構造体です。

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で定義しましょう。
また、関数の引数で使うので、CandidTypeDeserializederiveで指定します。

#[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

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

Nodeは、StableBTreeMapidをキーにして格納するので、idフィールドは削除します。

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

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

ContentNodeと同様に、reference_ofuriOptionをつけます。

修正後のNode

#[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を実装します。

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を定義します。

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

Idsは、StableBTreeMapに格納するので、Storableも実装します。

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で実装されていません。

error NotAuthorized();
error InvalidParams();
error NodeDoesNotExist();
error NodeAlreadyExists();

これをthiserrorを使って実装しましょう。thiserrorをインストールします。

cargo add thiserror

Solidityのエラー定義を選択し、インラインチャットで下記のプロンプトを実行します。

/fix Rustのenumに変換して

下記のコードが出力されました。

pub enum ContentGraphError {
    NotAuthorized,
    InvalidParams,
    NodeDoesNotExist,
    NodeAlreadyExists,
}

このエラー定義を選択し、インラインチャットで下記のプロンプトを実行します。

/fix thiserrorを使って

下記のコードが出力されました。

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

NodeTypeContentNodeNodeIdsContentGraphErrorは、types.rsにまとめて定義することにしましょう。

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

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_supplytoken_to_idも、NFTでないなら必要ないので、削除します。

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

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

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

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

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行で済むようになりました。

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が作ってくれました。

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が作ってくれました。

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が補完してくれました。

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に補完してもらいました。

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()の呼び出しがないので、手動で追加します。

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に補完してもらいました。

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が補完しました。

let caller = ic_cdk::caller();

次の行でletまで入力すると、残りはGithub Copilotが補完してくれました。

let owner = identity_registry::who_is(caller).ok_or(ContentGraphError::NotRegistered)?;

例外の種類が間違っているので、NotAuthorizedに変更します。

let owner = identity_registry::who_is(caller).ok_or(ContentGraphError::NotAuthorized)?;

次の行でregisterまで入力すると、残りはGithub Copilotが補完してくれました。

register_org_node(parent, owner)?;

子供のidを保存するためのchildren変数を用意します。

let mut children = Ids(vec![]);

content_nodesのループで、NodeType::ORGが出現することはないので、エラーを返します。

NodeType::ORG => return Err(ContentGraphError::InvalidParams),

子供のidchildrenに追加します。

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も修正します。

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を保存します。

state::set_children(parent, children);

修正後のコード

#[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(())
}
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1