From 89bde1dda8c70e2bfa2b0b3523fd9e7b12de1bdf Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Sat, 8 Feb 2025 19:48:47 +0100 Subject: [PATCH] * Re-did the mapping of DbAccountId so that we don't have to rely on derive macro FromJsonQueryResult that resulted in using JSON_TEXT in database column instead of VARCHAR * Tricky part I missed the first time was in impl of sea_orm::TryGetable::try_get_by where we should return Err TryGetError::Null in case the value is not present * Was otherwise throwing an exception whenever `parent` field (Option<DbAccountId>) was None when trying to persist the case in DB. --- src/entities/vault_account.rs | 100 ++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs index cfb7003..d04b02a 100644 --- a/src/entities/vault_account.rs +++ b/src/entities/vault_account.rs @@ -2,18 +2,19 @@ use crate::commands::{cesium, vault}; use crate::runtime_config::AccountId; use crate::utils::GcliError; use anyhow::anyhow; +use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; use sea_orm::prelude::StringLen; use sea_orm::ActiveValue::Set; +use sea_orm::PaginatorTrait; use sea_orm::QueryFilter; +use sea_orm::TryGetError; use sea_orm::{ ActiveModelBehavior, ColumnTrait, DbErr, DeriveEntityModel, DerivePrimaryKey, EnumIter, Linked, ModelTrait, QueryOrder, RelationDef, RelationTrait, TryFromU64, }; use sea_orm::{ActiveModelTrait, ConnectionTrait, PrimaryKeyTrait}; use sea_orm::{DeriveActiveEnum, EntityTrait}; -use sea_orm::{FromJsonQueryResult, PaginatorTrait}; -use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Display; @@ -112,41 +113,58 @@ impl Display for Model { } } -/// Necessary to create a wrapper over AccountId32 to implement sea-orm traits -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +/// Necessary to create a wrapper over AccountId to implement sea-orm traits +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DbAccountId(pub AccountId); -impl FromStr for DbAccountId { - type Err = GcliError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - AccountId::from_str(s) - .map(DbAccountId) - .map_err(|_| GcliError::Input("Invalid AccountId format".to_string())) +/// All the next methods are necessary to support the proper mapping of the DbAccountId from/to String in DB and +/// to allow using it as primaryKey +impl sea_orm::sea_query::Nullable for DbAccountId { + fn null() -> Value { + Value::String(None) } } -impl Display for DbAccountId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl sea_orm::TryGetable for DbAccountId { + /// Had to really pay attention to return proper TryGetError type when value not present => TryGetError::Null + /// + /// as otherwise, when using `Option<DbAccountId>` with a None value it was crashing (when no "parent") + fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> { + let value: String = res + .try_get_by(idx) + .map_err(|e| TryGetError::Null(e.to_string()))?; + Ok(DbAccountId(AccountId::from_str(&value).map_err(|e| { + TryGetError::DbErr(DbErr::Custom(e.to_string())) + })?)) } } -impl From<AccountId> for DbAccountId { - fn from(account_id: AccountId) -> Self { - DbAccountId(account_id) +impl sea_orm::sea_query::ValueType for DbAccountId { + fn try_from(v: Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { + match v { + Value::String(Some(value)) => Ok(DbAccountId( + AccountId::from_str(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr)?, + )), + _ => Err(sea_orm::sea_query::ValueTypeErr), + } } -} -impl From<DbAccountId> for AccountId { - fn from(db_account_id: DbAccountId) -> Self { - db_account_id.0 + fn type_name() -> String { + stringify!(DbAccountId).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::String + } + + fn column_type() -> ColumnType { + ColumnType::String(StringLen::None) } } -impl From<String> for DbAccountId { - fn from(s: String) -> Self { - DbAccountId(AccountId::from_str(&s).expect("Invalid AccountId format")) +impl From<DbAccountId> for Value { + fn from(account_id: DbAccountId) -> Self { + Value::String(Some(Box::new(account_id.0.to_string()))) } } @@ -159,6 +177,40 @@ impl TryFromU64 for DbAccountId { } } +impl Display for DbAccountId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for DbAccountId { + type Err = GcliError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(DbAccountId( + AccountId::from_str(s).map_err(|e| GcliError::Input(e.to_string()))?, + )) + } +} + +impl From<String> for DbAccountId { + fn from(s: String) -> Self { + DbAccountId(AccountId::from_str(&s).expect("Invalid AccountId format")) + } +} + +impl From<AccountId> for DbAccountId { + fn from(account_id: AccountId) -> Self { + DbAccountId(account_id) + } +} + +impl From<DbAccountId> for AccountId { + fn from(db_account_id: DbAccountId) -> Self { + db_account_id.0 + } +} + /// Didn't want to pollute the keys::CryptoScheme enum with sea-orm specific derivations /// /// created a separate enum for the database with conversions between the two -- GitLab