Skip to content

Add x-error-responses extension for partial-failure response shapes - #1137

Open
sean- wants to merge 1 commit into
opensearch-project:mainfrom
sean-:worktree-partial-failure-mode
Open

Add x-error-responses extension for partial-failure response shapes#1137
sean- wants to merge 1 commit into
opensearch-project:mainfrom
sean-:worktree-partial-failure-mode

Conversation

@sean-

@sean- sean- commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Introduce a per-operation x-error-responses vendor extension whose value is an array of $ref objects pointing at wrapper schemas in spec/schemas/_common.errors.yaml. Each wrapper describes one auxiliary failure shape an endpoint may carry alongside its primary 2xx response — including arity (array vs object vs map) and any parallel-array grouping (e.g. task_failures[] + node_failures[] together for the Tasks API) — using ordinary JSON Schema. Client generators walk an operation's x-error-responses array, look up each wrapper, and emit one typed handler per wrapper that detects and extracts the embedded failure content.

The new _common.errors.yaml defines 15 wrappers covering the distinct partial-failure encodings present in OpenSearch today: BulkItems, SearchShards, WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures, TaskFailures, MultiSearchItems, MultiDocItems, SnapshotCreateShardFailures, SnapshotGetShardFailures, SimulateDocFailures, RankEvalFailures, IngestionShardFailures, and PitNodeFailures. Each wrapper composes existing element schemas (ShardSearchFailure, ShardFailure, NodeStatistics, TaskFailure, SnapshotShardFailure, IngestionStateShardFailure, BulkByScrollFailure, etc.) — no new element types are added.

Annotate 115 operation variants across 10 namespace files (_core, asynchronous_search, cluster, dangling_indices, indices, ingest, ingestion, nodes, snapshot, tasks). indices.recovery and indices.get_upgrade are intentionally not annotated: their response classes extend BroadcastResponse but suppress the _shards block in their toXContent overrides.

Document the extension in DEVELOPER_GUIDE.md alongside the existing vendor extensions list.

Motivated by opensearch-go's port of partial-failure error handling into its generated v5preview/opensearchapi/ package, which needs a generator-driven mapping from operation to auxiliary failure types.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@sean-
sean- force-pushed the worktree-partial-failure-mode branch from cd63338 to 38354e8 Compare May 29, 2026 02:16
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 29, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request May 30, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 1, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 1, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 2, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 2, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 2, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 2, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 3, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 3, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
sean- added a commit to sean-/opensearch-go that referenced this pull request Jun 3, 2026
OpenSearch returns HTTP 200 for partial successes -- bulk item failures,
search-shard failures, single-doc replica failures -- so callers must
remember a second check after `err == nil`. v4 added an opt-in boolean
(Config.ReturnQueryErrors) that converted ALL partial-failure shapes
into typed Go errors. That single switch is too coarse: callers who
want shard-level errors but tolerate bulk item failures (or vice versa)
have no way to express it.

Replace the boolean with internal/errmask.ErrorMask, a 15-bit field
where each bit corresponds to one wrapper schema in the proposed
x-error-responses OpenAPI extension (BulkItems, SearchShards,
WriteShards, BroadcastShards, NodeFailures, BulkByScrollFailures,
TaskFailures, MultiSearchItems, MultiDocItems, Snapshot{Create,Get}-
ShardFailures, SimulateDocFailures, RankEvalFailures,
IngestionShardFailures, PitNodeFailures). A set bit MASKS that
category; the zero value reports every category. Callers express
fine-grained policy in code (Config.Errors = errmask.BulkItems |
errmask.SearchShards) or via OPENSEARCH_GO_ERROR_MASK using
comma-separated +/- tokens (e.g. "+all,-bulk_items").

Lifecycle (matches OPENSEARCH_GO_ROUTER):
  v4 (this commit): default `errmask.All` -- preserves pre-bitfield
    behavior (no partial-failure errors). Config.ReturnQueryErrors=true
    is honored as a deprecated alias for `errmask.None`.
  v5: default flips to `errmask.None` (safe by default).
  v6: Config.Errors / OPENSEARCH_GO_ERROR_MASK removed; behavior is
    unconditionally `errmask.None`.

The hand-written v4 opensearchapi/api_*.go call sites now read
c.errors.Has(errmask.<Wrapper>) for each operation's wrapper category.
A new hand-written v5preview/opensearchapi/errors.go ports the same
typed-error surface (PartialBulkError, PartialSearchError,
ShardFailureError, plus the IsPartialFailure / ToleratePartial-
Failures / RequireSuccessRate helpers) using v5preview's BulkResponse-
Item and ShardSearchFailure types. v5preview Config.Errors and the
clientInit(rootClient, mask) signature are wired through both
hand-written api.go and the generated clients_gen.go.

Spec side: opensearch-openapi.yaml is patched with 15
`_common.errors___<Wrapper>` schemas under components.schemas and 115
operation entries get an x-error-responses annotation. This mirrors
the upstream proposal in opensearch-api-specification (see
issue-x-partial-failure-mode.md). Once that PR lands and we re-bundle
from source, the local patch goes away cleanly.

Generator side: cmd/osgen reads x-error-responses from the spec
extension into ir.Operation.ErrorWrappers; cmd/osgen/errwrap supplies
a hardcoded fallback for plugin operations the spec doesn't yet
annotate. The dispatch fragment carries a data-driven `wrappers` map
of {Template, Applies}: each wrapper has both an emission template and
an Applies predicate that walks the response struct (including
embeds via the type registry) to confirm the field path the template
references actually exists. This keeps generated code compilable when
spec annotations land before the underlying response schema models the
relevant field -- v5preview's CreateResp and msearch's union response
item are skipped today and will start emitting once those types
acquire the missing fields.

Ref: opensearch-project#816
Ref: opensearch-project/opensearch-api-specification/pull/1137

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
@sean-
sean- force-pushed the worktree-partial-failure-mode branch from 38354e8 to 0c6dbf0 Compare June 4, 2026 00:43
sean- added a commit to opensearch-project/opensearch-go that referenced this pull request Jun 4, 2026
…n classifier, default router, and union decode overhaul

Consolidates the gh-816 burn-down: a partial-failure error model with
fine-grained masking, an HTTP-layer operation classifier, blocking
node discovery with default-router injection, a generator overhaul
(idiomatic naming, single-pass union decode, request-body union
constructors), and the v5preview regeneration plus integration
coverage and user docs that go with it.

## Partial-failure error model

OpenSearch returns HTTP 200 for partial successes (bulk item failures,
shard failures, single-doc replica failures), forcing callers to
double-check responses after `err == nil`. New typed errors expose
these through the standard Go `if err != nil` idiom; both `(resp, err)`
are non-nil on partial failure and the response is fully populated.

- Adds `PartialBulkError`, `PartialSearchError`, `ShardFailureError`,
  and helpers `IsPartialFailure`, `ToleratePartialFailures`,
  `RequireSuccessRate` for threshold-based tolerance.
- Replaces the initial `Config.ReturnQueryErrors` boolean with
  `internal/errmask.ErrorMask`, a 15-bit field where each bit masks
  one wrapper schema in the proposed `x-error-responses` OpenAPI
  extension (`BulkItems`, `SearchShards`, `WriteShards`,
  `BroadcastShards`, `NodeFailures`, `BulkByScrollFailures`,
  `TaskFailures`, `MultiSearchItems`, `MultiDocItems`,
  `Snapshot{Create,Get}ShardFailures`, `SimulateDocFailures`,
  `RankEvalFailures`, `IngestionShardFailures`, `PitNodeFailures`).
  A set bit suppresses that category; the zero value reports every
  category.
- Callers configure policy via `Config.Errors = errmask.BulkItems |
  errmask.SearchShards` or `OPENSEARCH_GO_ERROR_MASK` with `+/-`
  tokens (e.g. `+all,-bulk_items`).

Lifecycle:
- v4 default `errmask.All` preserves existing behavior.
  `Config.ReturnQueryErrors=true` is honored as a deprecated alias
  for `errmask.None`.
- v5 default flips to `errmask.None` (safe by default).
- v6 removes `Config.Errors` / `OPENSEARCH_GO_ERROR_MASK`;
  behavior is unconditionally `errmask.None`.

Spec side: `opensearch-openapi.yaml` is patched with 15
`_common.errors___<Wrapper>` schemas and 115 operations gain an
`x-error-responses` annotation, mirroring the upstream proposal in
`opensearch-api-specification`. The local patch goes away cleanly
once that PR lands and we re-bundle from source.

Generator side: `cmd/osgen` reads `x-error-responses` into
`ir.Operation.ErrorWrappers`; `cmd/osgen/errwrap` supplies a fallback
for plugin operations the spec does not yet annotate. The dispatch
fragment carries a data-driven `wrappers` map of `{Template, Applies}`,
so generated code stays compilable when annotations land before the
underlying response schema models the relevant field.

## OperationClassifier and bit-packed OperationID

Adds a zero-allocation HTTP method+path classifier (reusing the
existing `routeTrie`) that maps requests to structured
`OperationID` values. Enables transparent metrics, tracing, and
access-control middleware at the `http.RoundTripper` layer without
per-operation wrappers.

- `OperationID` is a bit-packed `int64` encoding R/W flag, category,
  and minor operation. `IsWrite`, `Category`, `Minor` support
  efficient bitwise filtering. `String()` returns Prometheus-friendly
  labels.
- `OperationClassifier` is built from the canonical route table and
  is safe for concurrent use. Returns `OpOther` for unrecognized
  patterns.
- `OperationID` field added to `trieLeaf`/`trieMatch`, `OpID()` to
  `Route`, `.Op()` to `RouteBuilder`. All 124 routes are tagged.

## Blocking DiscoverNodes and default Router injection

- `DiscoverNodes` now waits for an in-flight discovery to complete
  (or for the context to be cancelled) instead of returning `nil`
  immediately, letting callers block until topology data is
  available after client construction.
- `DiscoverNodesOnStart` becomes `*bool`. Auto-enabled when
  `OPENSEARCH_GO_ROUTER=true` and the caller did not set it.
- `v5preview/opensearchapi.NewClient` and `NewDefaultClient` inject
  `opensearchtransport.NewDefaultRouter` when `config.Client.Router`
  is `nil`, opting v5preview clients into role-aware dispatch,
  RTT-based scoring, AIMD congestion-window, and shard-cost
  weighting by default.
- `OPENSEARCH_GO_ROUTER` is a symmetric override: a falsy value
  (`false`/`0`) suppresses injection so `Router` stays `nil`,
  matching v4 behavior. Caller-provided Routers are preserved.
- `internal/envvars.Falsy(name)` distinguishes "unset" from
  "explicitly opted out" so the v5preview rule reads as
  `!envvars.Falsy(envvars.Router)` without re-implementing parse
  logic.

## Generator: idiomatic naming, request-body unions, single-pass decode

Naming rewrites at PascalCase boundaries:

| Spec form     | Idiomatic Go    |
|---------------|-----------------|
| `Msearch`     | `MSearch`       |
| `Mget`        | `MGet`          |
| `Mtermvectors`| `MTermVectors`  |
| `Termvectors` | `TermVectors`   |
| `Forcemerge`  | `ForceMerge`    |
| `Response`    | `Resp`          |

The `Response -> Resp` rule additionally requires the trailing
character to be uppercase, so standalone `SearchResponse` (a spec
wrapper) does not collide with the operation-level `<Op>Resp` name.
Hand-written types renamed for v4/v5 consistency:
`BulkResponseItem -> BulkRespItem`, `ErrorResponseBase ->
ErrorRespBase`, `MsearchErrors -> MSearchErrors`,
`MsearchTemplateErrors -> MSearchTemplateErrors`.

Request-body unions:
- `splitUnionsFromSiblings` partitions request-body subtree unions
  (e.g. `ReindexSourceSort`) so they are routed to `UnionFragment`
  instead of being emitted as empty structs.
- Plumbs `Op + Registry` into `UnionFragment` so plugin-package
  unions qualify cross-package branches as
  `opensearchapi.FieldSort`.
- Each generated discriminated union gains
  `New<Union>From<Branch>(v <branch type>) <Union>` constructors
  per branch and a `SetRaw(json.RawMessage)` typed escape hatch.
  `SetRaw` clears the typed branch so `MarshalJSON` returns the
  raw bytes verbatim.

Single-pass union decode:
- Case A (merged): object unions with one permissive primary branch
  plus discriminated branch(es) (mget, msearch, indices-open). The
  primary is embedded and the common case decodes in a single
  `json.Unmarshal`; each discriminated branch is detected by the
  presence of its distinguishing key and decoded only when matched.
  Drops the `build.HasJSONKeys` map probe and per-item raw copy.
- Case B (lazy `As<T>()`): aggregation/suggest result unions carry
  no wire discriminator, so they cannot be auto-selected.
  `UnmarshalJSON` only retains the raw bytes; generated
  `As<ConcreteType>()` accessors decode on demand.
- Unions fitting neither (reindex bodies, plugin-defined task
  status) keep the existing try-each decoder; the classifier logs
  once per union name when it declines.
- All union `UnmarshalJSON` aliases the owned response buffer
  (`u.raw = data`) rather than copying it; `RawJSON()` documents
  the borrowed-buffer contract.

Measured impact on mget (1000 docs): decode allocations down ~2.7x
(~43k -> ~16k allocs/op), time down ~1.7x (4.2ms -> 2.5ms).

Bulk wrapper template now resolves its walked element type from the
IR (via `bulkInnerItemType`) instead of hardcoding `BulkRespItem`,
so future spec or naming changes propagate automatically.

## v5preview regeneration

Mechanical output of `cmd/osgen` against the patched spec:

- `clients_gen.go` declares `errors errmask.ErrorMask` on `Client`
  and `clientInit` takes the mask as a second arg.
- 19 operation dispatch files emit per-wrapper post-`do()` blocks
  that return typed errors when the corresponding `errmask` bit is
  unset and the wire data carries a partial failure
  (`BulkItems`, `SearchShards`, `WriteShards`, `MultiSearchItems`).
- Reserved-but-unemitted wrappers (`BroadcastShards`, `NodeFailures`,
  `BulkByScrollFailures`, `TaskFailures`, `MultiDocItems`,
  `Snapshot{Create,Get}ShardFailures`, `SimulateDocFailures`,
  `RankEvalFailures`, `IngestionShardFailures`, `PitNodeFailures`)
  carry annotations but no emission until detection logic lands.
- Operations whose typed response shape lacks the field path a
  wrapper needs (`CreateResp` lacks `_shards`; msearch union items
  lack a top-level `Shards`) are silently skipped by the
  `Applies` guard; emission starts automatically once the response
  types catch up.
- Idiomatic-naming pass touches every generated type containing
  `Msearch`, `Mget`, `Mtermvectors`, `Termvectors`, `Forcemerge`,
  or compound `*Response*` substrings.
- Single-pass union decode applied to mget/msearch/indices-open
  success|error items and `As<T>()` accessors generated for
  aggregation and suggest result unions.

## Integration coverage and CI hardening

v5preview integration tests drive real requests and assert decoded
shape per server version:

- aggregation: lazy `As<T>()` accessors (terms, date_histogram,
  stats, avg, sum, min, max, value_count, cardinality).
- mget: merged success|error decode (`GetResult` found/not-found
  vs `MGetMultiGetError`).
- msearch: merged success|error decode
  (`MSearchMultiSearchItem` vs `ErrorRespBase`), the
  first-byte-switch `SearchHitsMetadataTotal` union, and the
  `MultiSearchItemError` partial-failure surface.

CI matrix changes:

- Skip multi-node clusters on OpenSearch <=2.17.x to avoid the
  node-join/node-left coordinator race
  (`opensearch-project/OpenSearch#15521`, backported via #16118
  on the 2.x line). Affected versions run with
  `OPENSEARCH_NODE_COUNT=1`.
- `.github/workflows/test-compatibility.yml` adds a per-entry
  `node_count` matrix field: `1` for 1.3.20 through 2.17.1, `3`
  for 2.18.0 and later. Comment cites the upstream PRs so the
  cutoff is greppable.
- `Makefile cluster.docker-up` respects `OPENSEARCH_NODE_COUNT`
  and passes `--scale opensearch-nodeN=0` to docker compose.
  Defaults to 3 for local development.
- Widen the existing 2.1.0-only shard-routing test skip to all
  versions below 2.2.0 (security plugin
  `java.io.OptionalDataException` from non-thread-safe `User`
  serialization, fixed by
  `opensearch-project/security#1970`).
- Generated-code emit/path test fixtures converted from raw
  `"GET"`/`"POST"` to `net/http http.Method*` constants for
  consistency with the rest of the suite.
- `testutil.PollUpdate()` decouples jitter calculation from
  runtime backoff execution to fix a flake.

## Dependency bumps

- `github.com/aws/aws-sdk-go-v2/config` 1.32.18 -> 1.32.20
- `github.com/aws/aws-sdk-go-v2/credentials` 1.19.17 -> 1.19.19
- `github.com/getkin/kin-openapi` v0.139.0 -> v0.140.0

Fixes: #852, #850

## Documentation

- `v5preview/opensearchapi/README.md`: Partial Failure Errors
  (Config.Errors, errmask, `OPENSEARCH_GO_ERROR_MASK`, typed errors,
  `opensearchapi.Errors` helper, per-Resp helpers, helper functions,
  operation constants) and Default Router Injection (truth table
  for `OPENSEARCH_GO_ROUTER`, opt-out semantics).
- `v5preview/opensearchapi/MIGRATING.md`: v4 -> v5preview surface
  delta (import path, `Indices -> Index` on multi-index Req types,
  `DocumentID -> ID` on `IndexReq`, optional `Params` becomes
  `*Params`, optional `bool` query params become `*bool`,
  partial-failure type renames, errmask default flip, default
  Router injection).
- `guides/error_handling.md`: Bulk/Search/Write partial-failure
  examples as paired v4/v5preview blocks; per-Resp helper
  subsection (`BulkItemFailures`, `SearchShardFailures`,
  `WriteShardFailures`, `MultiSearchItemFailures`,
  `PartialFailures(mask)`); error type reference covering v4 vs
  v5preview internal-field-type divergence.
- `guides/bulk.md`: v4/v5preview field-name and `BulkResp.Items`
  shape divergences with paired error-iteration examples for v4's
  `[]map[string]BulkRespItem` and v5preview's `[]BulkItem`.
- `UPGRADING.md`: keeps version-history essentials for the >=5.0
  partial-failure model and v5preview Router injection;
  forward-links to the new package docs.

Ref: #816
Ref: opensearch-project/opensearch-api-specification/pull/1137
@sean-
sean- force-pushed the worktree-partial-failure-mode branch from 0c6dbf0 to ccf8f79 Compare June 4, 2026 21:52
Introduce a per-operation `x-error-responses` vendor extension whose
value is an array of `$ref` objects pointing at wrapper schemas in
`spec/schemas/_common.errors.yaml`. Each wrapper describes one
auxiliary failure shape an endpoint may carry alongside its primary
2xx response — including arity (array vs object vs map) and any
parallel-array grouping (e.g. `task_failures[]` + `node_failures[]`
together for the Tasks API) — using ordinary JSON Schema. Client
generators walk an operation's `x-error-responses` array, look up
each wrapper, and emit one typed handler per wrapper that detects
and extracts the embedded failure content.

The new `_common.errors.yaml` defines 15 wrappers covering the
distinct partial-failure encodings present in OpenSearch today:
BulkItems, SearchShards, WriteShards, BroadcastShards, NodeFailures,
BulkByScrollFailures, TaskFailures, MultiSearchItems, MultiDocItems,
SnapshotCreateShardFailures, SnapshotGetShardFailures,
SimulateDocFailures, RankEvalFailures, IngestionShardFailures, and
PitNodeFailures. Each wrapper composes existing element schemas
(`ShardSearchFailure`, `ShardFailure`, `NodeStatistics`,
`TaskFailure`, `SnapshotShardFailure`, `IngestionStateShardFailure`,
`BulkByScrollFailure`, etc.) — no new element types are added.

Annotate 115 operation variants across 10 namespace files (_core,
asynchronous_search, cluster, dangling_indices, indices, ingest,
ingestion, nodes, snapshot, tasks). `indices.recovery` and
`indices.get_upgrade` are intentionally not annotated: their
response classes extend `BroadcastResponse` but suppress the
`_shards` block in their `toXContent` overrides.

Document the extension in DEVELOPER_GUIDE.md alongside the existing
vendor extensions list.

Motivated by `opensearch-go`'s port of partial-failure error
handling into its generated `v5preview/opensearchapi/` package,
which needs a generator-driven mapping from operation to
auxiliary failure types.

Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
@sean-
sean- force-pushed the worktree-partial-failure-mode branch from ccf8f79 to 465440c Compare June 4, 2026 22:08
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.

1 participant