diff --git a/docs/standard-library/built-in-data-types.md b/docs/standard-library/built-in-data-types.md index 719158cd91..d058b9dbe9 100644 --- a/docs/standard-library/built-in-data-types.md +++ b/docs/standard-library/built-in-data-types.md @@ -96,6 +96,23 @@ model OptionalProperties | Source | An object whose spread properties are all optional. | +#### Properties +None + +### `PickProperties` {#PickProperties} + +Represents a collection of properties with only the specified keys included. +```typespec +model PickProperties +``` + +#### Template Parameters +| Name | Description | +|------|-------------| +| Source | An object whose properties are spread. | +| Keys | The property keys to include. | + + #### Properties None diff --git a/docs/standard-library/built-in-decorators.md b/docs/standard-library/built-in-decorators.md index b705b4b502..86336bf0c3 100644 --- a/docs/standard-library/built-in-decorators.md +++ b/docs/standard-library/built-in-decorators.md @@ -933,6 +933,24 @@ Returns the model with the given properties omitted. +### `@withPickedProperties` {#@withPickedProperties} + +Returns the model with only the given properties included. +```typespec +@withPickedProperties(pick: string | Union) +``` + +#### Target + +`Model` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| pick | `string \| Union` | List of properties to include | + + + ### `@withUpdateableProperties` {#@withUpdateableProperties} Returns the model with non-updateable properties removed. diff --git a/packages/compiler/generated-defs/TypeSpec.ts b/packages/compiler/generated-defs/TypeSpec.ts index 489bc02cad..5b2724f7ea 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts @@ -76,6 +76,17 @@ export type WithoutOmittedPropertiesDecorator = ( omit: Type ) => void; +/** + * Returns the model with only the given properties included. + * + * @param pick List of properties to include + */ +export type WithPickedPropertiesDecorator = ( + context: DecoratorContext, + target: Model, + pick: Type +) => void; + /** * Returns the model with any default values removed. */ diff --git a/packages/compiler/generated-defs/TypeSpec.ts-test.ts b/packages/compiler/generated-defs/TypeSpec.ts-test.ts index 84b8407831..b923f63be0 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts-test.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts-test.ts @@ -35,6 +35,7 @@ import { $visibility, $withDefaultKeyVisibility, $withOptionalProperties, + $withPickedProperties, $withUpdateableProperties, $withVisibility, $withoutDefaultValues, @@ -76,6 +77,7 @@ import type { VisibilityDecorator, WithDefaultKeyVisibilityDecorator, WithOptionalPropertiesDecorator, + WithPickedPropertiesDecorator, WithUpdateablePropertiesDecorator, WithVisibilityDecorator, WithoutDefaultValuesDecorator, @@ -88,6 +90,7 @@ type Decorators = { $withOptionalProperties: WithOptionalPropertiesDecorator; $withUpdateableProperties: WithUpdateablePropertiesDecorator; $withoutOmittedProperties: WithoutOmittedPropertiesDecorator; + $withPickedProperties: WithPickedPropertiesDecorator; $withoutDefaultValues: WithoutDefaultValuesDecorator; $withDefaultKeyVisibility: WithDefaultKeyVisibilityDecorator; $summary: SummaryDecorator; @@ -131,6 +134,7 @@ const _: Decorators = { $withOptionalProperties, $withUpdateableProperties, $withoutOmittedProperties, + $withPickedProperties, $withoutDefaultValues, $withDefaultKeyVisibility, $summary, diff --git a/packages/compiler/lib/std/decorators.tsp b/packages/compiler/lib/std/decorators.tsp index 2259b95bdc..4d85b0eb49 100644 --- a/packages/compiler/lib/std/decorators.tsp +++ b/packages/compiler/lib/std/decorators.tsp @@ -141,7 +141,10 @@ extern dec error(target: Model); * scalar uuid extends string; * ``` */ -extern dec format(target: string | bytes | ModelProperty, format: valueof string); +extern dec format( + target: string | bytes | ModelProperty, + format: valueof string +); /** * Specify the the pattern this string should respect using simple regular expression syntax. @@ -251,7 +254,10 @@ extern dec maxValue(target: numeric | ModelProperty, value: valueof numeric); * scalar distance is float64; * ``` */ -extern dec minValueExclusive(target: numeric | ModelProperty, value: valueof numeric); +extern dec minValueExclusive( + target: numeric | ModelProperty, + value: valueof numeric +); /** * Specify the maximum value this numeric type should be, exclusive of the given @@ -264,7 +270,10 @@ extern dec minValueExclusive(target: numeric | ModelProperty, value: valueof num * scalar distance is float64; * ``` */ -extern dec maxValueExclusive(target: numeric | ModelProperty, value: valueof numeric); +extern dec maxValueExclusive( + target: numeric | ModelProperty, + value: valueof numeric +); /** * Mark this string as a secret value that should be treated carefully to avoid exposure @@ -304,7 +313,11 @@ extern dec tag(target: Namespace | Interface | Operation, tag: valueof string); * } * ``` */ -extern dec friendlyName(target: unknown, name: valueof string, formatArgs?: unknown); +extern dec friendlyName( + target: unknown, + name: valueof string, + formatArgs?: unknown +); /** * Provide a set of known values to a string type. @@ -396,7 +409,11 @@ extern dec projectedName( * ^ error cannot use subtype * ``` */ -extern dec encodedName(target: unknown, mimeType: valueof string, name: valueof string); +extern dec encodedName( + target: unknown, + mimeType: valueof string, + name: valueof string +); /** * Specify the property to be used to discriminate this type. @@ -604,6 +621,12 @@ extern dec withoutDefaultValues(target: Model); */ extern dec withoutOmittedProperties(target: Model, omit: string | Union); +/** + * Returns the model with only the given properties included. + * @param pick List of properties to include + */ +extern dec withPickedProperties(target: Model, pick: string | Union); + //--------------------------------------------------------------------------- // Debugging //--------------------------------------------------------------------------- @@ -624,10 +647,16 @@ extern dec inspectTypeName(target: unknown, text: valueof string); * Sets which visibilities apply to parameters for the given operation. * @param visibilities List of visibility strings which apply to this operation. */ -extern dec parameterVisibility(target: Operation, ...visibilities: valueof string[]); +extern dec parameterVisibility( + target: Operation, + ...visibilities: valueof string[] +); /** * Sets which visibilities apply to the return type for the given operation. * @param visibilities List of visibility strings which apply to this operation. */ -extern dec returnTypeVisibility(target: Operation, ...visibilities: valueof string[]); +extern dec returnTypeVisibility( + target: Operation, + ...visibilities: valueof string[] +); diff --git a/packages/compiler/lib/std/types.tsp b/packages/compiler/lib/std/types.tsp index 7f02346fe3..12fa180add 100644 --- a/packages/compiler/lib/std/types.tsp +++ b/packages/compiler/lib/std/types.tsp @@ -53,6 +53,18 @@ model OmitProperties { ...Source; } +/** + * Represents a collection of properties with only the specified keys included. + * + * @template Source An object whose properties are spread. + * @template Keys The property keys to include. + */ +@doc("The template for picking properties.") +@withPickedProperties(Keys) +model PickProperties { + ...Source; +} + /** * Represents a collection of properties with default values omitted. * diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index 0ea509d9e6..124fd3ceaa 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -31,6 +31,7 @@ import type { VisibilityDecorator, WithDefaultKeyVisibilityDecorator, WithOptionalPropertiesDecorator, + WithPickedPropertiesDecorator, WithUpdateablePropertiesDecorator, WithVisibilityDecorator, WithoutDefaultValuesDecorator, @@ -879,6 +880,29 @@ export const $withoutOmittedProperties: WithoutOmittedPropertiesDecorator = ( filterModelPropertiesInPlace(target, (prop) => !omitNames.has(prop.name)); }; +// -- @withPickedProperties decorator ---------------------- + +export const $withPickedProperties: WithPickedPropertiesDecorator = ( + context: DecoratorContext, + target: Model, + pickedProperties: Type +) => { + // Get the property or properties to pick + const pickedNames = new Set(); + if (pickedProperties.kind === "String") { + pickedNames.add(pickedProperties.value); + } else if (pickedProperties.kind === "Union") { + for (const variant of pickedProperties.variants.values()) { + if (variant.type.kind === "String") { + pickedNames.add(variant.type.value); + } + } + } + + // Remove all properties not picked + filterModelPropertiesInPlace(target, (prop) => pickedNames.has(prop.name)); +}; + // -- @withoutDefaultValues decorator ---------------------- export const $withoutDefaultValues: WithoutDefaultValuesDecorator = ( diff --git a/packages/compiler/test/decorators/decorators.test.ts b/packages/compiler/test/decorators/decorators.test.ts index d9fedf691f..5cb9b1a9a1 100644 --- a/packages/compiler/test/decorators/decorators.test.ts +++ b/packages/compiler/test/decorators/decorators.test.ts @@ -782,6 +782,43 @@ describe("compiler: built-in decorators", () => { }); }); + describe("@withPickedProperties", () => { + it("picks a model property when given a string literal", async () => { + const { TestModel } = await runner.compile( + ` + model OriginalModel { + pickMe: string; + notMe: string; + } + + @test + model TestModel is PickProperties { + }` + ); + + const properties = TestModel.kind === "Model" ? Array.from(TestModel.properties.keys()) : []; + deepStrictEqual(properties, ["pickMe"]); + }); + + it("picks model properties when given a union containing strings", async () => { + const { TestModel } = await runner.compile( + ` + model OriginalModel { + pickMe: string; + pickMeToo: string; + notMe: string; + } + + @test + model TestModel is PickProperties { + }` + ); + + const properties = TestModel.kind === "Model" ? Array.from(TestModel.properties.keys()) : []; + deepStrictEqual(properties, ["pickMe", "pickMeToo"]); + }); + }); + describe("@withDefaultKeyVisibility", () => { it("sets the default visibility on a key property when not already present", async () => { const { TestModel } = (await runner.compile(