Skip to content

Commit 23cb903

Browse files
committed
docs: drop per-Resp helpers from user docs, recommend for/switch
User-facing docs (v5preview/opensearchapi/README.md and guides/error_handling.md) no longer document the per-Resp helper methods (BulkItemFailures, SearchShardFailures, WriteShardFailures, MultiSearchItemFailures, PartialFailures(mask)). The Recommended pattern section presents two paths: - Treat any server or API failure as a hard error -- the idiomatic `if err != nil { return err }` for operations where any failure is reason to stop. - Inspect categories with a `for`/`switch` over opensearchapi.Errors(err) when partial error handling lets the application recover from known tolerated failure modes. The antipattern discussion now covers errors.As, `Has`-style helpers, and per-Resp helpers together: all three answer the narrow question "did this category happen?" and silently miss categories added in a future release. The type switch is the only category-aware pattern recommended. opensearchapi/partial_failure_methods.go file header reframes the helpers as engine machinery for the dispatch and points at guides/error_handling.md for the recommended call-site pattern. The methods stay available without deprecation markers. DEVELOPER_GUIDE.md and cmd/osgen/README.md describe the helpers as engine machinery the dispatch consumes and redirect call-site authors to opensearchapi.Errors(err) + for/switch. Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent 5a4d450 commit 23cb903

5 files changed

Lines changed: 81 additions & 41 deletions

File tree

DEVELOPER_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ The `x-error-responses` extension on a spec operation declares the categories of
399399

400400
- A typed Go error (e.g. `*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`) decoded from the response body when that category fires.
401401
- A bit on `errmask.ErrorMask` (PascalCase, e.g. `errmask.BulkItems`) plus the corresponding env-var token (`bulk_items`) so callers can suppress or surface it via `Config.Errors` or `OPENSEARCH_GO_ERROR_MASK`.
402-
- A per-Resp helper method on the operation's typed response (e.g. `BulkResp.BulkItemFailures()`, `SearchResp.SearchShardFailures()`).
402+
- A per-Resp helper method on the operation's typed response (e.g. `BulkResp.BulkItemFailures()`, `SearchResp.SearchShardFailures()`). These exist as engine machinery for the dispatch and are not the recommended call-site pattern; user docs point callers at a `for`/`switch` over `opensearchapi.Errors(err)` instead.
403403
- A `PartialFailures(mask)` aggregator on the same Resp.
404404

405405
Operations that declare two or more categories also get a per-op error container (e.g. `*MSearchErrors`) implementing `Unwrap() []error`, used when more than one category fires on a single response.

cmd/osgen/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ go run . api \
157157
3. Routes each operation to either the core `opensearchapi` package or a plugin package based on the operation group prefix.
158158
4. Renders Req structs (with path builder embedding, optional body, and header support), Params structs (with typed encode methods), and Resp stubs.
159159
5. Annotates generated code with availability (`x-version-added`), deprecation (`x-version-deprecated`, `x-deprecation-message`), and distribution exclusion metadata.
160-
6. Reads each operation's `x-error-responses` extension to emit typed partial-failure errors (`*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`, ...), the corresponding `errmask` bits and env-var tokens, per-Resp helper methods (`BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`), and -- for operations declaring two or more categories -- a per-op multi-error container implementing `Unwrap() []error`. See [`DEVELOPER_GUIDE.md` Partial-failure error generation](../../DEVELOPER_GUIDE.md#partial-failure-error-generation) for the full surface this produces, and [`v5preview/opensearchapi/README.md` Partial Failure Errors](../../v5preview/opensearchapi/README.md#partial-failure-errors) for the user-facing usage guide.
160+
6. Reads each operation's `x-error-responses` extension to emit typed partial-failure errors (`*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`, ...), the corresponding `errmask` bits and env-var tokens, per-Resp helper methods (`BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`) used internally by the dispatch, and -- for operations declaring two or more categories -- a per-op multi-error container implementing `Unwrap() []error`. The recommended call-site pattern in user code is a `for`/`switch` over `opensearchapi.Errors(err)`, not the per-Resp helpers; see [`DEVELOPER_GUIDE.md` Partial-failure error generation](../../DEVELOPER_GUIDE.md#partial-failure-error-generation) for the generated surface and [`v5preview/opensearchapi/README.md` Partial Failure Errors](../../v5preview/opensearchapi/README.md#partial-failure-errors) for the user-facing usage guide.
161161

162162
## Separate Module
163163

guides/error_handling.md

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -208,32 +208,43 @@ if err != nil {
208208
}
209209
```
210210

211-
### Per-Resp helper methods
211+
### Recommended pattern
212212

213-
Every operation that can return a partial failure exposes per-category helper methods on its typed response, plus a `PartialFailures(mask)` aggregator. Use these when you want focused inspection at the call site without going through the dispatch error. The helpers exist on both v4 `opensearchapi/` and v5preview `v5preview/opensearchapi/` Resp types and are nil-safe on a nil receiver.
213+
Two patterns cover every partial-failure use case. Pick the one that matches your operation's tolerance:
214+
215+
**Treat any server or API failure as a hard error** -- the simplest and most idiomatic Go path. Use this when the operation has no meaningful "partial success" -- any error is a reason to stop:
214216

215217
```go
216-
resp, _ := client.Bulk(ctx, req)
217-
if e := resp.BulkItemFailures(); e != nil {
218-
log.Printf("%d items failed", len(e.FailedItems))
218+
resp, err := client.Bulk(ctx, req)
219+
if err != nil {
220+
return err
219221
}
220-
221-
resp2, _ := client.MSearch(ctx, req)
222-
if e := resp2.SearchShardFailures(); e != nil { /* ... */ }
223-
if e := resp2.MultiSearchItemFailures(); e != nil { /* ... */ }
224-
225-
resp3, _ := client.Index(ctx, req)
226-
if e := resp3.WriteShardFailures(); e != nil { /* ... */ }
222+
// resp is fully populated; partial failures (if any) are folded into err.
227223
```
228224

229-
`r.PartialFailures(mask errmask.ErrorMask) []error` returns every wrapper category that fired and was not suppressed by `mask`. Useful for recreating the dispatch's mask-gated behavior at the call site -- pass `errmask.Empty` to see every category, or any narrower mask to suppress specific ones:
225+
**Inspect categories with a `for`/`switch`** -- when partial error handling is appropriate. Partial error handling lets the client and its application recover from known failure modes they can tolerate (e.g. continue serving a search with a few failed shards, or retry only the bulk items the server rejected) instead of failing the whole operation. The `default` arm catches transport / HTTP / decode errors and any partial-failure category added in a future release:
230226

231227
```go
232-
for _, sub := range resp.PartialFailures(errmask.Empty) {
233-
// same shape as opensearchapi.Errors(err)
228+
resp, err := client.MSearch(ctx, req)
229+
for _, sub := range opensearchapi.Errors(err) {
230+
switch e := sub.(type) {
231+
case *opensearchapi.PartialBulkError:
232+
log.Printf("%d items failed", len(e.FailedItems))
233+
case *opensearchapi.PartialSearchError:
234+
log.Printf("%d/%d shards failed", e.FailedShards, e.TotalShards)
235+
case *opensearchapi.MultiSearchItemError:
236+
log.Printf("%d sub-queries failed", len(e.Items))
237+
case *opensearchapi.ShardFailureError:
238+
log.Printf("%s: %d/%d shards failed", e.Operation, e.FailedShards, e.TotalShards)
239+
default:
240+
return err
241+
}
234242
}
243+
// resp is fully populated; use it regardless of partial failure.
235244
```
236245

246+
`opensearchapi.Errors(err)` flattens every error shape into a uniform slice -- single sub-error, multi-wrapper container, transport error, or `nil` (returns `nil`). The switch is the only pattern this guide recommends for category-aware handling: it stays correct when the API adds new categories, and a missing `case` is reviewable / lint-able.
247+
237248
### Inspecting Multi-Wrapper Errors with `opensearchapi.Errors`
238249

239250
Operations that can return more than one category of partial failure on the same response (today: `MSearch`, `MSearchTemplate`) sometimes do. The dispatch handler applies a runtime-collapse rule:
@@ -289,11 +300,13 @@ func handleMSearchError(err error) {
289300

290301
`opensearchapi.Errors(nil)` returns `nil`. A non-partial `err` (transport, HTTP, decode) returns a single-element slice containing `err`. Adding a new wrapper category later is purely additive: a new `case` in the switch picks it up; the `default` keeps catching everything else.
291302

292-
### Why a type switch, not `errors.As` or `Has`-style helpers
303+
### Why a type switch, not `errors.As`, `Has`, or per-Resp helpers
304+
305+
The set of partial-failure categories grows as the OpenSearch API evolves -- a future server or client release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible: static analysis and code review can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime. `errors.As(err, &target)` and `Has`-style helpers (e.g. `multierror.Contains`, `errors.Has`) only answer "did _this_ category happen?" -- they cannot tell a call site that a _new_ category appeared and is being silently dropped, because the categories of interest are arguments rather than cases.
293306

294-
The set of partial-failure categories grows as the OpenSearch API evolves -- a future server or client release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible: static analysis and code review can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime. `errors.As(err, &target)` and HashiCorp-style helpers (`multierror.Contains`, `errors.Has`) only answer "did _this_ category happen?" -- they cannot tell a call site that a _new_ category appeared and is being silently dropped, because the categories of interest are arguments rather than cases.
307+
The per-Resp helper methods (`resp.BulkItemFailures()`, `resp.SearchShardFailures()`, `resp.WriteShardFailures()`, `resp.MultiSearchItemFailures()`) and the per-Resp `PartialFailures(mask)` aggregator suffer the same forward-compatibility problem: a call site only sees the categories whose helpers it explicitly invokes. They exist on the response types as engine machinery for the dispatch and remain available for focused inspection of a known category. New code should use the `for`/`switch` pattern shown above.
295308

296-
Treat `As`/`Has` against the partial-failure error types as an antipattern: every call site that uses them becomes an audit liability the next time a category is added, because the omission is invisible to lint-time checks. The same reasoning applies to operations that today produce a single category -- preferring the type switch from day one means a future addition is purely additive rather than a silent behavior change.
309+
Treat `As`/`Has` and the per-Resp helpers against the partial-failure error types as an antipattern: every call site that uses them becomes an audit liability the next time a category is added, because the omission is invisible to lint-time checks. The same reasoning applies to operations that today produce a single category -- preferring the type switch from day one means a future addition is purely additive rather than a silent behavior change.
297310

298311
### Error Type Reference
299312

opensearchapi/partial_failure_methods.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,33 @@ import (
1010
"github.com/opensearch-project/opensearch-go/v4/errmask"
1111
)
1212

13-
// This file pairs every operation that declares x-error-responses with
14-
// per-Resp helper methods that detect each wrapper category. The
15-
// per-wrapper methods (e.g. SearchShardFailures) absorb every
13+
// This file pairs every operation that can return a partial failure
14+
// with per-Resp helper methods that detect each category. The
15+
// per-category methods (e.g. SearchShardFailures) absorb every
1616
// shape-specific concern: pointer guards on optional `_shards`
1717
// envelopes, per-sub-response iteration, union-branch dispatch (in
1818
// v5preview).
1919
//
2020
// Two surfaces are exposed for each op:
2121
//
22-
// - Per-wrapper public method, returning *<TypedError> or nil:
23-
// `func (r *<Op>Resp) <Wrapper>Failures() *<TypedError>`
22+
// - Per-category public method, returning *<TypedError> or nil:
23+
// `func (r *<Op>Resp) <Category>Failures() *<TypedError>`
2424
//
2525
// - Aggregator method, consulting the caller-supplied mask:
2626
// `func (r *<Op>Resp) PartialFailures(mask errmask.ErrorMask) []error`
2727
//
2828
// The dispatch handler in api_*.go calls the aggregator + collapses
29-
// via [collapsePerOpErrors]. Callers wanting focused inspection at the
30-
// call site invoke the per-wrapper methods directly without going
31-
// through the dispatch error.
29+
// via [collapsePerOpErrors] to produce the error returned to callers.
30+
//
31+
// The per-Resp helpers exposed by this file are not the recommended
32+
// way for callers to inspect partial failures. They answer the narrow
33+
// question "did this category happen?" -- a category added in a
34+
// future release is silently missed by call sites that only check the
35+
// existing helpers. The idiomatic pattern is a for/switch over
36+
// [Errors] applied to the dispatch error; see guides/error_handling.md
37+
// (Recommended pattern). The methods are kept here as engine
38+
// machinery for the dispatch and remain available for code that needs
39+
// focused inspection of a known category.
3240

3341
// ---------------------------------------------------------------------------
3442
// Bulk: BulkItems

v5preview/opensearchapi/README.md

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -264,25 +264,42 @@ for _, sub := range opensearchapi.Errors(err) {
264264

265265
All single-bit error types implement the `PartialFailureError` interface and work with `errors.As`. Per-op multi-error containers (`*MSearchErrors`, ...) implement `Unwrap() []error`, so `errors.As` against any sub-error type still matches whether the response carried one or many.
266266

267-
### Per-Resp helper methods
267+
### Recommended pattern
268268

269-
Every operation that can return a partial failure exposes per-category helper methods on its typed response, plus a `PartialFailures(mask)` aggregator. Use these when you want focused inspection at the call site without going through the dispatch error:
269+
Two patterns cover every partial-failure use case. Pick the one that matches your operation's tolerance:
270+
271+
**Treat any server or API failure as a hard error** -- the simplest and most idiomatic Go path. Use this when the operation has no meaningful "partial success" -- any error is a reason to stop:
270272

271273
```go
272-
resp, _ := client.Bulk(ctx, req)
273-
if e := resp.BulkItemFailures(); e != nil {
274-
log.Printf("%d items failed", len(e.FailedItems))
274+
resp, err := client.Bulk(ctx, req)
275+
if err != nil {
276+
return err
275277
}
278+
// resp is fully populated; partial failures (if any) are folded into err.
279+
```
276280

277-
resp2, _ := client.MSearch(ctx, req)
278-
if e := resp2.SearchShardFailures(); e != nil { /* ... */ }
279-
if e := resp2.MultiSearchItemFailures(); e != nil { /* ... */ }
281+
**Inspect categories with a `for`/`switch`** -- when partial error handling is appropriate. Partial error handling lets the client and its application recover from known failure modes they can tolerate (e.g. continue serving a search with a few failed shards, or retry only the bulk items that the server rejected) instead of failing the whole operation. The `default` arm catches transport / HTTP / decode errors and any partial-failure category added in a future release:
280282

281-
resp3, _ := client.Index(ctx, req)
282-
if e := resp3.WriteShardFailures(); e != nil { /* ... */ }
283+
```go
284+
resp, err := client.MSearch(ctx, req)
285+
for _, sub := range opensearchapi.Errors(err) {
286+
switch e := sub.(type) {
287+
case *opensearchapi.PartialBulkError:
288+
log.Printf("%d items failed", len(e.FailedItems))
289+
case *opensearchapi.PartialSearchError:
290+
log.Printf("%d/%d shards failed", e.FailedShards, e.TotalShards)
291+
case *opensearchapi.MultiSearchItemError:
292+
log.Printf("%d sub-queries failed", len(e.Items))
293+
case *opensearchapi.ShardFailureError:
294+
log.Printf("%s: %d/%d shards failed", e.Operation, e.FailedShards, e.TotalShards)
295+
default:
296+
return err
297+
}
298+
}
299+
// resp is fully populated; use it regardless of partial failure.
283300
```
284301

285-
The helpers are nil-safe on a nil receiver and return `nil` when the category did not fire. `r.PartialFailures(mask errmask.ErrorMask) []error` reports every category not suppressed by `mask` -- useful for reusing the dispatch's mask gating outside the dispatch path.
302+
`opensearchapi.Errors(err)` flattens every error shape into a uniform slice -- single sub-error, multi-wrapper container, transport error, or `nil` (returns `nil`). The switch is the only pattern this guide recommends for category-aware handling: it stays correct when the API adds new categories, and a missing `case` is reviewable / lint-able.
286303

287304
### Helper functions
288305

@@ -306,9 +323,11 @@ opensearchapi.OperationUpdate // "update"
306323
opensearchapi.OperationDelete // "delete"
307324
```
308325

309-
### Why a type switch, not `errors.As` or `Has`-style helpers
326+
### Why a type switch, not `errors.As`, `Has`, or per-Resp helpers
327+
328+
The set of partial-failure categories grows as the OpenSearch API evolves: a future server release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible -- review and static analysis can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime.
310329

311-
The set of partial-failure categories grows as the OpenSearch API evolves: a future server release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible: review and static analysis can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime. `errors.As` and HashiCorp-style `Has` helpers only answer "did _this_ category happen?" -- they cannot tell a call site that a _new_ category appeared. Treat `As` / `Has` against the partial-failure error types as an antipattern.
330+
`errors.As` and `Has`-style helpers and per-Resp helper methods (`resp.BulkItemFailures()`, `resp.SearchShardFailures()`, ...) all answer the same narrow question: "did _this_ category happen?" None of them can tell a call site that a _new_ category appeared and is being silently dropped. Treat them as an antipattern. The per-Resp helpers exist on the response types as engine machinery for the dispatch and remain available for focused inspection of a known category, but new code should use the type switch.
312331

313332
For the full best-practices guide (retry strategies, threshold tuning, manual partial-failure inspection), see [`../../guides/error_handling.md`](../../guides/error_handling.md).
314333

0 commit comments

Comments
 (0)