Add PickProperties type and @withPickedProperties decorator (#3488)

Add PickProperties based on OmitProperties

Based on
https://github.com/microsoft/typespec/discussions/3484#discussioncomment-9610555
you may not be wanting more mutating decorators, but PR is here if it's
ok.

---------

Co-authored-by: Timothee Guerin <timothee.guerin@outlook.com>
This commit is contained in:
☃ Elliot Shepherd 2024-06-12 01:12:23 +10:00 коммит произвёл GitHub
Родитель 22372f907b
Коммит d33090e933
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 137 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/compiler"
---
Add `PickProperties` type to dynamically select a subset of a model

Просмотреть файл

@ -96,6 +96,23 @@ model OptionalProperties<Source>
| 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<Source, Keys>
```
#### Template Parameters
| Name | Description |
|------|-------------|
| Source | An object whose properties are spread. |
| Keys | The property keys to include. |
#### Properties
None

Просмотреть файл

@ -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.

Просмотреть файл

@ -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.
*/

Просмотреть файл

@ -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,

Просмотреть файл

@ -604,6 +604,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
//---------------------------------------------------------------------------

Просмотреть файл

@ -53,6 +53,18 @@ model OmitProperties<Source, Keys extends string> {
...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, Keys extends string> {
...Source;
}
/**
* Represents a collection of properties with default values omitted.
*

Просмотреть файл

@ -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<string>();
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 = (

Просмотреть файл

@ -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<OriginalModel, "pickMe"> {
}`
);
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<OriginalModel, "pickMe" | "pickMeToo"> {
}`
);
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(