fix: honor --time-format rfc3339 for JSON output#127
Conversation
Previously, --time-format rfc3339 was silently ignored when using --format json, json-line, or json-pretty — the timestamp field always remained a numeric Unix value. build_json_object() now accepts a TimestampFormat parameter and emits a formatted RFC 3339 string when Rfc3339 is selected, while Unix (default) preserves the numeric f64 output byte-for-byte via the existing native serialization fallback. Fixes #123
There was a problem hiding this comment.
Pull request overview
Fixes #123 by making JSON outputs (json, json-line, json-pretty) honor --time-format rfc3339, so the timestamp field can be emitted as an RFC3339 string instead of always being a Unix numeric value.
Changes:
- Threaded
time_formatthrough JSON formatting sotimestampbecomes number (unix) or string (rfc3339) consistently for parse/search. - Updated CLI help text and README to state
--time-formatapplies to JSON formats too. - Added unit tests around JSON timestamp formatting.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils.rs | Updates TimestampFormat docs to reflect behavior across JSON and non-JSON formats. |
| src/bin/commands/search.rs | Updates clap help text for --time-format to include JSON formats. |
| src/bin/commands/parse.rs | Updates clap help text for --time-format to include JSON formats. |
| src/bin/commands/elem_format.rs | Threads time_format into JSON object construction and adds JSON timestamp tests. |
| README.md | Updates documentation to reflect JSON now honors --time-format. |
| CHANGELOG.md | Adds an Unreleased bug fix entry describing the behavior change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // If all default parse fields are selected, no collector field, and the default | ||
| // Unix timestamp format is in use, fall back to the original element serialization | ||
| // to preserve backward-compatible output byte-for-byte. | ||
| if fields == DEFAULT_FIELDS_PARSE | ||
| && !fields.contains(&"collector") | ||
| && time_format == TimestampFormat::Unix | ||
| { | ||
| return json!(elem); | ||
| } |
| let obj = build_json_object(&elem, DEFAULT_FIELDS_PARSE, None, TimestampFormat::Unix); | ||
| // With default fields + Unix, build_json_object should use native elem serialization | ||
| // which contains the full element (e.g. a `peer_ip` field). | ||
| assert!(obj.get("peer_ip").is_some()); | ||
| } |
build_json_object now always uses native BgpElem serialization for the default-fields case, overriding only the timestamp key when rfc3339 is selected. Previously, rfc3339 + default fields took the field-by-field path, dropping fields like origin_asns/only_to_customer that exist in native serialization but not in DEFAULT_FIELDS_PARSE. Also fix test to assert origin_asns (not in DEFAULT_FIELDS_PARSE) instead of peer_ip (which is), and add test for rfc3339 shape preservation. Addresses PR review feedback.
|
Addressed both review comments in 992979b: Line 305 (native serialization shape): Line 546 (test assertion): Replaced the |
| fn test_elem() -> BgpElem { | ||
| BgpElem { | ||
| timestamp: 1234567890.0, | ||
| elem_type: ElemType::ANNOUNCE, | ||
| peer_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)), | ||
| peer_asn: 65000.into(), | ||
| prefix: NetworkPrefix::from_str("10.0.0.0/8").unwrap(), | ||
| next_hop: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1))), | ||
| as_path: Some(AsPath { | ||
| segments: vec![AsPathSegment::AsSequence(vec![65000.into(), 65001.into()])], | ||
| }), | ||
| origin_asns: Some(vec![65001.into()]), | ||
| origin: Some(Origin::IGP), | ||
| local_pref: Some(100), | ||
| med: Some(0), | ||
| communities: None, | ||
| atomic: false, | ||
| aggr_asn: None, | ||
| aggr_ip: None, | ||
| only_to_customer: None, | ||
| unknown: None, | ||
| deprecated: None, | ||
| } | ||
| } |
| let obj = build_json_object(&elem, &fields, None, TimestampFormat::Unix); | ||
| let ts = obj.get("timestamp").unwrap(); | ||
| assert_eq!(ts.as_f64().unwrap(), 1234567890.0); | ||
| } |
| let obj = build_json_object(&elem, &fields, None, TimestampFormat::Rfc3339); | ||
| let ts = obj.get("timestamp").unwrap(); | ||
| assert!(ts.is_string(), "expected string for rfc3339 timestamp"); | ||
| let s = ts.as_str().unwrap(); | ||
| assert!(s.contains("2009")); |
| let unix_out = format_elem( | ||
| &elem, | ||
| OutputFormat::JsonLine, | ||
| &fields, | ||
| None, | ||
| TimestampFormat::Unix, | ||
| ) | ||
| .unwrap(); | ||
| let rfc_out = format_elem( |
| let rfc_out = format_elem( | ||
| &elem, | ||
| OutputFormat::JsonLine, | ||
| &fields, | ||
| None, | ||
| TimestampFormat::Rfc3339, | ||
| ) | ||
| .unwrap(); | ||
|
|
| let unix_json: serde_json::Value = serde_json::from_str(&unix_out).unwrap(); | ||
| let rfc_json: serde_json::Value = serde_json::from_str(&rfc_out).unwrap(); | ||
|
|
||
| assert!(unix_json.get("timestamp").unwrap().is_number()); | ||
| assert!(rfc_json.get("timestamp").unwrap().is_string()); | ||
| } |
| // Timestamp stays numeric for Unix | ||
| assert!(obj.get("timestamp").unwrap().is_number()); | ||
| } |
| // But the timestamp should be overridden to a string | ||
| let ts = obj.get("timestamp").unwrap(); | ||
| assert!(ts.is_string(), "rfc3339 timestamp should be a string"); | ||
| assert!(ts.as_str().unwrap().contains('T')); | ||
| } |
Fixes #123
--time-format rfc3339was being ignored forjson,json-line, andjson-prettyoutput — thetimestampfield always stayed a numeric Unix value even when you asked for RFC 3339.Root cause was in
build_json_object(), which had a hardcoded "JSON always uses Unix timestamp" comment and didn't thread thetime_formatparameter through. Now it does:--time-format unix(default) → numerictimestampfield, unchanged for backward compat--time-format rfc3339→ stringtimestampfield like"2024-08-23T14:15:00+00:00"Applies to
parseandsearchconsistently. Updated the clap help text and README which previously said "non-JSON output" — that's no longer accurate.Added tests covering both formats for json-line output.