diff --git a/compiler/src/model/metamodel.ts b/compiler/src/model/metamodel.ts index 05673cc6de..9f330ce6e9 100644 --- a/compiler/src/model/metamodel.ts +++ b/compiler/src/model/metamodel.ts @@ -180,7 +180,7 @@ export abstract class BaseType { specLocation: string } -export type Variants = ExternalTag | InternalTag | Container +export type Variants = ExternalTag | InternalTag | Container | Untagged export class VariantBase { /** @@ -207,6 +207,10 @@ export class Container extends VariantBase { kind: 'container' } +export class Untagged extends VariantBase { + kind: 'untagged' +} + /** * Inherits clause (aka extends or implements) for an interface or request */ @@ -358,8 +362,11 @@ export class TypeAlias extends BaseType { type: ValueOf /** generic parameters: either concrete types or open parameters from the enclosing type */ generics?: TypeName[] - /** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */ - variants?: InternalTag | ExternalTag + /** + * Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal) + * and untagged variants + */ + variants?: InternalTag | ExternalTag | Untagged } // ------------------------------------------------------------------------------------------------ diff --git a/compiler/src/model/utils.ts b/compiler/src/model/utils.ts index 86d6f8d8c0..66e1cd774c 100644 --- a/compiler/src/model/utils.ts +++ b/compiler/src/model/utils.ts @@ -496,8 +496,8 @@ export function modelTypeAlias (declaration: TypeAliasDeclaration): model.TypeAl if (variants != null) { assert( declaration.getJsDocs(), - variants.kind === 'internal_tag' || variants.kind === 'external_tag', - 'Type Aliases can only have internal or external variants' + variants.kind === 'internal_tag' || variants.kind === 'external_tag' || variants.kind === 'untagged', + 'Type Aliases can only have internal, external or untagged variants' ) typeAlias.variants = variants } @@ -1065,6 +1065,13 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined { } } + if (type === 'untagged') { + return { + kind: 'untagged', + nonExhaustive: nonExhaustive + } + } + assert(jsDoc, type === 'internal', `Bad variant type: ${type}`) const pairs = parseKeyValues(jsDoc, values, 'tag', 'default') diff --git a/compiler/src/steps/validate-model.ts b/compiler/src/steps/validate-model.ts index fa3fd581cf..5df841c6ea 100644 --- a/compiler/src/steps/validate-model.ts +++ b/compiler/src/steps/validate-model.ts @@ -575,7 +575,7 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma } } - function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag): void { + function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag | model.Untagged): void { if (variants.kind === 'external_tag') { // All items must have a 'variant' attribute const items = flattenUnionMembers(valueOf) @@ -619,6 +619,55 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma } validateValueOf(valueOf, new Set()) + } else if (variants.kind === 'untagged') { + const items = flattenUnionMembers(valueOf) + + const baseTypes = new Set(); + + for (const item of items) { + if (item.kind !== 'instance_of') { + modelError('Items of type untagged unions must be type references') + } else { + validateTypeRef(item.type, undefined, new Set()) + const type = getTypeDef(item.type) + if (type == null) { + modelError(`Type ${fqn(item.type)} not found`) + } else { + if (type.kind !== 'interface') { + modelError(`Type ${fqn(item.type)} must be an interface to be used in an untagged union`) + continue + } + + const intf = (type as model.Interface) + + if (!intf.inherits) { + modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`) + continue + } + + baseTypes.add(fqn(intf.inherits.type)) + + const baseType = getTypeDef(intf.inherits.type) + if (baseType === null) { + modelError(`Type ${fqn(intf.inherits.type)} not found`) + continue + } + + if (baseType?.kind !== 'interface') { + modelError(`Type ${fqn(intf.inherits.type)} must be an interface to be used as the base class of another interface`) + continue + } + + if (!intf.inherits.generics || intf.inherits.generics.length === 0) { + modelError('The common base type of an untagged union must accept at least one generic type argument') + } + } + } + } + + if (baseTypes.size !== 1) { + modelError('All items of an untagged union must derive from the same common base type') + } } } diff --git a/specification/_types/query_dsl/compound.ts b/specification/_types/query_dsl/compound.ts index 3e18821d51..f3ca40e765 100644 --- a/specification/_types/query_dsl/compound.ts +++ b/specification/_types/query_dsl/compound.ts @@ -171,7 +171,9 @@ export class DecayPlacement { origin?: TOrigin } -export class DecayFunctionBase { +export class DecayFunctionBase + implements AdditionalProperty> +{ /** * Determines how the distance is calculated when a field used for computing the decay contains multiple values. * @server_default min @@ -179,19 +181,25 @@ export class DecayFunctionBase { multi_value_mode?: MultiValueMode } -export class NumericDecayFunction - extends DecayFunctionBase - implements AdditionalProperty> {} +export class NumericDecayFunction extends DecayFunctionBase< + double, + double +> {} -export class DateDecayFunction - extends DecayFunctionBase - implements AdditionalProperty> {} +export class DateDecayFunction extends DecayFunctionBase< + DateMath, + Duration +> {} -export class GeoDecayFunction - extends DecayFunctionBase - implements AdditionalProperty> {} +export class GeoDecayFunction extends DecayFunctionBase< + GeoLocation, + Distance +> {} -/** @codegen_names date, numeric, geo */ +/** + * @codegen_names date, numeric, geo + * @variants untagged + */ // Note: deserialization depends on value types export type DecayFunction = | DateDecayFunction diff --git a/specification/_types/query_dsl/specialized.ts b/specification/_types/query_dsl/specialized.ts index f4e99b5277..92777001f3 100644 --- a/specification/_types/query_dsl/specialized.ts +++ b/specification/_types/query_dsl/specialized.ts @@ -69,7 +69,10 @@ export class DateDistanceFeatureQuery extends DistanceFeatureQueryBase< Duration > {} -/** @codegen_names geo, date */ +/** + * @codegen_names geo, date + * @variants untagged + */ // Note: deserialization depends on value types export type DistanceFeatureQuery = | GeoDistanceFeatureQuery diff --git a/specification/_types/query_dsl/term.ts b/specification/_types/query_dsl/term.ts index d828eed53d..eb12d3e102 100644 --- a/specification/_types/query_dsl/term.ts +++ b/specification/_types/query_dsl/term.ts @@ -105,33 +105,30 @@ export class PrefixQuery extends QueryBase { case_insensitive?: boolean } -export class RangeQueryBase extends QueryBase { +export class RangeQueryBase extends QueryBase { /** * Indicates how the range query matches values for `range` fields. * @server_default intersects */ relation?: RangeRelation -} - -export class DateRangeQuery extends RangeQueryBase { /** * Greater than. */ - gt?: DateMath + gt?: T /** * Greater than or equal to. */ - gte?: DateMath + gte?: T /** * Less than. */ - lt?: DateMath + lt?: T /** * Less than or equal to. */ - lte?: DateMath - from?: DateMath | null - to?: DateMath | null + lte?: T + from?: T | null + to?: T | null /** * Date format used to convert `date` values in the query. */ @@ -142,51 +139,24 @@ export class DateRangeQuery extends RangeQueryBase { time_zone?: TimeZone } -export class NumberRangeQuery extends RangeQueryBase { - /** - * Greater than. - */ - gt?: double - /** - * Greater than or equal to. - */ - gte?: double - /** - * Less than. - */ - lt?: double - /** - * Less than or equal to. - */ - lte?: double - from?: double | null - to?: double | null -} +export class DateRangeQuery extends RangeQueryBase< + DateMath +> {} -export class TermsRangeQuery extends RangeQueryBase { - /** - * Greater than. - */ - gt?: string - /** - * Greater than or equal to. - */ - gte?: string - /** - * Less than. - */ - lt?: string - /** - * Less than or equal to. - */ - lte?: string - from?: string | null - to?: string | null -} +export class NumberRangeQuery extends RangeQueryBase< + double +> {} -/** @codegen_names date, number, terms */ +export class TermRangeQuery extends RangeQueryBase< + string +> {} + +/** + * @codegen_names date, number, term + * @variants untagged + */ // Note: deserialization depends on value types -export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermsRangeQuery +export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermRangeQuery export enum RangeRelation { /**