Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improved pusher value in message #383

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ members = [
"crates/localizer",
"crates/job_runner",
"crates/search_index",
"crates/nft"
"crates/nft",
]

default-members = [
Expand Down Expand Up @@ -75,15 +75,15 @@ lazy_static = "1.4.0"
futures-util = "0.3.30"
uuid = { version = "1.8.0", features = ["v4"] }
rand = { version = "0.8.5" }
aws-sdk-s3 = { version = "1.5", features = ["behavior-version-latest"] }
aws-sdk-s3 = { version = "1.5", features = ["behavior-version-latest"] }

# db
diesel = { version = "2.2.3", features = ["postgres", "chrono", "serde_json"] }
diesel_migrations = { version = "2.2.0" }
redis = { version = "0.25.0", features = ["tokio-comp", "json"] }
chrono = { version = "0.4.34", features = ["serde"] }
clickhouse = { version = "0.11.6" }
meilisearch-sdk = { version = "0.27.1" }
meilisearch-sdk = { version = "0.27.1" }

# crypto
base64 = { version = "0.22.0" }
Expand All @@ -92,6 +92,7 @@ bech32 = { version = "0.11.0" }
bs58 = { version = "0.5.0", features = ["check"] }
hex = { version = "0.4.3" }
num-bigint = { version = "0.4.4", features = ["std", "serde"] }
num-format = { version = "0.4.4" }
num-traits = { version = "0.2.18" }
bigdecimal = "0.4"
cosmos-sdk-proto = { version = "0.21.1" }
Expand Down
1 change: 1 addition & 0 deletions apps/parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ futures = { workspace = true }
chrono = { workspace = true }
async-trait = { workspace = true }
rand = { workspace = true }
num-format = { workspace = true }

primitives = { path = "../../crates/primitives" }
storage = { path = "../../crates/storage" }
Expand Down
22 changes: 17 additions & 5 deletions apps/parser/src/pusher.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::error::Error;

use localizer::LanguageLocalizer;
use num_format::Locale;
use primitives::big_number_localizer::Format;
use primitives::{
AddressFormatter, BigNumberFormatter, Chain, PushNotification, PushNotificationTransaction, PushNotificationTypes, Subscription, Transaction,
TransactionSwapMetadata, TransactionType,
AddressFormatter, BigNumberFormatter, BigNumberLocalizer, Chain, PushNotification, PushNotificationTransaction, PushNotificationTypes, Subscription,
Transaction, TransactionSwapMetadata, TransactionType,
};
use storage::DatabaseClient;

Expand Down Expand Up @@ -32,9 +34,18 @@ impl Pusher {
}
}

pub fn message(&mut self, localizer: LanguageLocalizer, transaction: Transaction, subscription: Subscription) -> Result<Message, Box<dyn Error>> {
pub fn message(
&mut self,
localizer: LanguageLocalizer,
transaction: Transaction,
subscription: Subscription,
locale: Locale,
) -> Result<Message, Box<dyn Error>> {
let number_localizer = BigNumberLocalizer::default();
let asset = self.database_client.get_asset(transaction.asset_id.to_string().as_str())?;
let amount = BigNumberFormatter::value(transaction.value.as_str(), asset.decimals).unwrap_or_default();
let amount = number_localizer
.get_value(transaction.value.as_str(), asset.decimals, Format::Short, locale)
.unwrap_or_default();
let chain = transaction.asset_id.chain;
let to_address = self.get_address(chain, transaction.to.as_str())?;
let from_address = self.get_address(chain, transaction.from.as_str())?;
Expand Down Expand Up @@ -103,7 +114,8 @@ impl Pusher {
return Ok(0);
}
let localizer = LanguageLocalizer::new_with_language(&device.locale);
let message = self.message(localizer, transaction.clone(), subscription.clone())?;
let locale = Locale::from_name(device.locale.as_str()).unwrap_or(Locale::en);
let message = self.message(localizer, transaction.clone(), subscription.clone(), locale)?;

let notification_transaction = PushNotificationTransaction {
wallet_index: subscription.wallet_index,
Expand Down
1 change: 1 addition & 0 deletions crates/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
num-bigint = { workspace = true }
num-format = { workspace = true }
num-traits = { workspace = true }
bigdecimal = { workspace = true }
strum = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions crates/primitives/src/big_number_formatter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bigdecimal::BigDecimal;
use num_bigint::BigInt;

use std::str::FromStr;

pub struct BigNumberFormatter {}
Expand Down
125 changes: 125 additions & 0 deletions crates/primitives/src/big_number_localizer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::BigNumberFormatter;
use bigdecimal::RoundingMode;
use num_format::{Locale, ToFormattedString};

#[derive(Default)]
pub struct BigNumberLocalizer {}

pub enum Format {
Short,
Medium,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add full

}

impl Format {
fn get_scale(&self) -> i64 {
match self {
Format::Short => 2,
Format::Medium => 4,
}
}
}

impl BigNumberLocalizer {
pub fn get_value(&self, value: &str, decimals: i32, format: Format, locale: Locale) -> Option<String> {
Self::localized_value_with_scale(value, decimals, format.get_scale(), locale)
}

fn get_formatted_scale(value: &str, decimals: i32, target_scale: i64) -> Option<i64> {
let decimal = BigNumberFormatter::big_decimal_value(value, decimals as u32)?;
let decimal_string = decimal.to_string();

let parts: Vec<&str> = decimal_string.split('.').collect();
if parts.len() < 2 {
// No fractional part => just use target_scale
return Some(target_scale);
}

// Example: "0.00012" => integer_part="0", frac_str="00012"
// Example: "-0.00012" => integer_part="-0", frac_str="00012"
let mut frac_str = parts[1];

// If negative, strip the leading '-'.
// Usually `parts[1]` won't have '-', but just in case we do this.
if frac_str.starts_with('-') {
frac_str = &frac_str[1..];
}

// Count how many '0' from the start of fractional part until first non-'0'
let mut leading_zero_count = 0;
for ch in frac_str.chars() {
if ch == '0' {
leading_zero_count += 1;
} else {
break;
}
}

let computed_scale = leading_zero_count as i64 + target_scale;
if computed_scale > frac_str.len() as i64 {
// If our desired scale surpasses the entire fractional length,
// we fallback to just 'leading_zero_count'.
// This ensures we don't try to keep beyond the available digits.
Some(leading_zero_count as i64)
} else {
Some(computed_scale)
}
}

fn localized_value_with_scale(value: &str, decimals: i32, target_scale: i64, locale: Locale) -> Option<String> {
let scale = Self::get_formatted_scale(value, decimals, target_scale)?;
let decimal = BigNumberFormatter::big_decimal_value(value, decimals as u32)?;
let rounded_decimal = decimal.with_scale_round(scale, RoundingMode::Ceiling);

let s = rounded_decimal.to_string();
let parts: Vec<&str> = s.split('.').collect();
let integer_part = parts[0];
let fractional_part = if parts.len() > 1 { format!(".{}", parts[1]) } else { String::new() };

// Convert integer_part to a BigInt for formatting with `num-format`.
// Handle negative numbers.
let is_negative = integer_part.starts_with('-');
let abs_str = integer_part.trim_start_matches('-');
let int_val = abs_str.parse::<i128>().unwrap_or(0);

// Format the absolute value with commas.
let formatted_abs = int_val.to_formatted_string(&locale);

let result = if is_negative {
format!("-{}{}", formatted_abs, fractional_part)
} else {
format!("{}{}", formatted_abs, fractional_part)
};

Some(result)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_format_with_commas() {
let localizer = BigNumberLocalizer::default();
let locale = Locale::en;
assert_eq!(localizer.get_value("1123450000", 0, Format::Short, locale).unwrap(), "1,123,450,000.00");
assert_eq!(localizer.get_value("1123450000", 6, Format::Short, locale).unwrap(), "1,123.45");
assert_eq!(localizer.get_value("1123450000", 6, Format::Medium, locale).unwrap(), "1,124"); // Invalid?
assert_eq!(localizer.get_value("1123456666", 6, Format::Short, locale).unwrap(), "1,123.46");

assert_eq!(localizer.get_value("12000", 8, Format::Short, locale).unwrap(), "0.00012");
assert_eq!(localizer.get_value("0", 8, Format::Short, locale).unwrap(), "0.00");
assert_eq!(localizer.get_value("1", 8, Format::Short, locale).unwrap(), "0.01"); // Invalid?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many values are invalid, need to have better unit test coverage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did split code into BigNumberLocalizer to separate from BigNumberFormatter which is in theory should be just ValueFormatter or ValueDecoder

assert_eq!(localizer.get_value("1", 8, Format::Medium, locale).unwrap(), "0.0001"); // Invalid?
assert_eq!(localizer.get_value("129999", 8, Format::Short, locale).unwrap(), "0.0013");
// Invalid
}

#[test]
fn test_get_formatted_scale() {
assert_eq!(BigNumberLocalizer::get_formatted_scale("123450000", 6, 2).unwrap(), 2);
assert_eq!(BigNumberLocalizer::get_formatted_scale("123456666", 6, 2).unwrap(), 2);
assert_eq!(BigNumberLocalizer::get_formatted_scale("12000", 8, 2).unwrap(), 5);
assert_eq!(BigNumberLocalizer::get_formatted_scale("129999", 8, 2).unwrap(), 4);
}
}
2 changes: 2 additions & 0 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub mod transaction_metadata_types;
pub use self::transaction_metadata_types::TransactionSwapMetadata;
pub mod big_number_formatter;
pub use self::big_number_formatter::BigNumberFormatter;
pub mod big_number_localizer;
pub use self::big_number_localizer::BigNumberLocalizer;
pub mod number_formatter;
pub use self::number_formatter::NumberFormatter;
pub mod wallet_connect;
Expand Down
Loading