Skip to content

Commit 509f067

Browse files
committed
docs: cover the union consolidation and discriminator decoding
Add a Changed entry for discriminator-driven decoding and the strict request-selected accessors, and a Removed entry for the 135 consolidated union types. Give the v4 -> v5 deep dive three sections: the rename table, the six unions that now name their own branch, and the aggregation unions that cannot, with the typed_keys guidance those need. Retarget the tasks guide at TasksStatus and BulkByScrollTaskStatusOrException, and thread the error return through its examples. Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent dba2252 commit 509f067

3 files changed

Lines changed: 101 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
179179

180180
### Changed
181181

182+
- `cmd/osgen` now honors the OpenAPI `discriminator`, which it previously ignored entirely. Eight schemas in the spec declare one and every branch of all eight resolves to a distinct value, so 146 subtypes whose branch the payload names outright were being decoded by a JSON token-class heuristic instead. The discriminator is how OpenAPI expresses polymorphism, and the spec encodes the hierarchy through `allOf` (a subtype is `allOf[Base, {type: enum[keyword]}]`), so resolving a discriminant walks `allOf` transitively: the constant sits on the narrowing member while the inherited fields come from the base. A union is only discriminated when every branch resolves distinctly, so a partially-understood schema falls back rather than emitting a decoder with an unreachable case. Six emitted unions gain a `Type()`, discriminant constants, and an `UnmarshalJSON` that reads one property and decodes exactly that branch: `CommonMappingProperty` (on `type`, honoring the spec's `x-default` for the implicit object mapping), `CommonAnalysis{Analyzer,CharFilterDefinition,TokenFilterDefinition,TokenizerDefinition,Normalizer}`, plus `ClusterRemoteInfoCluster` on `mode`. A value naming no branch is an error rather than a silent mis-decode, and `ClusterRemoteInfoResp.Entries` becomes `map[string]ClusterRemoteInfoCluster` instead of an untyped map, dropping it from the `json.RawMessage` allowlist. Internal vocabulary follows the spec rather than the decode mechanism: `IsLazy`/`LazyAccessors` conflated "the spec provides no discriminator" with "we never read the discriminator" and become `RequestSelected`, since the aggregation and suggester unions carry `x-supports-typed-keys` and genuinely cannot be discriminated (the branch is chosen by the request and echoed only in the response map key, `avg#my_agg`); `TypeLazyUnion` becomes `TypeAmbiguousWire`, `unionNeedsTryEach` becomes `branchesCollideOnTokenClass`, and the token-class heuristic is retained and documented as the fallback it is
183+
- Union branch accessors on a request-selected union no longer return a zero value with a nil error when the payload cannot be that branch. `AsSum()` against histogram bytes returned an empty `Sum` and no error, indistinguishable from a genuine zero, because `encoding/json` ignores unknown keys and a decode-error check cannot see the mismatch; a required-property probe now rejects it. It still cannot distinguish `avg` from `sum`, which share one wire shape -- that is a property of the protocol, and is documented on the generated type. `UnionBranchError` gains an `Err` field and `Unwrap()`, so the underlying decode failure is reachable through `errors.As`, and `Got` may be `"incompatible payload"`
182184
- Generated doc comments now lead with the identifier they document, as godoc expects. Field and enum-member comments previously carried the spec description verbatim, so godoc rendered "The time taken by different phases of the search." against `PhaseTook` rather than a sentence naming the field. Prefixing cannot be uniform, because the descriptions are not one grammatical shape: 2,545 are noun phrases naming the value, where a copula reads correctly ("PhaseTook is the time taken by ..."), while 297 open with a verb or a conditional, where the same treatment yields "Found is whether the document was found". A copula is inserted only after a leading article; otherwise the description becomes its own sentence after the name. Enum members take the sentence form always, since their descriptions say what selecting the value means rather than naming an attribute of the constant, making the copula wrong for all 43 of them ("NodeRoleDataHot is the node can store hot data"). A promoted sentence is capitalized, since 161 spec descriptions are lowercase fragments. Descriptions already leading with the identifier keep their text, and a `Deprecated` marker is never prefixed, since godoc only honors it at the start of a paragraph
183185
- `cmd/osgen` bounds its generic-substitution traversals by detecting `$ref` cycles rather than capping descent depth. The previous seven-level cap was not a termination guarantee: `declaresProperty` follows `allOf` `$ref` members with no stopping rule and overflows the stack on a mutually recursive pair, which the cap merely hid. Each traversal now carries the set of `$ref` keys on the current path, since only a `$ref` can revisit a schema; a repeat resolves as concrete and keeps the field, matching how an unresolvable `$ref` is already treated. Sibling probes each get their own set, so a schema reached through one `allOf` member cannot suppress the answer for another. Generated output is byte-identical, and the diagnostic the cap emitted on exceeding the bound is removed with it
184186
- Configure Dependabot to scan the nested Go modules (`osprom`, `osotel`, `cmd/osgen`, `cmd/osapilint`) alongside the root module, and pin the weekly schedule to Tuesday 14:00 `America/Los_Angeles` rather than leaving the day to Dependabot and the time to UTC
@@ -242,6 +244,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
242244

243245
### Removed
244246

247+
- 135 generated union types are removed and 45 added, as a `oneOf`/`anyOf` schema reached through a `$ref` is now named after the schema itself rather than the field that referenced it, and emitted once instead of once per reference. This collapses 212 union types to 122. The removals are consolidations rather than lost functionality: every branch and accessor survives on the shared type, and more distinct union shapes survive than before (91, up from 87). Renames include `MGetRespBodyDocsItem` to `MGetRespItem`, `MSearchMultiSearchResultResponsesItem` to `MSearchRespItem`, `SortResultsItem` and `CommonAggregationsCompositeAggregateKeyValue` to `FieldValue`, `ErrorCauseHeaderValue` to `StringOrStringArray`, `SearchResultAggregationsValue` to `CommonAggregationsAggregate`, `IndicesIndexSettingsAnalysis{Analyzer,Normalizer}Value` to `CommonAnalysis{Analyzer,Normalizer}`, and `CatRecoveryRecord{Start,Stop}TimeMillis` with `Replication{,Index}FollowerStatusTotalWriteTimeMillis` to `StringifiedEpochTimeUnitMillis`. Branch accessors also drop the group prefix the union name already carries, so `MGetMultiGetError()` becomes `MultiGetError()` and `SearchHitsMetadataTotal.SearchTotalHits()` becomes `.TotalHits()`. See [`opensearchapi/UPGRADING_V4_TO_V5.md`](opensearchapi/UPGRADING_V4_TO_V5.md) for the rename table
245248
- **BREAKING**: Remove the `IncludeDedicatedClusterManagers` config flag from `opensearch.Config` and `opensearchtransport.Config`. Dedicated cluster managers (`cluster_manager` role with no work roles) are now unconditionally excluded from request routing: they are kept in the connection inventory for discovery reuse/eviction but never selected for query traffic (a user-supplied seed remains selectable so discovery can bootstrap against it). The flag previously defaulted to excluding them; opting them into routing is no longer supported. Delete any `IncludeDedicatedClusterManagers` field from your config (it is a compile error otherwise) ([#1004](https://github.com/opensearch-project/opensearch-go/pull/1004))
246249
- Remove deprecated `(*opensearch.Client).Perform` and `(*opensearchtransport.Transport).Perform`; `Stream(*http.Request) (*http.Response, error)` is now the sole method on `opensearchtransport.Interface`. Custom transport implementations must implement `Stream` instead of `Perform`. The `opensearch.Streamer` opt-in interface and `opensearch.ErrTransportMissingMethodStream` sentinel are removed. ([#872](https://github.com/opensearch-project/opensearch-go/issues/872))
247250
- **BREAKING**: Remove the `EnableMetrics` config flag from `opensearch.Config` and `opensearchtransport.Config`. The detailed-metrics snapshot (per-connection enumeration, per-policy breakdowns, and router cache state) is now always available; `Metrics()` returns the full snapshot unconditionally. The flag's only remaining purpose after [#891](https://github.com/opensearch-project/opensearch-go/issues/891) was to gate the detailed path, which now does its work lazily and lock-free at call time and so costs nothing until `Metrics()` is called. Delete any `EnableMetrics` field from your config (it is a compile error otherwise); see [`UPGRADING_V5.md`](UPGRADING_V5.md#enablemetrics-removed). ([#892](https://github.com/opensearch-project/opensearch-go/issues/892))

guides/usage-tasks.md

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,17 @@ Use `Tasks.Get` to poll a task by ID. The `Completed` field indicates whether th
120120
121121
## Inspecting Task Status
122122
123-
The `Status` field on a task is a discriminated union (`*opensearchapi.TasksTaskInfoBaseStatus`) because its shape depends on the task type. Call `Type()` to determine which branch was decoded, then call the matching accessor. The raw JSON is always available via `RawJSON()`.
123+
The `Status` field on a task is a union (`*opensearchapi.TasksStatus`) because its shape depends on the task type. Call `Type()` to determine which branch was decoded, then call the matching accessor. The raw JSON is always available via `RawJSON()`.
124124
125125
### BulkByScroll Tasks (reindex, delete_by_query, update_by_query)
126126
127127
For reindex, delete_by_query, and update_by_query tasks, call `BulkByScrollTaskStatus()` to get the typed struct. Note that `Created` and `Updated` are pointers and should be nil-checked:
128128
129129
```go
130-
status := taskResp.Task.Status.BulkByScrollTaskStatus()
130+
status, err := taskResp.Task.Status.BulkByScrollTaskStatus()
131+
if err != nil {
132+
return err
133+
}
131134

132135
fmt.Printf("Total: %d\n", status.Total)
133136
if status.Created != nil {
@@ -144,16 +147,22 @@ For reindex, delete_by_query, and update_by_query tasks, call `BulkByScrollTaskS
144147
fmt.Printf("Retries (search): %d\n", status.Retries.Search)
145148
```
146149
147-
For sliced requests, the `Slices` field contains per-slice status. Each element is a `BulkByScrollTaskStatusSlicesItem` -- a discriminated union that is either a nested `BulkByScrollTaskStatus` (on success) or an `ErrorCause` (on failure). Call `Type()` then the matching accessor:
150+
For sliced requests, the `Slices` field contains per-slice status. Each element is a `BulkByScrollTaskStatusOrException` -- a union that is either a nested `BulkByScrollTaskStatus` (on success) or an `ErrorCause` (on failure). Call `Type()` then the matching accessor. Each accessor returns `(T, error)` and reports a `*opensearchapi.UnionBranchError` when the union holds a different branch, so the zero value is never mistaken for real data:
148151
149152
```go
150153
for i, slice := range status.Slices {
151154
switch slice.Type() {
152-
case opensearchapi.BulkByScrollTaskStatusSlicesItemBulkByScrollTaskStatusType:
153-
sliceStatus := slice.BulkByScrollTaskStatus()
155+
case opensearchapi.BulkByScrollTaskStatusOrExceptionBulkByScrollTaskStatusType:
156+
sliceStatus, err := slice.BulkByScrollTaskStatus()
157+
if err != nil {
158+
return err
159+
}
154160
fmt.Printf("Slice %d: %d total\n", i, sliceStatus.Total)
155-
case opensearchapi.BulkByScrollTaskStatusSlicesItemExceptionType:
156-
exc := slice.Exception()
161+
case opensearchapi.BulkByScrollTaskStatusOrExceptionExceptionType:
162+
exc, err := slice.Exception()
163+
if err != nil {
164+
return err
165+
}
157166
reason := ""
158167
if exc.Reason != nil {
159168
reason = *exc.Reason
@@ -165,19 +174,25 @@ For sliced requests, the `Slices` field contains per-slice status. Each element
165174
166175
### Replication Tasks
167176
168-
For replication tasks (e.g. index, delete, bulk shard operations), call `TasksReplicationTaskStatus()`:
177+
For replication tasks (e.g. index, delete, bulk shard operations), call `ReplicationTaskStatus()`:
169178
170179
```go
171-
replStatus := taskResp.Task.Status.TasksReplicationTaskStatus()
180+
replStatus, err := taskResp.Task.Status.ReplicationTaskStatus()
181+
if err != nil {
182+
return err
183+
}
172184
fmt.Printf("Phase: %s\n", replStatus.Phase)
173185
```
174186
175187
### Persistent Tasks
176188
177-
For persistent task executors, call `TasksPersistentTaskStatus()`:
189+
For persistent task executors, call `PersistentTaskStatus()`:
178190
179191
```go
180-
persistStatus := taskResp.Task.Status.TasksPersistentTaskStatus()
192+
persistStatus, err := taskResp.Task.Status.PersistentTaskStatus()
193+
if err != nil {
194+
return err
195+
}
181196
fmt.Printf("State: %s\n", persistStatus.State)
182197
```
183198
@@ -244,13 +259,13 @@ Long-running tasks can be cancelled by task ID. The response body has a dynamic
244259
245260
## Status Type Reference
246261
247-
The OpenSearch server returns different status structures depending on the task type. The `Status` field is a discriminated union (`*opensearchapi.TasksTaskInfoBaseStatus`); call `Type()` to determine the branch, then the matching accessor.
262+
The OpenSearch server returns different status structures depending on the task type. The `Status` field is a union (`*opensearchapi.TasksStatus`); call `Type()` to determine the branch, then the matching accessor. Every accessor returns `(T, error)`.
248263
249-
| Task Type | Accessor | Status Struct | Key Fields |
250-
| ----------------------------------------- | ------------------------------ | ---------------------------- | ------------------------------------------------------------------------- |
251-
| reindex, delete_by_query, update_by_query | `BulkByScrollTaskStatus()` | `BulkByScrollTaskStatus` | total, created, updated, deleted, batches, retries, throttle info, slices |
252-
| replication (index, delete, bulk shard) | `TasksReplicationTaskStatus()` | `TasksReplicationTaskStatus` | phase |
253-
| persistent task executor | `TasksPersistentTaskStatus()` | `TasksPersistentTaskStatus` | state |
264+
| Task Type | Accessor | Status Struct | Key Fields |
265+
| ----------------------------------------- | -------------------------- | ---------------------------- | ------------------------------------------------------------------------- |
266+
| reindex, delete_by_query, update_by_query | `BulkByScrollTaskStatus()` | `BulkByScrollTaskStatus` | total, created, updated, deleted, batches, retries, throttle info, slices |
267+
| replication (index, delete, bulk shard) | `ReplicationTaskStatus()` | `TasksReplicationTaskStatus` | phase |
268+
| persistent task executor | `PersistentTaskStatus()` | `TasksPersistentTaskStatus` | state |
254269
255270
For any unrecognized task type, the raw JSON is available via `Status.RawJSON()` for direct unmarshaling, or `Status.Map()` for a `map[string]json.RawMessage`.
256271
@@ -259,7 +274,7 @@ For any unrecognized task type, the raw JSON is available via `Status.RawJSON()`
259274
If you read the same status type frequently, you can define a short helper in your own code:
260275
261276
```go
262-
func bulkByScrollStatus(status *opensearchapi.TasksTaskInfoBaseStatus) opensearchapi.BulkByScrollTaskStatus {
277+
func bulkByScrollStatus(status *opensearchapi.TasksStatus) (opensearchapi.BulkByScrollTaskStatus, error) {
263278
return status.BulkByScrollTaskStatus()
264279
}
265280
```

0 commit comments

Comments
 (0)