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