From 2dea8212dd33babcce70d8b5fc2b600c5683b759 Mon Sep 17 00:00:00 2001 From: Ben Striegel Date: Fri, 27 Sep 2019 14:02:52 -0400 Subject: [PATCH] feat: allow endpoints to accept either strings or numbers in JSON --- crates/interledger-api/src/lib.rs | 48 ++++++++++++++++++- crates/interledger-api/src/routes/accounts.rs | 5 +- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/crates/interledger-api/src/lib.rs b/crates/interledger-api/src/lib.rs index dbb9c3e53..4d4b7df96 100644 --- a/crates/interledger-api/src/lib.rs +++ b/crates/interledger-api/src/lib.rs @@ -7,11 +7,12 @@ use interledger_service::{Account, AddressStore, IncomingService, OutgoingServic use interledger_service_util::{BalanceStore, ExchangeRateStore}; use interledger_settlement::{SettlementAccount, SettlementStore}; use interledger_stream::StreamNotificationsStore; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize}; use std::{ error::Error as StdError, fmt::{self, Display}, net::SocketAddr, + str::FromStr, }; use warp::{self, Filter}; mod routes; @@ -21,6 +22,42 @@ use secrecy::SecretString; pub(crate) mod http_retry; +// This enum and the following two functions are used to allow clients to send either +// numbers or strings and have them be properly deserialized into the appropriate +// integer type. +#[derive(Deserialize)] +#[serde(untagged)] +enum NumOrStr { + Num(T), + Str(String), +} + +pub fn number_or_string<'de, D, T>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, + T: FromStr + Deserialize<'de>, + ::Err: Display, +{ + match NumOrStr::deserialize(deserializer)? { + NumOrStr::Num(n) => Ok(n), + NumOrStr::Str(s) => T::from_str(&s).map_err(de::Error::custom), + } +} + +pub fn optional_number_or_string<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, + T: FromStr + Deserialize<'de>, + ::Err: Display, +{ + match NumOrStr::deserialize(deserializer)? { + NumOrStr::Num(n) => Ok(Some(n)), + NumOrStr::Str(s) => T::from_str(&s) + .map_err(de::Error::custom) + .and_then(|n| Ok(Some(n))), + } +} + pub trait NodeStore: AddressStore + Clone + Send + Sync + 'static { type Account: Account; @@ -102,9 +139,11 @@ pub struct AccountDetails { pub ilp_address: Option
, pub username: Username, pub asset_code: String, + #[serde(deserialize_with = "number_or_string")] pub asset_scale: u8, - #[serde(default = "u64::max_value")] + #[serde(default = "u64::max_value", deserialize_with = "number_or_string")] pub max_packet_amount: u64, + #[serde(default, deserialize_with = "optional_number_or_string")] pub min_balance: Option, pub ilp_over_http_url: Option, pub ilp_over_http_incoming_token: Option, @@ -112,11 +151,16 @@ pub struct AccountDetails { pub ilp_over_btp_url: Option, pub ilp_over_btp_outgoing_token: Option, pub ilp_over_btp_incoming_token: Option, + #[serde(default, deserialize_with = "optional_number_or_string")] pub settle_threshold: Option, + #[serde(default, deserialize_with = "optional_number_or_string")] pub settle_to: Option, pub routing_relation: Option, + #[serde(default, deserialize_with = "optional_number_or_string")] pub round_trip_time: Option, + #[serde(default, deserialize_with = "optional_number_or_string")] pub amount_per_minute_limit: Option, + #[serde(default, deserialize_with = "optional_number_or_string")] pub packets_per_minute_limit: Option, pub settlement_engine_url: Option, } diff --git a/crates/interledger-api/src/routes/accounts.rs b/crates/interledger-api/src/routes/accounts.rs index b67b19559..34c557e47 100644 --- a/crates/interledger-api/src/routes/accounts.rs +++ b/crates/interledger-api/src/routes/accounts.rs @@ -1,4 +1,6 @@ -use crate::{http_retry::Client, AccountDetails, AccountSettings, ApiError, NodeStore}; +use crate::{ + http_retry::Client, number_or_string, AccountDetails, AccountSettings, ApiError, NodeStore, +}; use bytes::Bytes; use futures::{ future::{err, join_all, ok, Either}, @@ -30,6 +32,7 @@ const DEFAULT_HTTP_TIMEOUT: Duration = Duration::from_millis(5000); #[derive(Deserialize, Debug)] struct SpspPayRequest { receiver: String, + #[serde(deserialize_with = "number_or_string")] source_amount: u64, }