Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions flashduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ func TestOptionalObjectRequestFieldOmitsWhenUnset(t *testing.T) {
}
}

// TestDashboardVariableOmitsUnsetVariableQuery guards the flattened-oneOf case
// of the rule above: DashboardVariable is one struct for the datasource,
// custom, and query arms, and only the query arm carries variable_query. The
// backend rejects unknown fields per arm, so a datasource variable sent with
// `"variable_query":{"kind":""}` fails the whole dashboard create, update, or
// panel preview.
func TestDashboardVariableOmitsUnsetVariableQuery(t *testing.T) {
body, err := json.Marshal(DashboardVariable{
Kind: "datasource",
Name: "metrics_ds",
DatasourceType: "prometheus",
})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(body), `"variable_query"`) {
t.Fatalf("unset VariableQuery must be omitted from the wire, got %s", body)
}

body, err = json.Marshal(DashboardVariable{
Kind: "query",
Name: "service",
VariableQuery: DashboardVariableQuery{Kind: "prometheus", Metric: "up", Label: "job"},
})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(body), `"variable_query":{`) {
t.Fatalf("set VariableQuery must be present on the wire, got %s", body)
}
}

// TestResetPostMortemContentSendsZeroExpectedRevision guards a codegen
// contract: expected_revision is a required field where 0 is a valid value
// (first write to a never-saved document, per the spec's minimum: 0), so it
Expand Down
12 changes: 9 additions & 3 deletions internal/cmd/gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1170,8 +1170,9 @@ func pointerizableScalar(gt string) bool {
// isStructSchema reports whether property schema s is emitted as a Go struct
// (as opposed to a scalar, enum, alias, slice, or map). It resolves s the same
// way emitModels' top-level classification switch does (following $ref and
// allOf). Callers use this both to emit `,omitzero` for request values and to
// pointer-wrap nullable or absence-preserving fields.
// allOf, and flattening a named oneOf of object arms into one struct). Callers
// use this both to emit `,omitzero` for request values and to pointer-wrap
// nullable or absence-preserving fields.
func (g *Gen) isStructSchema(s map[string]any) bool {
if s == nil {
return false
Expand All @@ -1180,7 +1181,12 @@ func (g *Gen) isStructSchema(s map[string]any) bool {
if g.skip[ref] {
return false
}
return g.isStructSchema(asMap(g.schemas[ref]))
// emitModels queues a named schema as g.resolveObject(name), which
// flattens a oneOf of object arms into a single struct. Classify that
// emitted shape, not the raw oneOf, or an optional field of such a type
// gets `,omitempty` — a no-op on a struct value — and an unset field
// goes on the wire as an object holding only its zero discriminator.
return g.isStructSchema(g.resolveObject(ref))
}
if len(asSlice(s["allOf"])) > 0 {
s = g.mergeAllOf(s)
Expand Down
45 changes: 45 additions & 0 deletions internal/cmd/gen/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,51 @@ func TestEmitStructPreserveAbsenceRequestObjectPointerWraps(t *testing.T) {
}
}

// TestEmitStructOptionalFlattenedOneOfRequestFieldOmitsZero covers an optional
// request field whose $ref target is a oneOf of object arms. emitModels
// flattens that oneOf into one struct, so the field is a bare struct value and
// needs `,omitzero`: `,omitempty` never drops a struct, and the unset field
// would reach the wire as an object holding only an empty discriminator.
func TestEmitStructOptionalFlattenedOneOfRequestFieldOmitsZero(t *testing.T) {
g := newTestGen(map[string]any{
"PromQuery": map[string]any{
"type": "object",
"required": []any{"kind"},
"properties": map[string]any{
"kind": map[string]any{"type": "string"},
"metric": map[string]any{"type": "string"},
},
},
"LogsQuery": map[string]any{
"type": "object",
"required": []any{"kind"},
"properties": map[string]any{
"kind": map[string]any{"type": "string"},
"field": map[string]any{"type": "string"},
},
},
"VariableQuery": map[string]any{
"oneOf": []any{
map[string]any{"$ref": "#/components/schemas/PromQuery"},
map[string]any{"$ref": "#/components/schemas/LogsQuery"},
},
},
})
g.reqGoNames["Variable"] = true

schema := map[string]any{
"type": "object",
"properties": map[string]any{
"variable_query": map[string]any{"$ref": "#/components/schemas/VariableQuery"},
},
}

src := g.emitStruct("Variable", schema)
if !strings.Contains(src, "VariableQuery VariableQuery `json:\"variable_query,omitzero\"") {
t.Fatalf("optional flattened-oneOf request field must use omitzero; got:\n%s", src)
}
}

// TestOpClassificationPredicates pins the three-way split of non-standard
// operations: ndjson streams and non-JSON request bodies are hand-written,
// while bounded binary downloads generate ordinary raw methods.
Expand Down
2 changes: 1 addition & 1 deletion models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading