identity_namespace.rs (5391B)
1 use harvestcircle_application::{BoxFuture, IdentityNamespaceRepository, IdentityPreferenceKey}; 2 use harvestcircle_domain::{PublicKey, SafeError, SafeErrorCode, SafeMessage}; 3 use sqlx::Row; 4 5 use crate::db::{corrupt_storage, map_transaction_error, storage_unavailable}; 6 use crate::{Database, HARVESTCIRCLE_PREFERENCE_VALUE_UTF8_BYTES}; 7 8 impl IdentityNamespaceRepository for Database { 9 fn get_value<'a>( 10 &'a self, 11 owner: PublicKey, 12 key: IdentityPreferenceKey, 13 ) -> BoxFuture<'a, Result<Option<String>, SafeError>> { 14 Box::pin(async move { 15 self.host() 16 .transaction(|transaction| { 17 Box::pin(async move { 18 let rows = sqlx::query( 19 "SELECT substr(CAST(preference_value AS BLOB), 1, 4097) AS value, \ 20 length(CAST(preference_value AS BLOB)) AS value_bytes \ 21 FROM account_preferences WHERE owner_public_key = ? \ 22 AND preference_key = ? LIMIT 2", 23 ) 24 .bind(owner.as_bytes().as_slice()) 25 .bind(encode_key(key)) 26 .fetch_all(&mut *transaction) 27 .await 28 .map_err(|_| corrupt_storage())?; 29 match rows.as_slice() { 30 [] => Ok(None), 31 [row] => { 32 let value = row 33 .try_get::<Vec<u8>, _>("value") 34 .map_err(|_| corrupt_storage())?; 35 let length = row 36 .try_get::<i64, _>("value_bytes") 37 .map_err(|_| corrupt_storage())?; 38 let length = 39 usize::try_from(length).map_err(|_| corrupt_storage())?; 40 if length == 0 41 || length > HARVESTCIRCLE_PREFERENCE_VALUE_UTF8_BYTES 42 || length != value.len() 43 { 44 return Err(corrupt_storage()); 45 } 46 String::from_utf8(value) 47 .map(Some) 48 .map_err(|_| corrupt_storage()) 49 } 50 _ => Err(corrupt_storage()), 51 } 52 }) 53 }) 54 .await 55 .map_err(map_transaction_error) 56 }) 57 } 58 59 fn set_value<'a>( 60 &'a self, 61 owner: PublicKey, 62 key: IdentityPreferenceKey, 63 value: &'a str, 64 ) -> BoxFuture<'a, Result<(), SafeError>> { 65 Box::pin(async move { 66 if value.is_empty() 67 || value.len() > HARVESTCIRCLE_PREFERENCE_VALUE_UTF8_BYTES 68 || value.chars().any(char::is_control) 69 { 70 return Err(invalid_preference()); 71 } 72 let value = value.to_owned(); 73 self.host() 74 .transaction(|transaction| { 75 Box::pin(async move { 76 let result = sqlx::query( 77 "INSERT INTO account_preferences \ 78 (owner_public_key, preference_key, preference_value) VALUES (?, ?, ?) \ 79 ON CONFLICT(owner_public_key, preference_key) DO UPDATE SET \ 80 preference_value = excluded.preference_value", 81 ) 82 .bind(owner.as_bytes().as_slice()) 83 .bind(encode_key(key)) 84 .bind(&value) 85 .execute(&mut *transaction) 86 .await 87 .map_err(|_| storage_unavailable())?; 88 if result.rows_affected() != 1 { 89 return Err(storage_unavailable()); 90 } 91 Ok(()) 92 }) 93 }) 94 .await 95 .map_err(map_transaction_error) 96 }) 97 } 98 99 fn clear_owner(&self, owner: PublicKey) -> BoxFuture<'_, Result<(), SafeError>> { 100 Box::pin(async move { 101 self.host() 102 .transaction(|transaction| { 103 Box::pin(async move { 104 sqlx::query("DELETE FROM account_preferences WHERE owner_public_key = ?") 105 .bind(owner.as_bytes().as_slice()) 106 .execute(&mut *transaction) 107 .await 108 .map(|_| ()) 109 .map_err(|_| storage_unavailable()) 110 }) 111 }) 112 .await 113 .map_err(map_transaction_error) 114 }) 115 } 116 } 117 118 const fn encode_key(key: IdentityPreferenceKey) -> &'static str { 119 match key { 120 IdentityPreferenceKey::NamespaceProbe => "namespace_probe", 121 } 122 } 123 124 const fn invalid_preference() -> SafeError { 125 SafeError::new( 126 SafeErrorCode::InvalidIdentityMetadata, 127 SafeMessage::new("The identity preference is invalid."), 128 ) 129 }