Skip to content

fix: omit null object properties when serializing events - #251

Draft
marandaneto wants to merge 1 commit into
mainfrom
fix/null-property-serialization-sqq3ojkm
Draft

marandaneto wants to merge 1 commit into
mainfrom
fix/null-property-serialization-sqq3ojkm

Conversation

@marandaneto

@marandaneto marandaneto commented Sep 9, 2026

Copy link
Copy Markdown
Member

💡 Motivation and Context

Captured custom properties currently send null object members. This implements the serialization contract in PostHog/sdk-specs#60 without narrowing the public property API.

Both v0 and v1 wire serializers use one private helper to omit null object members recursively after hooks and enrichment. Null array entries keep their positions. Empty objects and arrays, other scalar values, and caller-owned events stay unchanged. Queued, batch, immediate, AI-named and exception events use the same boundary.

Generated missing/error feature flag response nulls retain their existing meaning only on $feature_flag_called, for $feature_flag_response and the exact $feature/<key> selected by $feature_flag. Privacy filtering remains authoritative. Envelope fields and unrelated caches are unchanged.

Includes a manually authored Sampo patch entry using the repository's required package key.

💚 How did you test it?

  • Both permanent loopback HTTP tests pass across blocking/async clients, v0/v1 capture, and error tracking enabled/disabled (eight feature combinations).
  • Focused serializer, event, envelope and minimal flag privacy unit tests pass in four error-tracking-enabled builds (21 tests per v0 build and 40 per v1 build).
  • Formatting and the public API snapshot check pass. Strict Clippy passes for both async builds.
  • Known baseline limitation: strict blocking Clippy fails on existing clippy::type_complexity at src/client/blocking.rs:706, also reproduced on unchanged baseline. Both blocking builds pass with only that lint allowed on the command line. No source suppression was added.
  • Isolated committed-head review reports no actionable findings. All 25 hosted checks passed for eb246af5dab6041f1a7fe56cb4db8325b3fd6384, including the CI test matrix, public API, CodeQL and both SDK compliance workflows. Existing compliance coverage does not prove all null-policy scenarios.
  • Full suites, minimum Rust versions, performance, local-poller and retry/compression-specific null behavior were not validated locally. The SDK has no disk-backed event queue to exercise. This draft is not release or universal conformance clearance.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran sampo add to generate a changeset file

The equivalent Sampo patch file was authored manually as permitted by RELEASING.md. No release command was run.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

posthog-rs-v0 Compliance Report

Date: 2026-09-09 09:35:38 UTC
Duration: 15377ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 100ms
Format Validation.Event Has Uuid 105ms
Format Validation.Event Has Lib Properties 98ms
Format Validation.Distinct Id Is String 98ms
Format Validation.Token Is Present 98ms
Format Validation.Custom Properties Preserved 98ms
Format Validation.Event Has Timestamp 97ms
Retry Behavior.Retries On 503 5106ms
Retry Behavior.Does Not Retry On 400 2102ms
Retry Behavior.Does Not Retry On 401 2100ms
Retry Behavior.Respects Retry After Header 5070ms
Retry Behavior.Implements Backoff 15074ms
Retry Behavior.Retries On 500 5071ms
Retry Behavior.Retries On 502 5070ms
Retry Behavior.Retries On 504 5070ms
Retry Behavior.Max Retries Respected 15074ms
Deduplication.Generates Unique Uuids 71ms
Deduplication.Preserves Uuid On Retry 5016ms
Deduplication.Preserves Uuid And Timestamp On Retry 10022ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5026ms
Deduplication.No Duplicate Events In Batch 18ms
Deduplication.Different Events Have Different Uuids 16ms
Compression.Sends Gzip When Enabled 16ms
Batch Format.Uses Proper Batch Structure 16ms
Batch Format.Flush With No Events Sends Nothing 33ms
Batch Format.Multiple Events Batched Together 85ms
Error Handling.Does Not Retry On 403 2047ms
Error Handling.Does Not Retry On 413 2053ms
Error Handling.Retries On 408 5065ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 31ms
Request Payload.Flags Request Uses V2 Query Param 34ms
Request Payload.Flags Request Hits Flags Path Not Decide 28ms
Request Payload.Flags Request Omits Authorization Header 17ms
Request Payload.Token In Flags Body Matches Init 14ms
Request Payload.Groups Round Trip 27ms
Request Payload.Groups Default To Empty Object 24ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 47ms
Request Payload.Disable Geoip Omitted Defaults To False 33ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 32ms
Request Lifecycle.No Flags Request On Init Alone 10ms
Request Lifecycle.No Flags Request On Normal Capture 29ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 31ms
Request Lifecycle.Mock Response Value Is Returned To Caller 31ms
Retry Behavior.Retries Flags On 502 218ms
Retry Behavior.Retries Flags On 504 221ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 23ms

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

posthog-rs-v1 Compliance Report

Date: 2026-09-09 09:35:42 UTC
Duration: 23092ms

✅ All Tests Passed!

111/111 tests passed


Capture_V1 Tests

94/94 tests passed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint 176ms
Endpoint And Method.Does Not Use Legacy Endpoints 173ms
Required Headers.Has Authorization Bearer Header 175ms
Required Headers.Has Content Type Json 181ms
Required Headers.Has Posthog Sdk Info Format 175ms
Required Headers.Has Posthog Attempt Header 174ms
Required Headers.Has Posthog Request Id 173ms
Required Headers.Has Posthog Request Timestamp 173ms
Required Headers.Has User Agent 171ms
Body Format.Body Has Created At And Batch 172ms
Body Format.No Api Key In Body 147ms
Body Format.No Sent At In Body 131ms
Event Format.Event Has Required Root Fields 146ms
Event Format.Event Uuid Is Valid 145ms
Event Format.Event Timestamp Is Rfc3339 146ms
Event Format.Distinct Id Is String 154ms
Event Format.Distinct Id At Root Not Properties 144ms
Event Format.Custom Properties Preserved 144ms
Event Format.Set Properties Preserved 145ms
Event Format.Set Once Properties Preserved 140ms
Event Format.Groups Properties Preserved 156ms
Event Format.Sdk Generates Uuid If Not Provided 140ms
Event Format.Event Has Required Root Fields Batch 180ms
Event Format.Event Uuid Is Valid Batch 199ms
Event Format.Event Timestamp Is Rfc3339 Batch 199ms
Event Format.Distinct Id Is String Batch 204ms
Event Format.Distinct Id At Root Not Properties Batch 198ms
Event Format.Custom Properties Preserved Batch 198ms
Event Format.Set Properties Preserved Batch 198ms
Event Format.Set Once Properties Preserved Batch 211ms
Event Format.Groups Properties Preserved Batch 110ms
Event Format.Sdk Generates Uuid If Not Provided Batch 161ms
Batch Behavior.Multiple Events In Single Batch 216ms
Batch Behavior.Batch Envelope Smoke 193ms
Batch Behavior.Flush With No Events Sends Nothing 120ms
Batch Behavior.Flush At Triggers Batch 1145ms
Batch Behavior.Created At Reflects Batch Creation Time 137ms
Deduplication.Generates Unique Uuids 223ms
Deduplication.Different Events Same Content Different Uuids 173ms
Deduplication.Preserves Uuid On Retry 5146ms
Deduplication.Preserves Timestamp On Retry 5129ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5133ms
Deduplication.No Duplicate Events In Batch 162ms
Header Behavior On Retry.Attempt Header Starts At One 82ms
Header Behavior On Retry.Attempt Header Increments On Retry 10085ms
Header Behavior On Retry.Request Id Preserved On Retry 5090ms
Header Behavior On Retry.Different Requests Have Different Request Ids 2099ms
Header Behavior On Retry.Request Timestamp Changes On Retry 5076ms
Response Format Validation.Success Response Has Uuid Keyed Results 83ms
Response Format Validation.Success Response Has Ok For Each Event 47ms
Response Format Validation.Success No Retry After When All Ok 34ms
Response Format Validation.Success Retry After Present When Retry Events 38ms
Response Format Validation.Success No Retry After When Drop Only 34ms
Response Format Validation.Response Echoes Request Id 32ms
Retry Behavior.Retries On 408 5039ms
Retry Behavior.Retries On 500 5028ms
Retry Behavior.Retries On 503 5027ms
Retry Behavior.Retries On 504 5026ms
Retry Behavior.Retryable Errors Have Retry After 2027ms
Retry Behavior.Respects Retry After On Retryable Error 8027ms
Retry Behavior.Does Not Retry On 400 2026ms
Retry Behavior.Does Not Retry On 401 2028ms
Retry Behavior.Does Not Retry On 402 2027ms
Retry Behavior.Does Not Retry On 413 2029ms
Retry Behavior.Does Not Retry On 415 2028ms
Retry Behavior.Non Retryable Errors Have No Retry After 2025ms
Retry Behavior.Implements Backoff 15027ms
Retry Behavior.Max Retries Respected 15026ms
Partial Batch Handling.Handles 200 Full Success 2029ms
Partial Batch Handling.Handles 200 With All Ok 3036ms
Partial Batch Handling.Does Not Retry Dropped Events 3039ms
Partial Batch Handling.Does Not Retry Limited Events 3034ms
Partial Batch Handling.Prunes Ok Events On Partial Retry 5032ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry 5026ms
Partial Batch Handling.Retries Only Retry Events From Partial 5028ms
Partial Batch Handling.Partial Retry Preserves Uuids 5027ms
Partial Batch Handling.Partial Retry Attempt Header Increments 5031ms
Partial Batch Handling.Partial Retry Request Id Preserved 5032ms
Partial Batch Handling.Respects Retry After On Partial 5026ms
Partial Batch Handling.Unknown Result Treated As Terminal 3025ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry 3035ms
Compression.Sends Gzip Content Encoding 26ms
Compression.No Content Encoding When Disabled 24ms
Compression.Compressed Body Is Decompressible 24ms
Error Handling.Does Not Retry On Unknown 4Xx 2024ms
Event Options.Cookieless Mode Override 26ms
Event Options.Disable Skew Correction Override 24ms
Event Options.Process Person Profile Override 24ms
Event Options.Product Tour Id Override 23ms
Event Options.Unset Options Omitted 24ms
Event Options.Options Override In Batch 24ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties 23ms
Geoip And Historical Migration.Historical Migration Set In Body 24ms
Geoip And Historical Migration.Historical Migration Absent By Default 24ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 16ms
Request Payload.Flags Request Uses V2 Query Param 17ms
Request Payload.Flags Request Hits Flags Path Not Decide 16ms
Request Payload.Flags Request Omits Authorization Header 16ms
Request Payload.Token In Flags Body Matches Init 18ms
Request Payload.Groups Round Trip 16ms
Request Payload.Groups Default To Empty Object 15ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 15ms
Request Payload.Disable Geoip Omitted Defaults To False 18ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 16ms
Request Lifecycle.No Flags Request On Init Alone 12ms
Request Lifecycle.No Flags Request On Normal Capture 28ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 20ms
Request Lifecycle.Mock Response Value Is Returned To Caller 17ms
Retry Behavior.Retries Flags On 502 222ms
Retry Behavior.Retries Flags On 504 220ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 25ms

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.

1 participant