Skip to content

feat(ext): serialize isoneof constraint values as YAML lists#6072

Merged
kodiakhq[bot] merged 5 commits into
flipt-io:v2from
jgaul3:use-yaml-list
Jun 23, 2026
Merged

feat(ext): serialize isoneof constraint values as YAML lists#6072
kodiakhq[bot] merged 5 commits into
flipt-io:v2from
jgaul3:use-yaml-list

Conversation

@jgaul3

@jgaul3 jgaul3 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description:

Editing isoneof/isnotoneof values produces noisy diffs in the resulting PRs. The list of values is stored as a stringified JSON list rather than a native YAML list. This PR adds a custom MarshalYAML/UnmarshalYAML on Constraint, so now

value: '["org-1", "org-2", "org-3"]'

gets stored as

value:
    - org-1
    - org-2
    - org-3

sorted lexicographically. As a result, git diffs are minimal and exactly indicate which elements are added or removed.

Backwards Compatibility

Old JSON-string formatted files are read just fine and don't require updates. The next time the files are edited through the UI, they'll be reformatted into yaml lists.

YAML special values

I don't want to cause Norway problems, and in yaml.v3 I don't think I will. UnmarshalYAML coerces bool and nil from hand-written files just in case and TestConstraint_UnmarshalYAML_BareYAMLSpecialValues confirms this behavior.

Signed-off-by: Jon Gaul <[email protected]>

# Conflicts:
#	internal/storage/fs/snapshot_test.go
@jgaul3
jgaul3 requested a review from a team as a code owner June 22, 2026 06:23
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 22, 2026
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.58559% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.65%. Comparing base (c660605) to head (62e55b1).

Files with missing lines Patch % Lines
internal/ext/common.go 84.61% 11 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##               v2    #6072      +/-   ##
==========================================
+ Coverage   61.45%   61.65%   +0.20%     
==========================================
  Files         142      142              
  Lines       14323    14430     +107     
==========================================
+ Hits         8802     8897      +95     
- Misses       4783     4790       +7     
- Partials      738      743       +5     
Flag Coverage Δ
integrationtests 34.28% <26.12%> (-0.09%) ⬇️
unittests 52.86% <85.58%> (+0.29%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve

This PR cleanly implements native YAML/JSON list serialization for isoneof/isnotoneof constraint values, bumps the format version to 1.6, and keeps backward compatibility for old stringified-JSON files. Both the CUE and JSON schemas are updated consistently, and the storage layer correctly bumps document versions on rewrite so validation stays in sync. The test suite covers marshaling, unmarshaling, round-trips, old-format normalization, bare YAML special values, and evaluation parity across 1.5 and 1.6 files. The two review points raised in the prior discussion (empty-value validation and JSON schema consistency) are both addressed in the current diff by the custom MarshalJSON/UnmarshalJSON pair and the unconditional list return for isoneof operators in MarshalYAML. No critical, major, or minor issues remain.

🤖 Automated review by the Flipt PR review agent.

- Add v1.6 as a valid document version (CUE + JSON Schema)
- Split isoneof/isnotoneof constraint values: string for <1.6, array for 1.6+
- Align JSON Schema with CUE via version-aware root-level allOf
- Set namespace documents to latest version on write
- Test to verify v1.5 and v1.6 fixtures

Signed-off-by: Roman Dmytrenko <[email protected]>
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 22, 2026
@erka erka added the v2 Flipt v2 label Jun 22, 2026
Comment thread internal/ext/common.go Outdated
func (c Constraint) MarshalYAML() (any, error) {
if c.Operator == "isoneof" || c.Operator == "isnotoneof" {
var list []any
if c.Value != "" && json.Unmarshal([]byte(c.Value), &list) == nil && len(list) > 0 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty / [] isoneof in a 1.6 doc will fail validation.

When the value is "", "[]", or otherwise not a non-empty JSON array, this falls through to the alias(c) path and serializes value as a string (or omits it via omitempty). But the new 1.6 CUE schema requires value: [...string] (a list, present — not optional), so writing such a constraint to a 1.6 file will fail validation in documentEncoder.Close(), where the string form previously saved fine.

This is reachable if the UI lets a user clear all selections on an isoneof/isnotoneof constraint. Suggest coercing the fallback to an empty list [] for isoneof operators (or making value optional for isoneof in the 1.6 CUE branch), and adding an empty-value case to TestSegmentStorage_PutResource_IsOneOf.

Comment thread internal/ext/common.go
Description string `yaml:"description,omitempty"`
}

func (c Constraint) MarshalYAML() (any, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON serialization is now inconsistent with the published schema.

This adds MarshalYAML/UnmarshalYAML but no MarshalJSON/UnmarshalJSON, so the JSON format keeps the stringified value. Meanwhile flipt.json now requires arrays for 1.6 isoneof (constraintValueV16) and constraint.value permits arrays. A hand-written 1.6 JSON document that follows the published schema (array value) can't be unmarshaled into Constraint.Value string, and JSON export still emits the string form — contradicting the schema.

If JSON feature files are a supported path in v2, this needs matching JSON (un)marshaling; if at-rest is effectively YAML-only, please note the intent so the schema/behavior drift is deliberate.

@erka
erka requested a review from markphelps June 23, 2026 14:17

@markphelps markphelps left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! thank you @jgaul3 for the idea/implementation. and thank you as always @erka

@erka erka added the automerge Used by Kodiak bot to automerge PRs label Jun 23, 2026
@kodiakhq
kodiakhq Bot merged commit 5892c8f into flipt-io:v2 Jun 23, 2026
29 of 30 checks passed
@github-project-automation github-project-automation Bot moved this to Done in Flipt V2 Jun 23, 2026
@jgaul3

jgaul3 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@erka @markphelps Thank you both for the help polishing this up and thank you for the review and feedback!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Used by Kodiak bot to automerge PRs size:L This PR changes 100-499 lines, ignoring generated files. v2 Flipt v2

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants