app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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 }