Skip to content

Commit db5ab8d

Browse files
authored
Fix deserialization when sample_freq values contain a % symbol
Co-authored-by: Benjamin Sparks <b.sparks@alugha.com>
1 parent c958098 commit db5ab8d

File tree

4 files changed

+42
-7
lines changed

4 files changed

+42
-7
lines changed

async-nats/src/jetstream/consumer/mod.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ pub struct Config {
273273
/// What percentage of acknowledgments should be samples for observability, 0-100
274274
#[serde(
275275
rename = "sample_freq",
276-
with = "from_str",
276+
with = "sample_freq_deser",
277277
default,
278278
skip_serializing_if = "is_default"
279279
)]
@@ -433,15 +433,25 @@ fn is_default<T: Default + Eq>(t: &T) -> bool {
433433
t == &T::default()
434434
}
435435

436-
pub(crate) mod from_str {
436+
pub(crate) mod sample_freq_deser {
437437
pub(crate) fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
438438
where
439439
T: std::str::FromStr,
440440
T::Err: std::fmt::Display,
441441
D: serde::Deserializer<'de>,
442442
{
443443
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
444-
T::from_str(&s).map_err(serde::de::Error::custom)
444+
445+
let mut spliterator = s.split('%');
446+
match (spliterator.next(), spliterator.next()) {
447+
// No percentage occurred, parse as number
448+
(Some(number), None) => T::from_str(number).map_err(serde::de::Error::custom),
449+
// A percentage sign occurred right at the end
450+
(Some(number), Some("")) => T::from_str(number).map_err(serde::de::Error::custom),
451+
_ => Err(serde::de::Error::custom(format!(
452+
"Malformed sample frequency: {s}"
453+
))),
454+
}
445455
}
446456

447457
pub(crate) fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>

async-nats/src/jetstream/consumer/pull.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ pub struct OrderedConfig {
605605
/// What percentage of acknowledgments should be samples for observability, 0-100
606606
#[serde(
607607
rename = "sample_freq",
608-
with = "super::from_str",
608+
with = "super::sample_freq_deser",
609609
default,
610610
skip_serializing_if = "is_default"
611611
)]
@@ -2051,7 +2051,7 @@ pub struct Config {
20512051
/// What percentage of acknowledgments should be samples for observability, 0-100
20522052
#[serde(
20532053
rename = "sample_freq",
2054-
with = "super::from_str",
2054+
with = "super::sample_freq_deser",
20552055
default,
20562056
skip_serializing_if = "is_default"
20572057
)]

async-nats/src/jetstream/consumer/push.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ pub struct Config {
236236
/// What percentage of acknowledgments should be samples for observability, 0-100
237237
#[serde(
238238
rename = "sample_freq",
239-
with = "super::from_str",
239+
with = "super::sample_freq_deser",
240240
default,
241241
skip_serializing_if = "is_default"
242242
)]
@@ -389,7 +389,7 @@ pub struct OrderedConfig {
389389
/// What percentage of acknowledgments should be samples for observability, 0-100
390390
#[serde(
391391
rename = "sample_freq",
392-
with = "super::from_str",
392+
with = "super::sample_freq_deser",
393393
default,
394394
skip_serializing_if = "is_default"
395395
)]

async-nats/tests/jetstream_tests.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,6 +2617,31 @@ mod jetstream {
26172617

26182618
assert_eq!(100, consumer.cached_info().config.sample_frequency);
26192619
}
2620+
2621+
// `sample_frequency` can contain a %, which occurs in the wild
2622+
// when managing NATS resources using Terraform / OpenTofu's NATS JetStream provider.
2623+
{
2624+
let stream = &stream.cached_info().config.name;
2625+
let consumer = serde_json::json!({
2626+
"stream_name": stream,
2627+
"config": {
2628+
"name": "consumer",
2629+
"sample_freq": "10%",
2630+
},
2631+
});
2632+
2633+
let response: Response<Info> = js
2634+
.request(format!("CONSUMER.CREATE.{}", stream), &consumer)
2635+
.await
2636+
.unwrap();
2637+
2638+
match response {
2639+
Response::Ok(info) => {
2640+
assert_eq!(info.config.sample_frequency, 10);
2641+
}
2642+
Response::Err { error } => panic!("expected ok response, got: {:?}", error),
2643+
}
2644+
}
26202645
}
26212646

26222647
#[tokio::test]

0 commit comments

Comments
 (0)