作者:李大狗


系列简介:狗哥 Rust 学习笔记系列是大狗为对抗 Rust 陡峭的学习曲线而推出的 Rust 学习系列,具备如下原则:

  1. 循序渐进原则

按照阶梯法则(下一篇的难度是上一篇难度+1)原则进行设计,让学习 Rust 跟打游戏一样简单。

  1. 单一知识点原则

一篇文章只讲一个一个知识点,保证简单性与专注性。

  1. 实用原则

所有案例均是真实实践案例,实用性超强。

在之前的两篇文章中,我们分别介绍了:

将二者整合,我们可以得到 WeId 的链上生成 — 本地存储的完整闭环。这个项目我已推送到 Github 上并补充了 README,欢迎 STAR~

https://github.com/leeduckgo/weid-rust-sample

项目结构

  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. ├── README.md
  5. ├── bin # bin 中放置的是 diesel 二进制软件体
  6. ├── diesel # diesel 开头均为数据库相关
  7. ├── diesel_cli
  8. ├── diesel_derives
  9. ├── diesel_migrations
  10. ├── ethereum # 以太坊包,为之后本地生成-链上注册做准备
  11. ├── model # 同上
  12. ├── examples.db # sqlite 数据库
  13. ├── migrations # 数据库记录文件
  14. ├── src # 主文件
  15. | ├── main.rs
  16. | └── models.rs
  17. ├── weid-light-client # 解耦轻客户端,对接 weid-rest-service 服务
  18. └── target # 编译后的文件

当前流程

  1. 调用 WeIdGenerator,在链上注册WeIdentity

借助了reqwest库,详细使用方法见:

链上注册WeId与错误处理 | Rust 学习笔记(六)

weid-light-client/src/weid_generator.rs源码:

  1. use serde_json::{Value};
  2. use thiserror::Error;
  3. /// Provide an implementation for the default() method:
  4. /// https://doc.rust-lang.org/stable/core/default/trait.Default.html
  5. #[derive(Default)]
  6. pub struct WeIdGenerator{
  7. endpoint_url: String,
  8. weid: String,
  9. }
  10. impl WeIdGenerator{
  11. pub fn new(endpoint_url: String) -> WeIdGenerator {
  12. WeIdGenerator {endpoint_url, ..Default::default()}
  13. }
  14. /// String or &str?
  15. /// Ref: https://zhuanlan.zhihu.com/p/123278299
  16. /// 显然,这取决于很多因素,但是一般地,保守来讲,如果我们正在构建的API不需要拥有或者修改使用的文本,
  17. /// 那么应该使用&str而不是String。
  18. /// 等一下,但是如果这个API的调用者真的有一个String并且出于某些未知原因无法将其转换成&str呢?完全没有问题。
  19. /// Rust有一个超级强大的特性叫做deref coercing,这个特性能够允许把传进来的带有借用操作符的String引用,
  20. /// 也就是&String,在API执行之前转成&str。我们会在另一篇文章里介绍更多地相关细节。
  21. pub fn generate_local(&mut self, chain_id: i32, addr: &str) -> String {
  22. self.weid = "did:weid:".to_string() + &chain_id.to_string() + ":" + addr;
  23. // Ref: https://stackoverflow.com/questions/38304666/how-to-define-a-copyable-struct-containing-a-string
  24. // String is copyable, use .clone()
  25. // String is not implicitly copyable, because that would cause non-obvious memory allocations to occur
  26. self.weid.clone()
  27. }
  28. /// create weid online.
  29. pub fn create_weid_online(&self) -> Result<Value, GenerateWeIdError>{
  30. let response = self.call_create_weid()?;
  31. let resp = self.str_to_json(&response)?;
  32. Ok(resp)
  33. }
  34. fn str_to_json(&self, payload: &str) -> Result<Value, serde_json::Error> {
  35. serde_json::from_str(payload)
  36. }
  37. pub fn call_create_weid(&self) -> Result<String, reqwest::Error> {
  38. let mut url =self.endpoint_url.to_string();
  39. url += &"/weid/api/invoke".to_string();
  40. // ::blocking:: to block
  41. let response = reqwest::blocking::Client::new()
  42. .post(&url)
  43. .json(&serde_json::json!({
  44. "functionArg": {},
  45. "transactionArg": {},
  46. "v": "1.0.0",
  47. "functionName": "createWeId"
  48. }))
  49. .send()?
  50. .text();
  51. response
  52. }
  53. }
  54. /// multi error handle:
  55. /// https://my.oschina.net/jmjoy/blog/3190024
  56. #[derive(Error, Debug)]
  57. pub enum GenerateWeIdError {
  58. #[error("req error")]
  59. RequestError(#[from] reqwest::Error),
  60. #[error("parse error")]
  61. ParseError(#[from] serde_json::Error),
  62. }
  1. create_weid_online()函数结果处理,见src/main.rs
  1. fn gen_weid_online_and_save(weid_generator: WeIdGenerator) -> Result<Value, GenerateWeIdError>{
  2. let result = weid_generator.create_weid_online();
  3. match result {
  4. Ok(payload) => {
  5. // TODO
  6. //weid = payload |> to_weid
  7. //vec_weid = weid |> vec
  8. // save to local sqlite
  9. // info
  10. Ok(payload)
  11. },
  12. Err(e) => {
  13. info!("{}", e);
  14. Err(e)
  15. }
  16. }
  17. }
  1. 定义数据结构与数据 CRUD 操作

详见:

用 Sqlite 存储 WeId | Rust 学习笔记(五)

src/models.rs源码:

  1. use chrono::NaiveDateTime;
  2. #[cfg(test)]
  3. use diesel::debug_query;
  4. use diesel::insert_into;
  5. use diesel::prelude::*;
  6. use std::env;
  7. use dotenv::dotenv;
  8. #[cfg(test)]
  9. use diesel::sqlite::Sqlite;
  10. pub mod schema {
  11. diesel::table! {
  12. weids {
  13. id -> Integer,
  14. chain_id -> Integer,
  15. addr -> Text,
  16. created_at -> Timestamp,
  17. updated_at -> Timestamp,
  18. }
  19. }
  20. }
  21. use schema::weids;
  22. #[derive(Insertable)]
  23. #[table_name = "weids"]
  24. pub struct NewWeId {
  25. pub chain_id: i32,
  26. pub addr: String,
  27. }
  28. #[derive(Queryable, PartialEq, Debug)]
  29. pub struct WeId {
  30. pub id: i32,
  31. pub chain_id: i32,
  32. pub addr: String,
  33. pub created_at: NaiveDateTime,
  34. pub updated_at: NaiveDateTime,
  35. }
  36. pub fn insert_default_values(conn: &SqliteConnection) -> QueryResult<usize> {
  37. use schema::weids::dsl::*;
  38. insert_into(weids).default_values().execute(conn)
  39. }
  40. pub fn establish_connection() -> SqliteConnection {
  41. dotenv().ok();
  42. let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
  43. SqliteConnection::establish(&database_url)
  44. .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
  45. }
  46. pub fn save_weid(conn: &SqliteConnection, c_id: i32, address: &str) -> usize {
  47. let new_weid = NewWeId {chain_id: c_id, addr: address.to_string()};
  48. diesel::insert_into(weids::table)
  49. .values(&new_weid)
  50. .execute(conn)
  51. .expect("Error saving new weid")
  52. }
  1. 补充 2 中的 TODO 部分
  1. fn gen_weid_online_and_save(weid_generator: WeIdGenerator) -> Result<Value, GenerateWeIdError>{
  2. let result = weid_generator.create_weid_online();
  3. match result {
  4. Ok(payload) => {
  5. // str handle
  6. let weid_str: String =
  7. payload["respBody"]
  8. .to_string()
  9. .replace("\"", "");
  10. // str to vec
  11. let vec: Vec<&str> =
  12. weid_str
  13. .split(":")
  14. .collect();
  15. let chain_id: i32 = vec[2].parse().unwrap();
  16. let addr: &str = vec[3];
  17. // create data
  18. let sqlite_conn = models::establish_connection();
  19. models::save_weid(&sqlite_conn, chain_id, addr);
  20. info!("gen and save weid to local {}.", weid_str);
  21. Ok(payload)
  22. },
  23. Err(e) => {
  24. info!("{}", e);
  25. Err(e)
  26. }
  27. }
  28. }
  1. 在 main 函数中调用
  1. fn main(){
  2. pretty_env_logger::init();
  3. // 从环境变量中拿取 "WEID_URL"
  4. let url = env::var("WEID_URL").expect("DATABASE_URL must be set");
  5. let weid_generator = WeIdGenerator::new(url.to_string());
  6. gen_weid_online_and_save(weid_generator);
  7. }

运行项目

  1. 初始化数据库
  1. ./bin/diesel database reset
  1. 设置环境变量
  1. # 推荐使用direnv
  2. export DATABASE_URL="examples.db"
  3. export BACKEND="sqlite"
  4. export WEID_URL=<weid-rest-service url>
  1. 运行项目
  1. RUST_LOG=info cargo run

目前会在链上创建托管型WeId并存储在本地的Sqlite数据库中。

Rust 学习笔记系列| Part 7 - 图1

升级方向

  1. 添加「私钥不出域」创建WeId的方式;
  2. 实现 WeIdentity Document的同步操作;
  3. 目前项目开发需要先启动WeIdentity-Rest-Service,考虑设计 Mock 接口,方便开发。