Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PickProperties type and @withPickedProperties decorator #3488

Merged
merged 2 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .chronus/changes/pick-properties-2024-5-3-17-55-34.md
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions docs/standard-library/built-in-data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 18 additions & 0 deletions docs/standard-library/built-in-decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions packages/compiler/generated-defs/TypeSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/compiler/generated-defs/TypeSpec.ts-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
$visibility,
$withDefaultKeyVisibility,
$withOptionalProperties,
$withPickedProperties,
$withUpdateableProperties,
$withVisibility,
$withoutDefaultValues,
Expand Down Expand Up @@ -76,6 +77,7 @@ import type {
VisibilityDecorator,
WithDefaultKeyVisibilityDecorator,
WithOptionalPropertiesDecorator,
WithPickedPropertiesDecorator,
WithUpdateablePropertiesDecorator,
WithVisibilityDecorator,
WithoutDefaultValuesDecorator,
Expand All @@ -88,6 +90,7 @@ type Decorators = {
$withOptionalProperties: WithOptionalPropertiesDecorator;
$withUpdateableProperties: WithUpdateablePropertiesDecorator;
$withoutOmittedProperties: WithoutOmittedPropertiesDecorator;
$withPickedProperties: WithPickedPropertiesDecorator;
$withoutDefaultValues: WithoutDefaultValuesDecorator;
$withDefaultKeyVisibility: WithDefaultKeyVisibilityDecorator;
$summary: SummaryDecorator;
Expand Down Expand Up @@ -131,6 +134,7 @@ const _: Decorators = {
$withOptionalProperties,
$withUpdateableProperties,
$withoutOmittedProperties,
$withPickedProperties,
$withoutDefaultValues,
$withDefaultKeyVisibility,
$summary,
Expand Down
6 changes: 6 additions & 0 deletions packages/compiler/lib/std/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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
//---------------------------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions packages/compiler/lib/std/types.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
24 changes: 24 additions & 0 deletions packages/compiler/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
VisibilityDecorator,
WithDefaultKeyVisibilityDecorator,
WithOptionalPropertiesDecorator,
WithPickedPropertiesDecorator,
WithUpdateablePropertiesDecorator,
WithVisibilityDecorator,
WithoutDefaultValuesDecorator,
Expand Down Expand Up @@ -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 = (
Expand Down
37 changes: 37 additions & 0 deletions packages/compiler/test/decorators/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading