From 210f714dcb517e52643a42ef75f187deec8dc88b Mon Sep 17 00:00:00 2001 From: Justin Carter Date: Tue, 8 Oct 2024 09:36:49 +0200 Subject: [PATCH] test: velocity (#240) * refactor: extract determin_entries_to_enforce * chore: add AccountValues to velocity context * refactor: nest determine_window under VelocityBalances * refactor: extract some fns onto AccountVelocityXxx values * test: unit test context_for_entry * fix: check-code * test: unit test account_control/value * fix: type_complexity * refactor: better pair axes --- cala-cel-interpreter/src/value.rs | 6 + cala-ledger-core-types/src/account.rs | 19 ++ cala-ledger-core-types/src/entry.rs | 2 +- cala-ledger-core-types/src/transaction.rs | 6 +- ...937c3c446b11076dd83e2bd202cee8c95e2c2.json | 22 -- ...c676865c4c1f8134bf6edc6d26201f98b5e27.json | 28 +++ .../src/velocity/account_control/mod.rs | 9 +- .../src/velocity/account_control/repo.rs | 27 ++- .../src/velocity/account_control/value.rs | 198 +++++++++++++++++- cala-ledger/src/velocity/balance/mod.rs | 137 ++++-------- cala-ledger/src/velocity/context.rs | 120 ++++++++++- ...937c3c446b11076dd83e2bd202cee8c95e2c2.json | 22 -- ...c676865c4c1f8134bf6edc6d26201f98b5e27.json | 28 +++ 13 files changed, 471 insertions(+), 153 deletions(-) delete mode 100644 cala-ledger/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json create mode 100644 cala-ledger/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json delete mode 100644 cala-server/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json create mode 100644 cala-server/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json diff --git a/cala-cel-interpreter/src/value.rs b/cala-cel-interpreter/src/value.rs index 4e4ad73d..7ad65222 100644 --- a/cala-cel-interpreter/src/value.rs +++ b/cala-cel-interpreter/src/value.rs @@ -247,6 +247,12 @@ impl From<&Literal> for CelValue { } } +impl From> for CelValue { + fn from(d: DateTime) -> Self { + CelValue::Timestamp(d) + } +} + impl TryFrom<&CelValue> for Arc { type Error = CelError; diff --git a/cala-ledger-core-types/src/account.rs b/cala-ledger-core-types/src/account.rs index 1504f4a7..bd5b4a47 100644 --- a/cala-ledger-core-types/src/account.rs +++ b/cala-ledger-core-types/src/account.rs @@ -21,3 +21,22 @@ pub struct AccountConfig { pub is_account_set: bool, pub eventually_consistent: bool, } + +mod cel { + use cel_interpreter::{CelMap, CelValue}; + + impl From<&super::AccountValues> for CelValue { + fn from(account: &super::AccountValues) -> Self { + let mut map = CelMap::new(); + map.insert("id", account.id); + map.insert("code", account.code.clone()); + map.insert("name", account.name.clone()); + map.insert("externalId", account.code.clone()); + map.insert("normalBalanceType", account.normal_balance_type); + if let Some(metadata) = &account.metadata { + map.insert("metadata", metadata.clone()); + } + map.into() + } + } +} diff --git a/cala-ledger-core-types/src/entry.rs b/cala-ledger-core-types/src/entry.rs index 258c9b01..f816dff3 100644 --- a/cala-ledger-core-types/src/entry.rs +++ b/cala-ledger-core-types/src/entry.rs @@ -26,7 +26,7 @@ mod cel { fn from(entry: &super::EntryValues) -> Self { let mut map = CelMap::new(); map.insert("id", entry.id); - map.insert("entry_type", entry.entry_type.clone()); + map.insert("entryType", entry.entry_type.clone()); map.insert("sequence", CelValue::UInt(entry.sequence as u64)); map.insert("layer", entry.layer); map.insert("direction", entry.direction); diff --git a/cala-ledger-core-types/src/transaction.rs b/cala-ledger-core-types/src/transaction.rs index b69e72e6..229b0b29 100644 --- a/cala-ledger-core-types/src/transaction.rs +++ b/cala-ledger-core-types/src/transaction.rs @@ -23,10 +23,10 @@ mod cel { fn from(tx: &super::TransactionValues) -> Self { let mut map = CelMap::new(); map.insert("id", tx.id); - map.insert("journal_id", tx.journal_id); - map.insert("tx_template_id", tx.tx_template_id); + map.insert("journalId", tx.journal_id); + map.insert("txTemplateId", tx.tx_template_id); map.insert("effective", tx.effective); - map.insert("correlation_id", tx.correlation_id.clone()); + map.insert("correlationId", tx.correlation_id.clone()); if let Some(metadata) = &tx.metadata { map.insert("metadata", metadata.clone()); } diff --git a/cala-ledger/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json b/cala-ledger/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json deleted file mode 100644 index 281dff95..00000000 --- a/cala-ledger/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT values FROM cala_velocity_account_controls\n WHERE data_source_id = '00000000-0000-0000-0000-000000000000' AND account_id = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "values", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "UuidArray" - ] - }, - "nullable": [ - false - ] - }, - "hash": "8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2" -} diff --git a/cala-ledger/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json b/cala-ledger/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json new file mode 100644 index 00000000..7afbcf9d --- /dev/null +++ b/cala-ledger/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT values, latest_values\n FROM cala_velocity_account_controls v\n JOIN cala_accounts a\n ON v.account_id = a.id\n AND v.data_source_id = a.data_source_id\n WHERE v.data_source_id = '00000000-0000-0000-0000-000000000000'\n AND account_id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "values", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "latest_values", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "UuidArray" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27" +} diff --git a/cala-ledger/src/velocity/account_control/mod.rs b/cala-ledger/src/velocity/account_control/mod.rs index 93f628b0..3c123bb3 100644 --- a/cala-ledger/src/velocity/account_control/mod.rs +++ b/cala-ledger/src/velocity/account_control/mod.rs @@ -7,12 +7,16 @@ use sqlx::PgPool; use std::collections::HashMap; +use cala_types::{ + account::AccountValues, + velocity::{VelocityControlValues, VelocityLimitValues}, +}; + use crate::{ atomic_operation::*, param::Params, primitives::{AccountId, DebitOrCredit, Layer}, }; -use cala_types::velocity::{VelocityControlValues, VelocityLimitValues}; use super::error::VelocityError; @@ -101,7 +105,8 @@ impl AccountControls { &self, op: &mut AtomicOperation<'_>, account_ids: &[AccountId], - ) -> Result>, VelocityError> { + ) -> Result)>, VelocityError> + { self.repo.find_for_enforcement(op.tx(), account_ids).await } } diff --git a/cala-ledger/src/velocity/account_control/repo.rs b/cala-ledger/src/velocity/account_control/repo.rs index 81f6e1a6..f78f38ca 100644 --- a/cala-ledger/src/velocity/account_control/repo.rs +++ b/cala-ledger/src/velocity/account_control/repo.rs @@ -2,6 +2,8 @@ use sqlx::{PgPool, Postgres, Transaction}; use std::collections::HashMap; +use cala_types::account::AccountValues; + use crate::primitives::{AccountId, VelocityControlId}; use super::{super::error::*, value::*}; @@ -39,21 +41,36 @@ impl AccountControlRepo { &self, db: &mut Transaction<'_, Postgres>, account_ids: &[AccountId], - ) -> Result>, VelocityError> { + ) -> Result)>, VelocityError> + { let rows = sqlx::query!( - r#"SELECT values FROM cala_velocity_account_controls - WHERE data_source_id = '00000000-0000-0000-0000-000000000000' AND account_id = ANY($1)"#, + r#"SELECT values, latest_values + FROM cala_velocity_account_controls v + JOIN cala_accounts a + ON v.account_id = a.id + AND v.data_source_id = a.data_source_id + WHERE v.data_source_id = '00000000-0000-0000-0000-000000000000' + AND account_id = ANY($1)"#, account_ids as &[AccountId], ) .fetch_all(&mut **db) .await?; - let mut res: HashMap> = HashMap::new(); + let mut res: HashMap)> = HashMap::new(); for row in rows { let values: AccountVelocityControl = serde_json::from_value(row.values).expect("Failed to deserialize control values"); - res.entry(values.account_id).or_default().push(values); + res.entry(values.account_id) + .or_insert_with(|| { + ( + serde_json::from_value(row.latest_values) + .expect("Failed to deserialize account values"), + Vec::new(), + ) + }) + .1 + .push(values); } Ok(res) diff --git a/cala-ledger/src/velocity/account_control/value.rs b/cala-ledger/src/velocity/account_control/value.rs index bfe36d44..4ba9fa8a 100644 --- a/cala-ledger/src/velocity/account_control/value.rs +++ b/cala-ledger/src/velocity/account_control/value.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; use cala_types::{ balance::BalanceSnapshot, - velocity::{PartitionKey, VelocityEnforcement}, + entry::EntryValues, + velocity::{PartitionKey, VelocityEnforcement, Window}, }; use crate::{ @@ -22,6 +23,17 @@ pub struct AccountVelocityControl { pub velocity_limits: Vec, } +impl AccountVelocityControl { + pub fn needs_enforcement(&self, ctx: &CelContext) -> Result { + if let Some(condition) = &self.condition { + let result: bool = condition.try_evaluate(ctx)?; + Ok(result) + } else { + Ok(true) + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AccountVelocityLimit { pub limit_id: VelocityLimitId, @@ -32,6 +44,33 @@ pub struct AccountVelocityLimit { } impl AccountVelocityLimit { + pub fn window_for_enforcement( + &self, + ctx: &CelContext, + entry: &EntryValues, + ) -> Result, VelocityError> { + if let Some(currency) = &self.currency { + if currency != &entry.currency { + return Ok(None); + } + } + + if let Some(condition) = &self.condition { + let result: bool = condition.try_evaluate(ctx)?; + if !result { + return Ok(None); + } + } + + let mut map = serde_json::Map::new(); + for key in self.window.iter() { + let value: serde_json::Value = key.value.try_evaluate(ctx)?; + map.insert(key.alias.clone(), value); + } + + Ok(Some(map.into())) + } + pub fn enforce( &self, ctx: &CelContext, @@ -92,3 +131,160 @@ pub struct AccountBalanceLimit { pub start: DateTime, pub end: Option>, } + +#[cfg(test)] +mod tests { + use crate::primitives::*; + + use super::*; + + #[test] + fn control_needs_enforcement_when_no_condition_given() { + let control = AccountVelocityControl { + account_id: AccountId::new(), + control_id: VelocityControlId::new(), + enforcement: VelocityEnforcement::default(), + condition: None, + velocity_limits: vec![], + }; + let ctx = crate::cel_context::initialize(); + assert!(control.needs_enforcement(&ctx).unwrap()); + } + + #[test] + fn control_needs_enforcement_when_condition_is_true() { + let mut control = AccountVelocityControl { + account_id: AccountId::new(), + control_id: VelocityControlId::new(), + enforcement: VelocityEnforcement::default(), + condition: Some("true".parse().unwrap()), + velocity_limits: vec![], + }; + let ctx = crate::cel_context::initialize(); + assert!(control.needs_enforcement(&ctx).unwrap()); + control.condition = Some("1 == 2".parse().unwrap()); + assert!(!control.needs_enforcement(&ctx).unwrap()); + } + + fn entry() -> EntryValues { + EntryValues { + id: EntryId::new(), + version: 1, + transaction_id: TransactionId::new(), + journal_id: JournalId::new(), + account_id: AccountId::new(), + entry_type: "TEST_ENTRY_TYPE".to_string(), + sequence: 1, + layer: Layer::Settled, + currency: "USD".parse().unwrap(), + direction: DebitOrCredit::Credit, + units: Decimal::from(100), + description: None, + } + } + + #[test] + fn limit_needs_enforcement_when_no_condition_given() { + let limit = AccountVelocityLimit { + limit_id: VelocityLimitId::new(), + window: vec![], + condition: None, + currency: None, + limit: AccountLimit { + timestamp_source: None, + balance: vec![], + }, + }; + let ctx = crate::cel_context::initialize(); + let entry = entry(); + assert!(limit + .window_for_enforcement(&ctx, &entry) + .unwrap() + .is_some()); + } + + #[test] + fn limit_does_not_need_enforcement_when_currency_does_not_match() { + let limit = AccountVelocityLimit { + limit_id: VelocityLimitId::new(), + window: vec![], + condition: None, + currency: Some("EUR".parse().unwrap()), + limit: AccountLimit { + timestamp_source: None, + balance: vec![], + }, + }; + let ctx = crate::cel_context::initialize(); + let mut entry = entry(); + assert!(limit + .window_for_enforcement(&ctx, &entry) + .unwrap() + .is_none()); + + entry.currency = "EUR".parse().unwrap(); + assert!(limit + .window_for_enforcement(&ctx, &entry) + .unwrap() + .is_some()); + } + + #[test] + fn limit_needs_enforcement_when_condition_is_true() { + let mut limit = AccountVelocityLimit { + limit_id: VelocityLimitId::new(), + window: vec![], + currency: None, + condition: Some("true".parse().unwrap()), + limit: AccountLimit { + timestamp_source: None, + balance: vec![], + }, + }; + let ctx = crate::cel_context::initialize(); + let entry = entry(); + assert!(limit + .window_for_enforcement(&ctx, &entry) + .unwrap() + .is_some()); + limit.condition = Some("1 == 2".parse().unwrap()); + assert!(limit + .window_for_enforcement(&ctx, &entry) + .unwrap() + .is_none()); + } + + #[test] + fn limit_interpolates_window() { + let limit = AccountVelocityLimit { + limit_id: VelocityLimitId::new(), + window: vec![ + PartitionKey { + alias: "entry_type".to_string(), + value: "entry.entryType".parse().unwrap(), + }, + PartitionKey { + alias: "entry_sequence".to_string(), + value: "entry.sequence".parse().unwrap(), + }, + ], + condition: None, + currency: None, + limit: AccountLimit { + timestamp_source: None, + balance: vec![], + }, + }; + let entry = entry(); + let mut ctx = crate::cel_context::initialize(); + ctx.add_variable("entry", &entry); + let window = limit.window_for_enforcement(&ctx, &entry).unwrap(); + assert_eq!( + window.unwrap(), + Window::from(serde_json::json!({ + "entry_type": "TEST_ENTRY_TYPE", + "entry_sequence": 1 + })) + ); + } +} diff --git a/cala-ledger/src/velocity/balance/mod.rs b/cala-ledger/src/velocity/balance/mod.rs index 41c1b024..a1848c63 100644 --- a/cala-ledger/src/velocity/balance/mod.rs +++ b/cala-ledger/src/velocity/balance/mod.rs @@ -6,10 +6,8 @@ use sqlx::PgPool; use std::collections::HashMap; use cala_types::{ - balance::BalanceSnapshot, - entry::EntryValues, + account::AccountValues, balance::BalanceSnapshot, entry::EntryValues, transaction::TransactionValues, - velocity::{PartitionKey, Window}, }; use crate::{atomic_operation::*, primitives::AccountId}; @@ -36,42 +34,57 @@ impl VelocityBalances { created_at: DateTime, transaction: &TransactionValues, entries: &[EntryValues], - controls: HashMap>, + controls: HashMap)>, ) -> Result<(), VelocityError> { - let empty = Vec::new(); + let mut context = + super::context::EvalContext::new(transaction, controls.values().map(|v| &v.0)); - let mut context = super::context::EvalContext::new(transaction); + let entries_to_enforce = Self::balances_to_check(&mut context, entries, &controls)?; - let mut entries_to_add: HashMap< + if entries_to_enforce.is_empty() { + return Ok(()); + } + + let current_balances = self + .repo + .find_for_update(op.tx(), entries_to_enforce.keys()) + .await?; + + let new_balances = + Self::new_snapshots(context, created_at, current_balances, &entries_to_enforce)?; + + self.repo + .insert_new_snapshots(op.tx(), new_balances) + .await?; + + Ok(()) + } + + #[allow(clippy::type_complexity)] + fn balances_to_check<'a>( + context: &mut super::context::EvalContext, + entries: &'a [EntryValues], + controls: &'a HashMap)>, + ) -> Result< + HashMap>, + VelocityError, + > { + let mut balances_to_check: HashMap< VelocityBalanceKey, Vec<(&AccountVelocityLimit, &EntryValues)>, > = HashMap::new(); for entry in entries { - for control in controls.get(&entry.account_id).unwrap_or(&empty) { - let ctx = context.control_context(entry); - let control_active = if let Some(condition) = &control.condition { - let control_active: bool = condition.try_evaluate(&ctx)?; - control_active - } else { - true - }; - if control_active { + let (_, controls) = match controls.get(&entry.account_id) { + Some(control) => control, + None => continue, + }; + for control in controls.iter() { + let ctx = context.context_for_entry(entry); + + if control.needs_enforcement(&ctx)? { for limit in &control.velocity_limits { - if let Some(currency) = &limit.currency { - if currency != &entry.currency { - continue; - } - } - - let limit_active = if let Some(condition) = &limit.condition { - let limit_active: bool = condition.try_evaluate(&ctx)?; - limit_active - } else { - true - }; - if limit_active { - let window = determine_window(&limit.window, &ctx)?; - entries_to_add + if let Some(window) = limit.window_for_enforcement(&ctx, entry)? { + balances_to_check .entry(( window, entry.currency, @@ -88,23 +101,7 @@ impl VelocityBalances { } } - if entries_to_add.is_empty() { - return Ok(()); - } - - let current_balances = self - .repo - .find_for_update(op.tx(), entries_to_add.keys()) - .await?; - - let new_balances = - Self::new_snapshots(context, created_at, current_balances, &entries_to_add)?; - - self.repo - .insert_new_snapshots(op.tx(), new_balances) - .await?; - - Ok(()) + Ok(balances_to_check) } fn new_snapshots<'a>( @@ -120,7 +117,7 @@ impl VelocityBalances { let mut new_balances = Vec::new(); for (limit, entry) in entries { - let ctx = context.control_context(entry); + let ctx = context.context_for_entry(entry); let balance = match (latest_balance.take(), current_balances.remove(key)) { (Some(latest), _) => { new_balances.push(latest.clone()); @@ -148,45 +145,3 @@ impl VelocityBalances { Ok(res) } } - -fn determine_window( - keys: &[PartitionKey], - ctx: &cel_interpreter::CelContext, -) -> Result { - let mut map = serde_json::Map::new(); - for key in keys { - let value: serde_json::Value = key.value.try_evaluate(ctx)?; - map.insert(key.alias.clone(), value); - } - Ok(map.into()) -} - -#[cfg(test)] -mod test { - #[test] - fn window_determination() { - use super::*; - use cala_types::velocity::PartitionKey; - use cel_interpreter::CelContext; - use serde_json::json; - - let keys = vec![ - PartitionKey { - alias: "foo".to_string(), - value: "'bar'".parse().expect("Failed to parse"), - }, - PartitionKey { - alias: "baz".to_string(), - value: "'qux'".parse().expect("Failed to parse"), - }, - ]; - - let ctx = CelContext::new(); - let result = determine_window(&keys, &ctx).unwrap(); - let expected = json!({ - "foo": "bar", - "baz": "qux", - }); - assert_eq!(Window::from(expected), result); - } -} diff --git a/cala-ledger/src/velocity/context.rs b/cala-ledger/src/velocity/context.rs index 37177b0a..32441a8d 100644 --- a/cala-ledger/src/velocity/context.rs +++ b/cala-ledger/src/velocity/context.rs @@ -1,32 +1,48 @@ use std::collections::HashMap; -use cala_types::{entry::EntryValues, transaction::TransactionValues}; +use cala_types::{account::AccountValues, entry::EntryValues, transaction::TransactionValues}; use cel_interpreter::{CelMap, CelValue}; -use crate::{cel_context::*, primitives::EntryId}; +use crate::{ + cel_context::*, + primitives::{AccountId, EntryId}, +}; pub struct EvalContext { transaction: CelValue, entry_values: HashMap, + account_values: HashMap, } impl EvalContext { - pub fn new(transaction: &TransactionValues) -> Self { + pub fn new<'a>( + transaction: &TransactionValues, + accounts: impl Iterator, + ) -> Self { + let account_values = accounts.map(|a| (a.id, a.into())).collect(); Self { transaction: transaction.into(), entry_values: HashMap::new(), + account_values, } } - pub fn control_context(&mut self, entry: &EntryValues) -> CelContext { - let entry = self + pub fn context_for_entry(&mut self, entry: &EntryValues) -> CelContext { + let cel_entry = self .entry_values .entry(entry.id) .or_insert_with(|| entry.into()); let mut vars = CelMap::new(); vars.insert("transaction", self.transaction.clone()); - vars.insert("entry", entry.clone()); + vars.insert("entry", cel_entry.clone()); + vars.insert( + "account", + self.account_values + .get(&entry.account_id) + .expect("account values not set for context") + .clone(), + ); let mut context = CelMap::new(); context.insert("vars", vars); @@ -37,3 +53,95 @@ impl EvalContext { ctx } } + +#[cfg(test)] +mod tests { + use rust_decimal::Decimal; + use serde_json::json; + + use cala_types::account::AccountConfig; + use cel_interpreter::CelExpression; + + use crate::{primitives::*, velocity::context::EvalContext}; + + use super::*; + + fn transaction() -> TransactionValues { + TransactionValues { + id: TransactionId::new(), + version: 1, + journal_id: JournalId::new(), + tx_template_id: TxTemplateId::new(), + entry_ids: vec![], + effective: chrono::Utc::now().date_naive(), + correlation_id: "correlation_id".to_string(), + external_id: Some("external_id".to_string()), + description: None, + metadata: Some(serde_json::json!({ + "tx": "metadata", + "test": true, + })), + } + } + + fn account() -> AccountValues { + AccountValues { + id: AccountId::new(), + version: 1, + code: "code".to_string(), + name: "name".to_string(), + normal_balance_type: DebitOrCredit::Credit, + status: Status::Active, + external_id: None, + description: None, + metadata: Some(json!({ + "account": "metadata", + "test": true, + })), + config: AccountConfig { + is_account_set: false, + eventually_consistent: false, + }, + } + } + + fn entry(account_id: AccountId, tx: &TransactionValues) -> EntryValues { + EntryValues { + id: EntryId::new(), + version: 1, + transaction_id: tx.id, + journal_id: tx.journal_id, + account_id, + entry_type: "TEST".to_string(), + sequence: 1, + layer: Layer::Settled, + currency: "USD".parse().unwrap(), + direction: DebitOrCredit::Credit, + units: Decimal::from(100), + description: None, + } + } + + #[test] + fn context_for_entry() { + let account = account(); + let tx = transaction(); + let entry = entry(account.id, &tx); + let mut context = EvalContext::new(&tx, std::iter::once(&account)); + let ctx = context.context_for_entry(&entry); + + let expr: CelExpression = "context.vars.transaction.id".parse().unwrap(); + let result: uuid::Uuid = expr.try_evaluate(&ctx).unwrap(); + assert!(result == tx.id.into()); + + let expr: CelExpression = "context.vars.account.metadata.test".parse().unwrap(); + let result: bool = expr.try_evaluate(&ctx).unwrap(); + assert!(result); + + let expr: CelExpression = "context.vars.entry.units == decimal('100')" + .parse() + .unwrap(); + let result: bool = expr.try_evaluate(&ctx).unwrap(); + assert!(result); + } +} diff --git a/cala-server/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json b/cala-server/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json deleted file mode 100644 index 281dff95..00000000 --- a/cala-server/.sqlx/query-8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT values FROM cala_velocity_account_controls\n WHERE data_source_id = '00000000-0000-0000-0000-000000000000' AND account_id = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "values", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "UuidArray" - ] - }, - "nullable": [ - false - ] - }, - "hash": "8b87fd1b4f25b8265528a02375e937c3c446b11076dd83e2bd202cee8c95e2c2" -} diff --git a/cala-server/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json b/cala-server/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json new file mode 100644 index 00000000..7afbcf9d --- /dev/null +++ b/cala-server/.sqlx/query-ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT values, latest_values\n FROM cala_velocity_account_controls v\n JOIN cala_accounts a\n ON v.account_id = a.id\n AND v.data_source_id = a.data_source_id\n WHERE v.data_source_id = '00000000-0000-0000-0000-000000000000'\n AND account_id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "values", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "latest_values", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "UuidArray" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "ad05507f024449cd30d1f223ecac676865c4c1f8134bf6edc6d26201f98b5e27" +}