Skip to content

Commit 43080f9

Browse files
committed
Maintain "behavior" meta-data in specification
1 parent ce9d54c commit 43080f9

File tree

23 files changed

+453
-257
lines changed

23 files changed

+453
-257
lines changed

compiler/src/model/build-model.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,8 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int
459459
properties: new Array<model.Property>()
460460
}
461461

462-
hoistTypeAnnotations(type, declaration.getJsDocs())
462+
const jsDocs = declaration.getJsDocs()
463+
hoistTypeAnnotations(type, jsDocs)
463464

464465
const variant = parseVariantNameTag(declaration.getJsDocs())
465466
if (typeof variant === 'string') {
@@ -522,7 +523,7 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int
522523
if (Node.isClassDeclaration(declaration)) {
523524
for (const implement of declaration.getImplements()) {
524525
if (isKnownBehavior(implement)) {
525-
type.behaviors = (type.behaviors ?? []).concat(modelBehaviors(implement))
526+
type.behaviors = (type.behaviors ?? []).concat(modelBehaviors(implement, jsDocs))
526527
} else {
527528
type.implements = (type.implements ?? []).concat(modelImplements(implement))
528529
}

compiler/src/model/metamodel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export class Container extends VariantBase {
213213
export class Inherits {
214214
type: TypeName
215215
generics?: ValueOf[]
216+
meta?: string[]
216217
}
217218

218219
/**

compiler/src/model/utils.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -414,14 +414,32 @@ export function modelImplements (node: ExpressionWithTypeArguments): model.Inher
414414
* A class could have multiple behaviors from multiple classes,
415415
* which are defined inside the node typeArguments.
416416
*/
417-
export function modelBehaviors (node: ExpressionWithTypeArguments): model.Inherits {
417+
export function modelBehaviors (node: ExpressionWithTypeArguments, jsDocs: JSDoc[]): model.Inherits {
418+
const behaviorName = node.getExpression().getText()
418419
const generics = node.getTypeArguments().map(node => modelType(node))
420+
421+
let meta: string[] | undefined
422+
const tags = parseJsDocTagsAllowDuplicates(jsDocs)
423+
if (tags.behavior_meta !== undefined) {
424+
// Splits a string by comma, but preserves comma in quoted strings
425+
const re = /(?<=")[^"]+?(?="(?:\s*?,|\s*?$))|(?<=(?:^|,)\s*?)(?:[^,"\s][^,"]*[^,"\s])|(?:[^,"\s])(?![^"]*?"(?:\s*?,|\s*?$))(?=\s*?(?:,|$))/g
426+
for (const tag of tags.behavior_meta) {
427+
const id = tag.split(' ')
428+
if (id[0].trim() !== behaviorName) {
429+
continue
430+
}
431+
meta = id.slice(1).join(' ').match(re) as string[]
432+
break
433+
}
434+
}
435+
419436
return {
420437
type: {
421-
name: node.getExpression().getText(),
438+
name: behaviorName,
422439
namespace: getNameSpace(node)
423440
},
424-
...(generics.length > 0 && { generics })
441+
...(generics.length > 0 && { generics }),
442+
meta
425443
}
426444
}
427445

@@ -574,7 +592,7 @@ function setTags<TType extends model.BaseType | model.Property | model.EnumMembe
574592
)
575593

576594
for (const tag of validTags) {
577-
if (tag === 'behavior') continue
595+
if (tag === 'behavior' || tag === 'behavior_meta') continue
578596
if (tags[tag] !== undefined) {
579597
setter(tags, tag, tags[tag])
580598
}
@@ -695,7 +713,7 @@ export function hoistTypeAnnotations (type: model.TypeDefinition, jsDocs: JSDoc[
695713
assert(jsDocs, jsDocs.length < 2, 'Use a single multiline jsDoc block instead of multiple single line blocks')
696714

697715
const validTags = ['class_serializer', 'doc_url', 'doc_id', 'behavior', 'variants', 'variant', 'shortcut_property',
698-
'codegen_names', 'non_exhaustive', 'es_quirk']
716+
'codegen_names', 'non_exhaustive', 'es_quirk', 'behavior_meta']
699717
const tags = parseJsDocTags(jsDocs)
700718
if (jsDocs.length === 1) {
701719
const description = jsDocs[0].getDescription()

compiler/src/steps/validate-model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,13 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
652652
context.push('Behaviors')
653653
for (const parent of typeDef.behaviors) {
654654
validateTypeRef(parent.type, parent.generics, openGenerics)
655+
656+
if (parent.type.name === 'AdditionalProperty' && (parent.meta == null || parent.meta.length < 2 || parent.meta.length > 3)) {
657+
modelError(`AdditionalProperty behavior for type '${fqn(typeDef.name)}' requires a 'behavior_meta' decorator with at least 2 arguments (name of name, name of value, description)`)
658+
}
659+
if (parent.type.name === 'AdditionalProperties' && (parent.meta == null || parent.meta.length !== 2)) {
660+
modelError(`AdditionalProperties behavior for type '${fqn(typeDef.name)}' requires a 'behavior_meta' decorator with exactly 2 arguments (name, description)`)
661+
}
655662
}
656663
context.pop()
657664
}

docs/behaviors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ This puts it into a bin that needs a client specific solution.
1515
We therefore document the requirement to behave like a dictionary for unknown properties with this interface.
1616

1717
```ts
18+
/**
19+
* @behavior_meta AdditionalProperties sub_aggregations
20+
*/
1821
class IpRangeBucket implements AdditionalProperties<AggregateName, Aggregate> {}
1922
```
2023

2124
There are also many places where we expect only one runtime-defined property, such as in field-related queries. To capture that uniqueness constraint, we can use the `AdditionalProperty` (singular) behavior.
2225

2326
```ts
27+
/**
28+
* @behavior_meta AdditionalProperty field, bounding_box
29+
*/
2430
class GeoBoundingBoxQuery extends QueryBase
2531
implements AdditionalProperty<Field, BoundingBox>
2632
```

0 commit comments

Comments
 (0)