fix(NATSRS-003): StreamConfig.max_age uses serde_nanos without skip_serializing_if, sending zero duration on the wire - #48
Conversation
…erializing_if, sending zero duration on the wire
There was a problem hiding this comment.
🦩 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 alphabet — nats/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.
| /// 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 |
There was a problem hiding this comment.
🦩 🟠 ✅ 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.
| /// 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 |
There was a problem hiding this comment.
🦩 🟠 ✅ 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 { | |||
There was a problem hiding this comment.
🦩 🟠 ✅ 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.
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.
nats/src/jetstream/types.rs:290nats/src/jetstream/types.rs:218nats/src/jetstream/types.rs:26nats/src/jetstream/types.rs:96What 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-0698bc64d01bMerging 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.