Skip to content

Commit 5dd37f5

Browse files
apollo_config: generic comma-separated deserializer (#9643)
1 parent 869bb86 commit 5dd37f5

File tree

7 files changed

+35
-71
lines changed

7 files changed

+35
-71
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_config/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ const_format.workspace = true
1414
itertools.workspace = true
1515
serde = { workspace = true, features = ["derive"] }
1616
serde_json = { workspace = true, features = ["arbitrary_precision"] }
17-
starknet-types-core.workspace = true
18-
starknet_api.workspace = true
1917
strum_macros.workspace = true
2018
thiserror.workspace = true
2119
tracing.workspace = true

crates/apollo_config/src/converters.rs

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ use std::time::Duration;
3131

3232
use serde::de::Error;
3333
use serde::{Deserialize, Deserializer, Serialize};
34-
use starknet_api::core::ContractAddress;
35-
use starknet_types_core::felt::Felt;
3634
use url::Url;
3735

3836
/// Deserializes milliseconds to duration object.
@@ -264,7 +262,7 @@ where
264262
T: FromStr,
265263
T::Err: std::fmt::Display,
266264
{
267-
let raw: String = <String as serde::Deserialize>::deserialize(de)?;
265+
let raw = String::deserialize(de)?;
268266

269267
if raw.trim().is_empty() {
270268
return Ok(Vec::new());
@@ -275,38 +273,28 @@ where
275273
.collect()
276274
}
277275

278-
// TODO(Asmaa): refactor this to `deserialize_comma_separated_str<T>` where
279-
// T: TryFrom<&str>, to support deserializing comma-separated lists of any type.
280-
/// Deserializes an optional comma-separated list of contract addresses into
281-
/// `Option<Vec<ContractAddress>>`. An empty string is invalid if the `#is_none` flag is false.
282-
pub fn deserialize_optional_contract_addresses<'de, D>(
283-
de: D,
284-
) -> Result<Option<Vec<ContractAddress>>, D::Error>
276+
/// Deserializes an optional comma-separated list of values implementing `FromStr` into
277+
/// `Option<Vec<T>>`. Returns `None` for empty or missing strings.
278+
pub fn deserialize_comma_separated_str<'de, D, T>(de: D) -> Result<Option<Vec<T>>, D::Error>
285279
where
286280
D: Deserializer<'de>,
281+
T: FromStr,
282+
<T as FromStr>::Err: std::fmt::Display,
287283
{
288-
let raw: String = match Option::<String>::deserialize(de)? {
289-
Some(addresses) => addresses,
290-
None => return Ok(None),
291-
};
292-
293-
if raw.is_empty() {
294-
return Err(D::Error::custom(
295-
"Empty string is not a valid input for contract addresses. The config field is marked \
296-
as not none.",
297-
));
284+
let raw = String::deserialize(de).unwrap_or_default();
285+
if raw.trim().is_empty() {
286+
return Ok(None);
298287
}
299288

300-
let mut result = Vec::new();
301-
for addresses_str in raw.split(',') {
302-
let felt = Felt::from_str(addresses_str).map_err(|err| {
303-
D::Error::custom(format!("Failed to parse Felt from '{addresses_str}': {err}"))
304-
})?;
305-
let addr = ContractAddress::try_from(felt).map_err(|err| {
306-
D::Error::custom(format!("Invalid contract address '{addresses_str}': {err}"))
307-
})?;
308-
result.push(addr);
289+
let mut output: Vec<T> = Vec::new();
290+
for part in raw.split(',').filter(|s| !s.is_empty()) {
291+
let value = T::from_str(part)
292+
.map_err(|e| D::Error::custom(format!("Invalid value '{part}': {e}")))?;
293+
output.push(value);
309294
}
310295

311-
Ok(Some(result))
296+
if output.is_empty() {
297+
return Ok(None);
298+
}
299+
Ok(Some(output))
312300
}

crates/apollo_consensus_orchestrator_config/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::fmt::Debug;
33
use std::time::Duration;
44

55
use apollo_config::converters::{
6+
deserialize_comma_separated_str,
67
deserialize_milliseconds_to_duration,
7-
deserialize_optional_contract_addresses,
88
deserialize_seconds_to_duration,
99
};
1010
use apollo_config::dumping::{ser_optional_param, ser_param, SerializeConfig};
@@ -96,7 +96,7 @@ pub struct ContextConfig {
9696
pub num_validators: u64,
9797
/// Optional explicit set of validator IDs (contract addresses) to use.
9898
/// If provided, this overrides `num_validators`.
99-
#[serde(default, deserialize_with = "deserialize_optional_contract_addresses")]
99+
#[serde(default, deserialize_with = "deserialize_comma_separated_str")]
100100
pub validator_ids: Option<Vec<ContractAddress>>,
101101
/// The chain id of the Starknet chain.
102102
pub chain_id: ChainId,

crates/apollo_gateway_config/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::BTreeMap;
22

3-
use apollo_config::converters::deserialize_optional_contract_addresses;
3+
use apollo_config::converters::deserialize_comma_separated_str;
44
use apollo_config::dumping::{
55
prepend_sub_config_name,
66
ser_optional_param,
@@ -25,7 +25,7 @@ pub struct GatewayConfig {
2525
pub stateful_tx_validator_config: StatefulTransactionValidatorConfig,
2626
pub chain_info: ChainInfo,
2727
pub block_declare: bool,
28-
#[serde(default, deserialize_with = "deserialize_optional_contract_addresses")]
28+
#[serde(default, deserialize_with = "deserialize_comma_separated_str")]
2929
pub authorized_declarer_accounts: Option<Vec<ContractAddress>>,
3030
}
3131

crates/apollo_network/src/lib.rs

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ mod test_utils;
1919
pub mod utils;
2020

2121
use std::collections::{BTreeMap, HashSet};
22-
use std::str::FromStr;
2322
use std::time::Duration;
2423

2524
use apollo_config::converters::{
25+
deserialize_comma_separated_str,
2626
deserialize_optional_vec_u8,
2727
deserialize_seconds_to_duration,
2828
serialize_optional_vec_u8,
@@ -39,42 +39,12 @@ use discovery::DiscoveryConfig;
3939
use libp2p::swarm::dial_opts::DialOpts;
4040
use libp2p::Multiaddr;
4141
use peer_manager::PeerManagerConfig;
42-
use serde::de::Error;
43-
use serde::{Deserialize, Deserializer, Serialize};
42+
use serde::{Deserialize, Serialize};
4443
use starknet_api::core::ChainId;
4544
use validator::{Validate, ValidationError};
4645

4746
pub(crate) type Bytes = Vec<u8>;
4847

49-
// TODO(AndrewL): Fix this
50-
/// This function considers `""` to be `None` and
51-
/// `"multiaddr1,multiaddr2"` to be `Some(vec![multiaddr1, multiaddr2])`.
52-
/// It was purposefully designed this way to be compatible with the old config where only one
53-
/// bootstrap peer was supported. Hence there is no way to express an empty vector in the config.
54-
fn deserialize_multi_addrs<'de, D>(de: D) -> Result<Option<Vec<Multiaddr>>, D::Error>
55-
where
56-
D: Deserializer<'de>,
57-
{
58-
let raw_str: String = Deserialize::deserialize(de).unwrap_or_default();
59-
if raw_str.is_empty() {
60-
return Ok(None);
61-
}
62-
63-
let mut vector = Vec::new();
64-
for i in raw_str.split(',').filter(|s| !s.is_empty()) {
65-
let value = Multiaddr::from_str(i).map_err(|_| {
66-
D::Error::custom(format!("Couldn't deserialize vector. Failed to parse value: {i}"))
67-
})?;
68-
vector.push(value);
69-
}
70-
71-
if vector.is_empty() {
72-
return Ok(None);
73-
}
74-
75-
Ok(Some(vector))
76-
}
77-
7848
// TODO(Tsabary): move to the config converter module.
7949
pub fn serialize_multi_addrs(multi_addrs: &Option<Vec<Multiaddr>>) -> String {
8050
match multi_addrs {
@@ -95,7 +65,7 @@ pub struct NetworkConfig {
9565
pub session_timeout: Duration,
9666
#[serde(deserialize_with = "deserialize_seconds_to_duration")]
9767
pub idle_connection_timeout: Duration,
98-
#[serde(deserialize_with = "deserialize_multi_addrs")]
68+
#[serde(deserialize_with = "deserialize_comma_separated_str")]
9969
#[validate(custom(function = "validate_bootstrap_peer_multiaddr_list"))]
10070
pub bootstrap_peer_multiaddr: Option<Vec<Multiaddr>>,
10171
#[validate(custom = "validate_vec_u256")]

crates/starknet_api/src/core.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
mod core_test;
44

55
use std::fmt::Debug;
6+
use std::str::FromStr;
67
use std::sync::LazyLock;
78

89
use num_traits::ToPrimitive;
@@ -154,6 +155,15 @@ impl From<u128> for ContractAddress {
154155

155156
impl_from_through_intermediate!(u128, ContractAddress, u8, u16, u32, u64);
156157

158+
impl FromStr for ContractAddress {
159+
type Err = StarknetApiError;
160+
fn from_str(s: &str) -> Result<Self, Self::Err> {
161+
let felt = Felt::from_str(s)
162+
.map_err(|e| StarknetApiError::OutOfRange { string: format!("{e}") })?;
163+
Ok(ContractAddress(PatriciaKey::try_from(felt)?))
164+
}
165+
}
166+
157167
/// The maximal size of storage var.
158168
pub const MAX_STORAGE_ITEM_SIZE: u16 = 256;
159169
/// The prefix used in the calculation of a contract address.

0 commit comments

Comments
 (0)