Rust Holochain数据完整性类型库holochain_integrity_types的使用,支持分布式应用数据模型验证与类型安全
holochain_integrity_types
这个crate提供了Holochain应用开发者在完整性zome代码中需要的类型,除此之外没有其他内容。
这个crate被有意保持得尽可能最小化,因为它通常作为依赖项包含在Holochain Zomes中,而Zomes是以Wasm块的形式分布的。
贡献
Holochain是一个开源项目。我们欢迎各种形式的参与,并正在积极扩大接受参与的范围。请参阅我们的贡献指南,了解我们关于参与社区的一般实践和协议,以及关于代码格式化、测试实践、持续集成等方面的具体期望。
许可证
版权所有 © 2019 - 2024, Holochain Foundation
这个程序是免费软件:你可以重新分发和/或修改它,遵循LICENSE文件(Apache 2.0)中提供的许可证条款。这个程序分发时希望它有用,但没有任何保证;甚至没有适销性或特定用途适用性的隐含保证。
安装
在你的项目目录中运行以下Cargo命令:
cargo add holochain_integrity_types
或者在你的Cargo.toml中添加以下行:
holochain_integrity_types = "0.5.4"
完整示例代码
下面是一个使用holochain_integrity_types的完整示例,展示了如何定义数据模型并进行验证:
use holochain_integrity_types::*;
use hdi::prelude::*;
// 定义一个简单的博客文章Entry类型
#[entry_def]
#[derive(Clone)]
pub struct BlogPost {
pub title: String,
pub content: String,
pub author: AgentPubKey,
pub timestamp: Timestamp,
}
// 实现EntryDefRegistration trait来注册Entry类型
impl EntryDefRegistration for BlogPost {
fn entry_def() -> EntryDef {
EntryDef {
id: "blog_post".into(),
visibility: EntryVisibility::Public,
required_validations: 3.into(),
required_validation_package: RequiredValidationType::Entry,
}
}
}
// 定义链接类型
#[link_type]
pub enum BlogLinkType {
AuthorToPosts,
}
// 定义验证回调
#[hdk_extern]
pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
match op {
Op::StoreEntry { entry, .. } => {
match entry {
Entry::App(app_entry) => {
if app_entry.entry_def_id == BlogPost::entry_def().id {
// 验证博客文章数据
let post: BlogPost = app_entry.try_into()?;
if post.title.is_empty() {
return Ok(ValidateCallbackResult::Invalid(
"Title cannot be empty".to_string(),
));
}
if post.content.len() < 10 {
return Ok(ValidateCallbackResult::Invalid(
"Content too short".to_string(),
));
}
}
}
_ => (),
}
}
Op::RegisterCreateLink { .. } => {
// 验证链接创建
}
Op::RegisterDeleteLink { .. } => {
// 验证链接删除
}
_ => (),
}
Ok(ValidateCallbackResult::Valid)
}
#[hdk_extern]
pub fn init(_: ()) -> ExternResult<InitCallbackResult> {
Ok(InitCallbackResult::Pass)
}
这个示例展示了:
- 定义一个BlogPost Entry类型
- 实现EntryDefRegistration trait来注册Entry类型
- 定义链接类型
- 实现验证回调来确保数据完整性
- 基本的初始化函数
使用holochain_integrity_types可以帮助你在分布式应用中确保数据模型的类型安全和完整性验证。
扩展示例代码
以下是一个更完整的示例,展示了如何使用holochain_integrity_types实现一个简单的社交应用:
use holochain_integrity_types::*;
use hdi::prelude::*;
// 定义用户个人资料Entry类型
#[entry_def]
#[derive(Clone)]
pub struct UserProfile {
pub username: String,
pub bio: String,
pub avatar_hash: Option<EntryHash>,
}
// 实现EntryDefRegistration
impl EntryDefRegistration for UserProfile {
fn entry_def() -> EntryDef {
EntryDef {
id: "user_profile".into(),
visibility: EntryVisibility::Public,
required_validations: 3.into(),
required_validation_package: RequiredValidationType::Entry,
}
}
}
// 定义帖子Entry类型
#[entry_def]
#[derive(Clone)]
pub struct SocialPost {
pub content: String,
pub creator: AgentPubKey,
pub created_at: Timestamp,
pub tags: Vec<String>,
}
// 实现EntryDefRegistration
impl EntryDefRegistration for SocialPost {
fn entry_def() -> EntryDef {
EntryDef {
id: "social_post".into(),
visibility: EntryVisibility::Public,
required_validations: 3.into(),
required_validation_package: RequiredValidationType::Entry,
}
}
}
// 定义链接类型
#[link_type]
pub enum SocialLinkType {
CreatorToPosts,
TagToPosts,
AgentToProfile,
}
// 实现验证回调
#[hdk_extern]
pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
match op {
Op::StoreEntry { entry, .. } => {
match entry {
Entry::App(app_entry) => {
if app_entry.entry_def_id == UserProfile::entry_def().id {
let profile: UserProfile = app_entry.try_into()?;
if profile.username.is_empty() {
return Ok(ValidateCallbackResult::Invalid(
"Username cannot be empty".to_string(),
));
}
if profile.bio.len() > 500 {
return Ok(ValidateCallbackResult::Invalid(
"Bio too long".to_string(),
));
}
} else if app_entry.entry_def_id == SocialPost::entry_def().id {
let post: SocialPost = app_entry.try_into()?;
if post.content.is_empty() {
return Ok(ValidateCallbackResult::Invalid(
"Post content cannot be empty".to_string(),
));
}
if post.content.len() > 1000 {
return Ok(ValidateCallbackResult::Invalid(
"Post too long".to_string(),
));
}
}
}
_ => (),
}
}
Op::RegisterCreateLink {
base_address,
target_address,
link_type,
tag,
..
} => {
// 验证链接创建逻辑
}
Op::RegisterDeleteLink { .. } => {
// 验证链接删除逻辑
}
_ => (),
}
Ok(ValidateCallbackResult::Valid)
}
// 初始化函数
#[hdk_extern]
pub fn init(_: ()) -> ExternResult<InitCallbackResult> {
Ok(InitCallbackResult::Pass)
}
// 创建用户个人资料的函数
#[hdk_extern]
pub fn create_profile(username: String, bio: String) -> ExternResult<EntryHash> {
let profile = UserProfile {
username,
bio,
avatar_hash: None,
};
create_entry(profile)
}
// 创建帖子的函数
#[hdk_extern]
pub fn create_post(content: String, tags: Vec<String>) -> ExternResult<EntryHash> {
let post = SocialPost {
content,
creator: agent_info()?.agent_latest_pubkey,
created_at: sys_time()?,
tags,
};
create_entry(post)
}
这个扩展示例展示了:
- 定义用户个人资料和社交帖子两种Entry类型
- 实现对应的EntryDefRegistration
- 定义更丰富的链接类型
- 更详细的验证逻辑
- 提供了创建个人资料和帖子的实用函数
- 展示了如何获取代理信息和系统时间等常用操作
基于提供的内容,我将整理出完整的holochain_integrity_types使用示例。首先展示内容中已有的示例,然后给出一个完整的博客应用demo。
内容中提供的示例
定义记录类型
use holochain_integrity_types::EntryDef;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlogPost {
pub title: String,
pub content: String,
pub author: String,
pub timestamp: u64,
}
impl EntryDef for BlogPost {
fn entry_def() -> EntryDef {
EntryDef {
id: "blog_post".into(),
visibility: EntryVisibility::Public,
required_validations: 5.into(),
required_validation_type: RequiredValidationType::Full,
}
}
}
定义链接类型
use holochain_integrity_types::LinkType;
pub enum BlogLinkTypes {
AuthorToPosts,
PostToComments,
}
impl LinkType for BlogLinkTypes {
fn try_from_scope_string(scope: &str) -> Result<Self, WasmError> {
match scope {
"author_to_posts" => Ok(Self::AuthorToPosts),
"post_to_comments" => Ok(Self::PostToComments),
_ => Err(WasmError::Guest(format!("Unknown link type: {}", scope))),
}
}
fn as_scope_string(&self) -> String {
match self {
Self::AuthorToPosts => "author_to_posts".to_string(),
Self::PostToComments => "post_to_comments".to_string(),
}
}
}
完整博客应用示例
//! 完整的Holochain博客应用示例
use holochain_integrity_types::*;
use serde::{Serialize, Deserialize};
// ========== 数据结构定义 ==========
/// 博客文章结构体
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlogPost {
pub title: String,
pub content: String,
pub author: String,
pub timestamp: u64,
pub tags: Vec<String>,
}
/// 博客评论结构体
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlogComment {
pub content: String,
pub author: String,
pub timestamp: u64,
pub post_hash: String,
}
// ========== EntryDef实现 ==========
impl EntryDef for BlogPost {
fn entry_def() -> EntryDef {
EntryDef {
id: "blog_post".into(),
visibility: EntryVisibility::Public,
required_validations: 3.into(),
required_validation_type: RequiredValidationType::Full,
}
}
}
impl EntryDef for BlogComment {
fn entry_def() -> EntryDef {
EntryDef {
id: "blog_comment".into(),
visibility: EntryVisibility::Public,
required_validations: 3.into(),
required_validation_type: RequiredValidationType::Subset,
}
}
}
// ========== 数据验证实现 ==========
impl ValidateData<BlogPost> for BlogPost {
fn validate(&self) -> Result<(), String> {
if self.title.is_empty() {
return Err("标题不能为空".to_string());
}
if self.content.len() < 20 {
return Err("内容至少需要20个字符".to_string());
}
if self.author.is_empty() {
return Err("作者不能为空".to_string());
}
if self.timestamp == 0 {
return Err("无效的时间戳".to_string());
}
Ok(())
}
}
impl ValidateData<BlogComment> for BlogComment {
fn validate(&self) -> Result<(), String> {
if self.content.is_empty() {
return Err("评论内容不能为空".to_string());
}
if self.author.is_empty() {
return Err("评论作者不能为空".to_string());
}
if self.post_hash.is_empty() {
return Err("必须关联到一篇文章".to_string());
}
Ok(())
}
}
// ========== 链接类型定义 ==========
pub enum BlogLinkTypes {
AuthorToPosts, // 作者到文章的链接
PostToComments, // 文章到评论的链接
PostToTags, // 文章到标签的链接
TagToPosts, // 标签到文章的链接
}
impl LinkType for BlogLinkTypes {
fn try_from_scope_string(scope: &str) -> Result<Self, WasmError> {
match scope {
"author_to_posts" => Ok(Self::AuthorToPosts),
"post_to_comments" => Ok(Self::PostToComments),
"post_to_tags" => Ok(Self::PostToTags),
"tag_to_posts" => Ok(Self::TagToPosts),
_ => Err(WasmError::Guest(format!("未知的链接类型: {}", scope))),
}
}
fn as_scope_string(&self) -> String {
match self {
Self::AuthorToPosts => "author_to_posts".to_string(),
Self::PostToComments => "post_to_comments".to_string(),
Self::PostToTags => "post_to_tags".to_string(),
Self::TagToPosts => "tag_to_posts".to_string(),
}
}
}
// ========== Zome配置 ==========
pub fn blog_zome_info() -> ZomeInfo {
ZomeInfo {
name: ZomeName::new("blog_zome").unwrap(),
id: 0,
properties: Default::default(),
entry_defs: vec![
BlogPost::entry_def(),
BlogComment::entry_def(),
],
link_types: vec![],
}
}
// ========== 测试用例 ==========
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blog_post_validation() {
let valid_post = BlogPost {
title: "Rust Holochain指南".to_string(),
content: "这是一篇关于Holochain的详细指南...".to_string(),
author: "rustacean".to_string(),
timestamp: 1234567890,
tags: vec!["rust".to_string(), "holochain".to_string()],
};
assert!(valid_post.validate().is_ok());
let invalid_post = BlogPost {
title: "".to_string(), // 无效: 空标题
content: "短".to_string(), // 无效: 内容太短
author: "".to_string(), // 无效: 空作者
timestamp: 0, // 无效: 零时间戳
tags: vec![],
};
assert!(invalid_post.validate().is_err());
}
#[test]
fn test_link_type_conversion() {
let link = BlogLinkTypes::try_from_scope_string("post_to_comments").unwrap();
assert_eq!(link.as_scope_string(), "post_to_comments");
}
}
使用说明
- 这个完整示例展示了如何在Holochain中构建一个博客应用
- 包含了两种数据类型(BlogPost和BlogComment)的定义
- 实现了数据验证逻辑确保数据完整性
- 定义了多种链接类型来建立数据关系
- 包含了基本的测试用例
注意事项
- 确保在实际应用中使用最新的holochain_integrity_types版本
- 根据业务需求调整验证规则
- 在分布式环境中,所有节点必须使用相同的验证规则
- 考虑数据隐私和权限设置