{"record":{"id":"4b6c14f3d8a06a21","repo":"vectordotdev/vector","slug":"unit-structs-should-be-rejected-during-ast-parsing","errorCode":null,"errorMessage":"unit structs should be rejected during AST parsing","messagePattern":"unit structs should be rejected during AST parsing","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/vector-config-macros/src/configurable.rs","lineNumber":220,"sourceCode":"            variants\n                .iter()\n                .filter(|variant| variant.tagging() == &Tagging::None)\n                .count()\n                >= 2\n        }\n    }\n}\n\nfn build_struct_generate_schema_fn(\n    container: &Container<'_>,\n    style: &Style,\n    fields: &[Field<'_>],\n) -> proc_macro2::TokenStream {\n    match style {\n        Style::Struct => build_named_struct_generate_schema_fn(container, fields),\n        Style::Tuple => build_tuple_struct_generate_schema_fn(fields),\n        Style::Newtype => build_newtype_struct_generate_schema_fn(fields),\n        Style::Unit => panic!(\"unit structs should be rejected during AST parsing\"),\n    }\n}\n\nfn generate_struct_field(field: &Field<'_>, sibling_keys: &[String]) -> proc_macro2::TokenStream {\n    let field_metadata_ref = Ident::new(\"field_metadata\", Span::call_site());\n    let field_metadata = generate_field_metadata(&field_metadata_ref, field);\n    let field_schema_ty = get_field_schema_ty(field);\n\n    // Flattened `Option<T>` cannot use `Option`'s nullable-property schema: `allOf` merge\n    // validates the parent object, which is never JSON `null`. Tagged enums get an absence\n    // encoding instead; other `Option<T>` flatten fields fall back to the property schema.\n    let spanned_generate_schema = match (field.flatten(), option_inner_type(field_schema_ty)) {\n        (true, Some(inner_ty)) => {\n            let sibling_key_lits = sibling_keys.iter().map(|key| quote! { #key });\n            quote_spanned! {field.span()=>\n                ::vector_config::schema::generate_flattened_optional_schema(\n                    &<#inner_ty as ::vector_config::Configurable>::as_configurable_ref(),\n                    &<#field_schema_ty as ::vector_config::Configurable>::as_configurable_ref(),","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/lib/vector-config-macros/src/configurable.rs#L202-L238","documentation":"Same panic family as errorIndex 8 but on the rate-metric branch of `decode_ddseries_v2` (src/sources/datadog_agent/metrics.rs:345): `Utc.timestamp_opt(dd_point.timestamp, 0).single().expect(\"invalid timestamp\")` panics when the Datadog point's seconds-since-epoch timestamp is outside chrono's supported range. It affects rate/count-type points (branch with `.with_interval_ms(non_rate_interval)`).","triggerScenarios":"A v2 series payload with a rate/count point whose `timestamp` i64 cannot be represented as a UTC datetime — epoch-milliseconds supplied where seconds are expected, negative values, or i64-near-max garbage from a malformed/protobuf-unmarshal-adjacent issue.","commonSituations":"Third-party emitters masquerading as the Datadog agent sending wrong timestamp units; corrupted or truncated protobuf payloads; test fixtures with placeholder timestamps like 0 or i64::MAX.","solutions":["Validate `dd_point.timestamp` against a sane epoch-seconds range before building the metric and drop the point with a warning on failure.","Use `.single()` result directly (Option) and skip the point instead of expect, mirroring the non-rate branch fix.","Convert milliseconds to seconds (`ts / 1000`) if the upstream emitter documents ms units."],"exampleFix":"// before\n.with_timestamp(Some(\n    Utc.timestamp_opt(dd_point.timestamp, 0)\n        .single()\n        .expect(\"invalid timestamp\"),\n))\n// after\nlet ts = Utc.timestamp_opt(dd_point.timestamp, 0).single()?;\n// ... .with_timestamp(Some(ts))","handlingStrategy":"validation","validationCode":"fn valid_dd_timestamp(secs: i64) -> bool {\n    (0..=253_402_300_799).contains(&secs)\n}","typeGuard":"fn dd_ts_to_utc(secs: i64) -> Option<DateTime<Utc>> {\n    if !(0..=253_402_300_799).contains(&secs) { return None; }\n    Utc.timestamp_opt(secs, 0).single()\n}","tryCatchPattern":"// In the rate branch, propagate None upward:\nlet ts = Utc.timestamp_opt(dd_point.timestamp, 0).single()?;\n// builder.with_timestamp(Some(ts))","preventionTips":["Apply the same timestamp guard on every decode branch (gauge and rate), not just one.","Reject or rescale out-of-range timestamps with a warning metric for observability.","Share one helper for point-timestamp conversion across all Datadog metric types."],"tags":["datadog","metrics","timestamp","chrono","rust","panic"],"backgroundTag":"value-out-of-range","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}