Skip to content

[@kbn/config-schema] add discriminatedUnion schema type#246095

Merged
nickofthyme merged 10 commits intoelastic:mainfrom
nickofthyme:schema-one-of-kind
Jan 12, 2026
Merged

[@kbn/config-schema] add discriminatedUnion schema type#246095
nickofthyme merged 10 commits intoelastic:mainfrom
nickofthyme:schema-one-of-kind

Conversation

@nickofthyme
Copy link
Copy Markdown
Contributor

@nickofthyme nickofthyme commented Dec 12, 2025

Summary

Adds discriminatedUnion method to @kbn/config-schema for discriminated union of object schemas.

const exampleType = schema.discriminatedUnion('type', [
  schema.object({ type: schema.literal('str'), string: schema.string() }),
  schema.object({ type: schema.literal('num'), number: schema.number() }),
  schema.object({ type: schema.literal('bool'), boolean: schema.boolean() }),
]);

Order of Errors:

  1. missing discriminator property.
  2. discriminator property has wrong type (only strings via schema.literal and one schema.string are permitted)
  3. discriminator is a string but not one of the expected discriminators.
  4. validation errors from the respective schema based on discriminator, only the matching schema is validated.

Also allows for fallback schema. Limit of one fallback, position in array does not matter.

const exampleType = schema.discriminatedUnion('type', [
  schema.object({ type: schema.literal('str'), string: schema.string() }),
  schema.object({ type: schema.literal('num'), number: schema.number() }),
  schema.object({ type: schema.string }, { unknowns: 'allow' }), // allows any other type other than 'str' and 'num'
]);

Error Example

{ type: 'str', string: 123 } // "string" property is a number instead of a string

[string]: expected value of type [string] but got [number]

closes #84264

Checklist

  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The release_note:breaking label should be applied in these situations.
  • The PR description includes the appropriate Release Notes section, and the correct release_note:* label is applied per the guidelines
  • Review the backport guidelines and apply applicable backport:* labels.

@nickofthyme nickofthyme added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting labels Dec 12, 2025
- remove `DiscriminatorType`, not really needed
- fix return type of `oneOfKind` to return correct resolved type
- add type tests
@nickofthyme nickofthyme marked this pull request as ready for review December 12, 2025 14:40
@nickofthyme nickofthyme requested a review from a team as a code owner December 12, 2025 14:40
- allow fallback or catch-all schema
- validate 0 or 1 fallback schema
- validate unique discriminators
@nickofthyme nickofthyme changed the title [@kbn/config-schema] add oneOfKind schema type [@kbn/config-schema] add discriminatedUnion schema type Jan 7, 2026
@nickofthyme nickofthyme requested a review from jloleysens January 8, 2026 21:59
Copy link
Copy Markdown
Contributor

@jloleysens jloleysens left a comment

Choose a reason for hiding this comment

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

Great work @nickofthyme , this looks like a solid version of discriminated union with solid DX.

@elasticmachine
Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
agentBuilder 1400 1401 +1
dashboard 1021 1022 +1
discover 1976 1977 +1
infra 1892 1893 +1
lens 1679 1680 +1
observability 1803 1804 +1
observabilityAIAssistantApp 800 801 +1
securitySolution 8633 8634 +1
stackConnectors 640 641 +1
unifiedDocViewer 778 779 +1
total +10

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/config-schema 143 145 +2

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
lens 1.9MB 1.9MB +1.6KB
observability 1.8MB 1.8MB +1.6KB
securitySolution 10.8MB 10.8MB +1.6KB
stackConnectors 1.1MB 1.1MB +1.6KB
total +6.3KB

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
@kbn/config-schema 20 22 +2

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
lens 63.9KB 63.9KB -2.0B
Unknown metric groups

API count

id before after diff
@kbn/config-schema 150 152 +2

History

@nickofthyme nickofthyme merged commit 9efbd2d into elastic:main Jan 12, 2026
13 checks passed
@nickofthyme nickofthyme deleted the schema-one-of-kind branch January 12, 2026 18:38
tsullivan pushed a commit that referenced this pull request Jan 12, 2026
## Summary

Adds `discriminatedUnion` method to `@kbn/config-schema` for
discriminated union of object schemas.

```ts
const exampleType = schema.discriminatedUnion('type', [
  schema.object({ type: schema.literal('str'), string: schema.string() }),
  schema.object({ type: schema.literal('num'), number: schema.number() }),
  schema.object({ type: schema.literal('bool'), boolean: schema.boolean() }),
]);
```

Order of Errors:
1) missing `discriminator` property.
1) `discriminator` property has wrong type (only strings via
`schema.literal` and one `schema.string` are permitted)
1) `discriminator` is a string but not one of the expected
discriminators.
1) validation errors from the respective schema based on
`discriminator`, only the matching schema is validated.


Also allows for fallback schema. Limit of one fallback, position in
array does not matter.

```ts
const exampleType = schema.discriminatedUnion('type', [
  schema.object({ type: schema.literal('str'), string: schema.string() }),
  schema.object({ type: schema.literal('num'), number: schema.number() }),
  schema.object({ type: schema.string }, { unknowns: 'allow' }), // allows any other type other than 'str' and 'num'
]);
```

## Error Example

```
{ type: 'str', string: 123 } // "string" property is a number instead of a string

[string]: expected value of type [string] but got [number]
```

closes #84264

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
- [x] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.
jloleysens added a commit that referenced this pull request Jan 23, 2026
## Summary

Close #181994

Support generating OAS for `@kbn/config-schema`'s using our new
`schema.discriminatedUnion` type
#246095

## Notes

* Force developers to define a `{ meta: { id: '...' } }` for the objects
inside a `schema.discriminatedUnion` so that we can generate the correct
OAS. Guidance for name is as follows: `<your-id>-<your-team-or-area>`.
The intention is that IDs are globally unique while remaining somewhat
user readable as they will be surfaced in our docs.
* Tackled a few unrelated OAS/schema chores in this PR

## Conversion

Given a schema like:

```ts
    schema.discriminatedUnion('type', [
      schema.object(
        { type: schema.literal('str'), value: schema.string() },
        { meta: { id: 'my-str-my-team' } }
      ),
      schema.object(
        { type: schema.literal('num'), value: schema.number() },
        { meta: { id: 'my-num-team' } }
      ),
    ]),
```

Produce OAS like:

```js
    {
      oneOf: [
        {
          $ref: '#/components/schemas/my-str-my-team',
        },
        {
          $ref: '#/components/schemas/my-num-team',
        },
      ],
      discriminator: {
        propertyName: 'type',
      }
    }
...
components: {
  schemas: {
      'my-str-my-team': {
        type: 'object',
        properties: {
          type: { type: 'string', enum: ['str'] },
        },
      },
      'my-num-team': {
        type: 'object',
        properties: {
          type: { type: 'string', enum: ['num'] },
        },
      },
    },
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[kbn/config-schema] Improve OneOf error message format

5 participants