はじめに
今回は、実際に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
は、等号比較で使うので、PartialEQ
とEQ
を実装しておきましょう。
また、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
で定義しましょう。
また、関数の引数で使うので、CandidType
とDeserialize
をderive
で指定します。
#[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
は、StableBTreeMap
にid
をキーにして格納するので、id
フィールドは削除します。
Solidity
の実装では、Node
はNFT
でしたが、Rust
では、わざわざNFT
にする必要はないので、token
フィールドは削除します。
認証に関するaccess_auth
とreference_auth
は、今の段階では要件が不明なので、一旦削除します。
ContentNode
と同様に、reference_of
とuri
にOption
をつけます。
修正後の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
NodeType
、ContentNode
、Node
、Ids
、ContentGraphError
は、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_supply
とtoken_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),
子供のid
をchildren
に追加します。
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(())
}