Skip to content

fix(NATSRS-003): StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire - #48

Draft
flamingo[bot] wants to merge 1 commit into
mainfrom
ai-fix/natsrs-003-3-1bff4c8b
Draft

fix(NATSRS-003): StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire#48
flamingo[bot] wants to merge 1 commit into
mainfrom
ai-fix/natsrs-003-3-1bff4c8b

Conversation

@flamingo

@flamingo flamingo Bot commented Aug 11, 2026

Copy link
Copy Markdown

Closes findings from rule NATSRS-003 — StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire.

Draft — this is a starting point, not a finished change. The fix required judgment, so read it before trusting it.

# Status Finding Location
1 ✅ fixed StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire nats/src/jetstream/types.rs:290
2 ✅ fixed StreamConfig fields max_bytes, max_msgs, max_msgs_per_subject, max_consumers lack skip_serializing_if nats/src/jetstream/types.rs:218
3 ✅ fixed StreamMessageGetRequest.seq is Option but uses is_default instead of Option::is_none nats/src/jetstream/types.rs:26
4 ⚠️ not fixed base64::decode used in nats crate is the deprecated v0.13 API which panics on invalid alphabet nats/src/jetstream/types.rs:96

What changed — and what was deliberately left — is explained per finding as inline review comments on the lines each finding touched.


Run: https://product-hub.flamingo.so/admin/code-review
Run id: 1bff4c8b-6382-4a91-9010-0698bc64d01b

Merging this PR is recorded as acceptance of the rule that produced it;
closing it unmerged is recorded as rejection. Both feed rule health, so
closing a wrong suggestion is useful rather than merely tidy.

…erializing_if, sending zero duration on the wire

@flamingo flamingo Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 What this fix changed, finding by finding

4 finding(s) — 3 fixed (3 explained inline on the diff), 1 not fixed.

⚠️ Not fixed — needs human judgment

🔴 4. base64::decode used in nats crate is the deprecated v0.13 API which panics on invalid alphabetnats/src/jetstream/types.rs:96
NOT FIXED: The base64::decode calls in TryFrom<RawStreamMessage> for StreamMessage (lines ~88 and ~100) use the base64 v0.13 API. A mechanical fix would require changing the base64 dependency version in nats/Cargo.toml to v0.21+ and updating call sites to use base64::engine::general_purpose::STANDARD.decode(...). This is a multi-file change (Cargo.toml + this file) and carries risk of breaking other callers of the base64 crate within the same crate. The nats crate is also noted as being in maintenance mode. Instead, explanatory comments have been added at both call sites documenting the limitation and the correct fix path. The logic itself is left unchanged.

🤖 Prompt for AI agents
In nats/src/jetstream/types.rs around line 96, address this code-review finding: base64::decode used in nats crate is the deprecated v0.13 API which panics on invalid alphabet.
The `base64::decode` calls in `TryFrom<RawStreamMessage> for StreamMessage` (lines ~88 and ~100) use the `base64` v0.13 API. A mechanical fix would require changing the `base64` dependency version in `nats/Cargo.toml` to v0.21+ and updating call sites to use `base64::engine::general_purpose::STANDARD.decode(...)`. This is a multi-file change (Cargo.toml + this file) and carries risk of breaking other callers of the `base64` crate within the same crate. The `nats` crate is also noted as being in maintenance mode. Instead, explanatory comments have been added at both call sites documenting the limitation and the correct fix path. The logic itself is left unchanged.
Make the minimal change that resolves the finding; do not refactor unrelated code.

Comment on lines 335 to 347
/// A name for the Stream. Must not have spaces, tabs or period `.` characters
pub name: String,
/// How large the Stream may become in total bytes before the configured discard policy kicks in
#[serde(default, skip_serializing_if = "is_default")]
pub max_bytes: i64,
/// How large the Stream may become in total messages before the configured discard policy kicks in
#[serde(default, skip_serializing_if = "is_default")]
pub max_msgs: i64,
/// Maximum amount of messages to keep per subject
#[serde(default, skip_serializing_if = "is_default")]
pub max_msgs_per_subject: i64,
/// When a Stream has reached its configured `max_bytes` or `max_msgs`, this policy kicks in.
/// `DiscardPolicy::New` refuses new messages or `DiscardPolicy::Old` (default) deletes old messages to make space

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 ✅ Fixed — StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire

FIXED: StreamConfig.max_age (line 290) — added skip_serializing_if = "is_default" to the existing #[serde(with = "serde_nanos")] attribute, making it #[serde(with = "serde_nanos", skip_serializing_if = "is_default")]. This matches the pattern used by all other Duration fields in the struct and suppresses serialization when the value is Duration::ZERO.

Comment on lines 335 to 347
/// A name for the Stream. Must not have spaces, tabs or period `.` characters
pub name: String,
/// How large the Stream may become in total bytes before the configured discard policy kicks in
#[serde(default, skip_serializing_if = "is_default")]
pub max_bytes: i64,
/// How large the Stream may become in total messages before the configured discard policy kicks in
#[serde(default, skip_serializing_if = "is_default")]
pub max_msgs: i64,
/// Maximum amount of messages to keep per subject
#[serde(default, skip_serializing_if = "is_default")]
pub max_msgs_per_subject: i64,
/// When a Stream has reached its configured `max_bytes` or `max_msgs`, this policy kicks in.
/// `DiscardPolicy::New` refuses new messages or `DiscardPolicy::Old` (default) deletes old messages to make space

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 ✅ Fixed — StreamConfig fields max_bytes, max_msgs, max_msgs_per_subject, max_consumers lack skip_serializing_if

FIXED: StreamConfig fields max_bytes, max_msgs, max_msgs_per_subject, max_consumers (lines 218–228) — added #[serde(default, skip_serializing_if = "is_default")] to each of the four fields (max_bytes: i64, max_msgs: i64, max_msgs_per_subject: i64, max_consumers: i32). These fields previously had no serde attribute at all; they now skip serialization when equal to their Default::default() value (0).

@@ -24,10 +24,10 @@ pub type DateTime = time::OffsetDateTime;

#[derive(Serialize)]
pub(crate) struct StreamMessageGetRequest {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 ✅ Fixed — StreamMessageGetRequest.seq is Option but uses is_default instead of Option::is_none

FIXED: StreamMessageGetRequest.seq and StreamMessageGetRequest.last_by_subject (lines 26–31) — changed skip_serializing_if = "is_default" to skip_serializing_if = "Option::is_none" on both seq: Option<u64> and last_by_subject: Option<String>. This is the canonical and semantically correct guard for Option<T> fields per the finding's guidance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants