Skip to content

fix: fall back to call-site default for null variant properties#34

Draft
fabriziodemaria wants to merge 1 commit into
spotify:mainfrom
fabriziodemaria:fix/null-variant-property-falls-back-to-default
Draft

fix: fall back to call-site default for null variant properties#34
fabriziodemaria wants to merge 1 commit into
spotify:mainfrom
fabriziodemaria:fix/null-variant-property-falls-back-to-default

Conversation

@fabriziodemaria

Copy link
Copy Markdown
Member

Summary

Closes #33.

When a resolved flag property has the value null, the SDK currently serves the type's zero value (false / 0 / 0.0 / "") instead of the call-site default. A null property means the variant defines no concrete value, so resolution should fall back to the call-site default — the same behaviour as a missing property, and what every other Confidence SDK (and all local-resolve providers in confidence-resolver) does.

Root cause

In confidence/src/models.rs, into_value coerces each field by schema type:

SchemaType::BoolType   => ConfidenceValue::Bool(value.as_bool().unwrap_or_default()),
SchemaType::StringType => ConfidenceValue::String(value.as_str().unwrap_or_default().to_string()),
// …

For a JSON null, serde_json's as_bool() / as_i64() / as_f64() / as_str() return None, so unwrap_or_default() produces the zero value. That concrete zero is stored as e.g. ConfidenceValue::Bool(false), then process_flag returns it with TargetingMatch and the call-site default is never consulted. (ConfidenceValue has no Null variant, so null can't be represented as a value.)

Fix

Skip null-valued fields during conversion. The downstream path lookup in process_flag then misses, which surfaces the call-site default — exactly how a missing property already behaves. This is the minimal change; it deliberately treats null as "absent" rather than introducing a new ConfidenceValue::Null variant (which would touch every exhaustive match).

Tests

Adds two regression tests in models.rs:

  • null_valued_property_is_omitted_so_resolution_falls_back_to_default — a top-level null property is omitted (would be Bool(false) before the fix), non-null siblings untouched.
  • null_valued_nested_property_is_omitted — same for a property inside a nested struct.

cargo test in confidence/: 12 passed (10 prior + 2 new).

Why draft

Opening as draft for maintainer review of the approach. Two open questions:

  1. Minimal (this PR) vs. model-complete: should null instead be a first-class ConfidenceValue::Null so the native get_flag returns Ok(default) rather than routing through the not-found/Err path? The OpenFeature provider returns the default either way, but the native API semantics differ.
  2. Formatting: the crate isn't cargo fmt-clean on main (e.g. confidence_value.rs uses 2-space indent), so I matched models.rs's existing 4-space style rather than running cargo fmt. Happy to align to whatever you prefer.

Context

Found while auditing null-property resolution semantics across all Confidence SDKs + the local-resolve providers. 12 of 13 implementations return the call-site default for a null property; this standalone Rust SDK was the lone exception.

Made with Cursor

A resolved flag property whose value is `null` was coerced to the type's
zero value (`false` / `0` / `0.0` / ""`) via `unwrap_or_default()` in
`into_value`, silently replacing the call-site default. A null property
means the variant defines no value, so resolution must fall back to the
call-site default — the same behaviour as a missing property, and what
every other Confidence SDK (and all local-resolve providers) does.

Skip null-valued fields during conversion so the downstream path lookup
misses and the default is served. Adds regression tests for a null
top-level property and a null nested-struct property.

Fixes spotify#33

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

Null variant property resolves to type zero-value instead of the call-site default

1 participant