Skip to content

Commit 7c3d1ce

Browse files
authored
Add support for non exhaustive variants (#1662)
1 parent 280095b commit 7c3d1ce

File tree

16 files changed

+266
-208
lines changed

16 files changed

+266
-208
lines changed

compiler/src/model/metamodel.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,28 @@ export abstract class BaseType {
177177

178178
export type Variants = ExternalTag | InternalTag | Container
179179

180-
export class ExternalTag {
180+
export class VariantBase {
181+
/**
182+
* Is this variant type open to extensions? Default to false. Used for variants that can
183+
* be extended with plugins. If true, target clients should allow for additional variants
184+
* with a variant tag outside the ones defined in the spec and arbitrary data as the value.
185+
*/
186+
nonExhaustive?: boolean
187+
}
188+
189+
export class ExternalTag extends VariantBase {
181190
kind: 'external_tag'
182191
}
183192

184-
export class InternalTag {
193+
export class InternalTag extends VariantBase {
185194
kind: 'internal_tag'
186195
/* Name of the property that holds the variant tag */
187196
tag: string
188197
/* Default value for the variant tag if it's missing */
189198
defaultTag?: string
190199
}
191200

192-
export class Container {
201+
export class Container extends VariantBase {
193202
kind: 'container'
194203
}
195204

compiler/src/model/utils.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export function modelEnumDeclaration (declaration: EnumDeclaration): model.Enum
440440
}
441441

442442
const tags = parseJsDocTags(declaration.getJsDocs())
443-
if (typeof tags.open_enum === 'string') {
443+
if (typeof tags.non_exhaustive === 'string') {
444444
type.isOpen = true
445445
}
446446

@@ -647,7 +647,7 @@ export function hoistTypeAnnotations (type: model.TypeDefinition, jsDocs: JSDoc[
647647
// We want to enforce a single jsDoc block.
648648
assert(jsDocs, jsDocs.length < 2, 'Use a single multiline jsDoc block instead of multiple single line blocks')
649649

650-
const validTags = ['class_serializer', 'doc_url', 'doc_id', 'behavior', 'variants', 'variant', 'shortcut_property', 'codegen_names']
650+
const validTags = ['class_serializer', 'doc_url', 'doc_id', 'behavior', 'variants', 'variant', 'shortcut_property', 'codegen_names', 'non_exhaustive']
651651
const tags = parseJsDocTags(jsDocs)
652652
if (jsDocs.length === 1) {
653653
const description = jsDocs[0].getDescription()
@@ -665,6 +665,8 @@ export function hoistTypeAnnotations (type: model.TypeDefinition, jsDocs: JSDoc[
665665
}
666666
} else if (tag === 'variants') {
667667
} else if (tag === 'variant') {
668+
} else if (tag === 'non_exhaustive') {
669+
assert(jsDocs, typeof tags.variants === 'string', '@non_exhaustive only applies to enums and @variants')
668670
} else if (tag === 'doc_url') {
669671
assert(jsDocs, isValidUrl(value), '@doc_url is not a valid url')
670672
type.docUrl = value
@@ -954,13 +956,21 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
954956
return undefined
955957
}
956958

959+
const nonExhaustive = (typeof tags.non_exhaustive === 'string') ? true : undefined
960+
957961
const [type, ...values] = tags.variants.split(' ')
958962
if (type === 'external') {
959-
return { kind: 'external_tag' }
963+
return {
964+
kind: 'external_tag',
965+
nonExhaustive: nonExhaustive
966+
}
960967
}
961968

962969
if (type === 'container') {
963-
return { kind: 'container' }
970+
return {
971+
kind: 'container',
972+
nonExhaustive: nonExhaustive
973+
}
964974
}
965975

966976
assert(jsDoc, type === 'internal', `Bad variant type: ${type}`)
@@ -970,6 +980,7 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
970980

971981
return {
972982
kind: 'internal_tag',
983+
nonExhaustive: nonExhaustive,
973984
tag: pairs.tag,
974985
defaultTag: pairs.default
975986
}
@@ -1002,13 +1013,16 @@ export function parseCommaSeparated (value: string): string[] {
10021013

10031014
/**
10041015
* Parses an array of "key=value" pairs and validate key names. Values can optionally be enclosed with single
1005-
* or double quotes.
1016+
* or double quotes. If there is only a key with no value (no '=') the value is set to 'true'
10061017
*/
10071018
export function parseKeyValues (node: Node | Node[], pairs: string[], ...validKeys: string[]): Record<string, string> {
10081019
const result = {}
10091020
pairs.forEach(item => {
10101021
const kv = item.split('=')
1011-
assert(node, kv.length === 2, 'Malformed key/value list')
1022+
assert(node, kv.length <= 2, 'Malformed key/value list')
1023+
if (kv.length === 1) {
1024+
kv.push('true')
1025+
}
10121026
assert(node, validKeys.includes(kv[0]), `Unknown key '${kv[0]}'`)
10131027
result[kv[0]] = kv[1].replace(/["']/g, '')
10141028
})

docs/modeling-guide.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ enum Orientation {
9898
}
9999
```
100100

101-
Some enumerations can accept arbitrary values other than the one defined. The `@open_enum` jsdoc tac can be used to describe this behavior.
102-
By default, an enum is to be considered closed.
101+
Some enumerations can accept arbitrary values other than the ones defined. The `@non_exhaustive` jsdoc tag can be used to describe this behavior.
102+
By default, an enum is to be considered exhaustive.
103103

104104
```ts
105-
/** @open_enum */
105+
/** @non_exhaustive */
106106
export enum ScriptLanguage {
107107
painless,
108108
expression,
@@ -234,6 +234,11 @@ class Response {
234234

235235
Variants is a special syntax that can be used by language generators to understand
236236
which type they will need to build based on the variant configuration.
237+
238+
If the list of variants is not exhaustive (e.g. for types where new variants can be added by
239+
Elasticsearch plugins), you can add the `@non_exhaustive` js doc tag to indicate that additional
240+
variants can exist and should be accepted.
241+
237242
There are three type of variants:
238243

239244
#### Internal

0 commit comments

Comments
 (0)