Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-azure-resource-manager"
- "@azure-tools/typespec-autorest"
- "@azure-tools/typespec-azure-rulesets"
---

Add `@featureFile`, `@featureFiles`, and `@featureFileOptions` decorators in `Azure.ResourceManager` namespace as alternatives to the Legacy `@feature`, `@features`, and `@featureOptions` decorators. Add `arm-feature-file-usage-discourage` linting rule. Fix `arm-custom-resource-usage-discourage` rule to propagate suppressions from model templates to their instantiations.
32 changes: 16 additions & 16 deletions packages/typespec-autorest/test/arm/arm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ it("generates PATCH bodies for resource patch of common resource envelope mixins
it("can split resources and operations by feature", async () => {
const { privateLink, privateEndpoint } = await CompileOpenApiWithFeatures(
`
@Azure.ResourceManager.Legacy.features(Features)
@Azure.ResourceManager.featureFiles(Features)
@armProviderNamespace
@armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v5)
namespace Microsoft.PrivateLinkTest;
Expand All @@ -522,7 +522,7 @@ it("can split resources and operations by feature", async () => {

interface Operations extends Azure.ResourceManager.Operations {}

@Azure.ResourceManager.Legacy.feature(Features.privateEndpoint)
@Azure.ResourceManager.featureFile(Features.privateEndpoint)
@tenantResource
model PrivateEndpointConnectionResource is ProxyResource<PrivateEndpointConnectionProperties> {
@path
Expand All @@ -531,20 +531,20 @@ it("can split resources and operations by feature", async () => {
name: string;
}

@Azure.ResourceManager.Legacy.feature(Features.privateEndpoint)
@Azure.ResourceManager.featureFile(Features.privateEndpoint)
@armResourceOperations(PrivateEndpointConnectionResource)
interface PrivateEndpointConnections {
#suppress "deprecated" "PrivateLinkResourceListResultV5 validation"
listConnections is ArmResourceListByParent<PrivateEndpointConnectionResource,
Response = ArmResponse<Azure.ResourceManager.CommonTypes.PrivateEndpointConnectionListResultV5>>;
}

@Azure.ResourceManager.Legacy.feature(Features.privateLink)
@Azure.ResourceManager.featureFile(Features.privateLink)
model PrivateLinkResource is ProxyResource<PrivateLinkResourceProperties> {
...PrivateLinkResourceParameter;
}

@Azure.ResourceManager.Legacy.feature(Features.privateLink)
@Azure.ResourceManager.featureFile(Features.privateLink)
@armResourceOperations(PrivateLinkResource)
interface PrivateLinkResources {
#suppress "deprecated" "PrivateLinkResourceListResultV5 validation"
Expand Down Expand Up @@ -576,49 +576,49 @@ it("can represent type references within and between features", async () => {
const { featureA, featureB, shared } = await CompileOpenApiWithFeatures(
`

@Azure.ResourceManager.Legacy.features(Features)
@Azure.ResourceManager.featureFiles(Features)
@armProviderNamespace("Microsoft.Test")
namespace Microsoft.Test;
enum Features {
@Azure.ResourceManager.Legacy.featureOptions(#{featureName: "Common", fileName: "shared", description: "The data for common features"})
@Azure.ResourceManager.featureFileOptions(#{featureName: "Common", fileName: "shared", description: "The data for common features"})
Common: "Common",
@Azure.ResourceManager.Legacy.featureOptions(#{featureName: "FeatureA", fileName: "featureA", description: "The data for feature A"})
@Azure.ResourceManager.featureFileOptions(#{featureName: "FeatureA", fileName: "featureA", description: "The data for feature A"})
FeatureA: "Feature A",
@Azure.ResourceManager.Legacy.featureOptions(#{featureName: "FeatureB", fileName: "featureB", description: "The data for feature B"})
@Azure.ResourceManager.featureFileOptions(#{featureName: "FeatureB", fileName: "featureB", description: "The data for feature B"})
FeatureB: "Feature B",
}
@secret
scalar secretString extends string;

@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
@Azure.ResourceManager.featureFile(Features.FeatureA)
model FooResource is TrackedResource<FooResourceProperties> {
...ResourceNameParameter<FooResource>;
}

@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
@Azure.ResourceManager.featureFile(Features.FeatureA)
model FooResourceProperties {
...DefaultProvisioningStateProperty;
password: secretString;
}

@Azure.ResourceManager.Legacy.feature(Features.FeatureB)
@Azure.ResourceManager.featureFile(Features.FeatureB)
model BarResource is ProxyResource<BarResourceProperties> {
...ResourceNameParameter<BarResource>;
}
@Azure.ResourceManager.Legacy.feature(Features.FeatureB)
@Azure.ResourceManager.featureFile(Features.FeatureB)
model BarResourceProperties {
...DefaultProvisioningStateProperty;
password: secretString;
}

@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
@Azure.ResourceManager.featureFile(Features.FeatureA)
@armResourceOperations
interface Foos extends Azure.ResourceManager.TrackedResourceOperations<FooResource, FooResourceProperties> {}

@Azure.ResourceManager.Legacy.feature(Features.FeatureB)
@Azure.ResourceManager.featureFile(Features.FeatureB)
@armResourceOperations
interface Bars extends Azure.ResourceManager.TrackedResourceOperations<BarResource, BarResourceProperties> {}
@@Azure.ResourceManager.Legacy.feature(Bars.get, Features.FeatureA);
@@Azure.ResourceManager.featureFile(Bars.get, Features.FeatureA);
`,
["featureA", "featureB", "shared"],
{ preset: "azure" },
Expand Down
61 changes: 61 additions & 0 deletions packages/typespec-azure-resource-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Available ruleSets:
| [`@azure-tools/typespec-azure-resource-manager/version-progression`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/version-progression) | Validate that ARM service versions all use unique dates and are declared in strictly increasing chronological order. |
| [`@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-custom-resource-no-key) | Validate that custom resource contains a key property. |
| [`@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-custom-resource-usage-discourage) | Verify the usage of @customAzureResource decorator. |
| [`@azure-tools/typespec-azure-resource-manager/arm-feature-file-usage-discourage`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-feature-file-usage-discourage) | Verify the usage of @featureFiles decorator. |
| [`@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/beyond-nesting-levels) | Tracked Resources must use 3 or fewer levels of nesting. |
| [`@azure-tools/typespec-azure-resource-manager/arm-resource-operation`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-resource-operation) | Validate ARM Resource operations. |
| [`@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-resource-delete-operation) | Check for resources that must have a delete operation. |
Expand Down Expand Up @@ -87,6 +88,9 @@ Available ruleSets:
- [`@armResourceUpdate`](#@armresourceupdate)
- [`@armVirtualResource`](#@armvirtualresource)
- [`@extensionResource`](#@extensionresource)
- [`@featureFile`](#@featurefile)
- [`@featureFileOptions`](#@featurefileoptions)
- [`@featureFiles`](#@featurefiles)
- [`@identifiers`](#@identifiers)
- [`@locationResource`](#@locationresource)
- [`@resourceBaseType`](#@resourcebasetype)
Expand Down Expand Up @@ -398,6 +402,63 @@ See more details on [different Azure Resource Manager resource type here.](https

None

#### `@featureFile`

Decorator to associate a feature file with a model, interface, or namespace

```typespec
@Azure.ResourceManager.featureFile(featureName: EnumMember)
```

##### Target

The target to associate the feature file with
`Model | Operation | Interface | Namespace`

##### Parameters

| Name | Type | Description |
| ----------- | ------------ | ---------------------------------------- |
| featureName | `EnumMember` | The feature to associate with the target |

#### `@featureFileOptions`

Decorator to define options for a specific feature file

```typespec
@Azure.ResourceManager.featureFileOptions(options: valueof Azure.ResourceManager.ArmFeatureFileOptions)
```

##### Target

The enum member that represents the feature
`EnumMember`

##### Parameters

| Name | Type | Description |
| ------- | --------------------------------------------------------- | -------------------------------- |
| options | [valueof `ArmFeatureFileOptions`](#armfeaturefileoptions) | The options for the feature file |

#### `@featureFiles`

Decorator to define a set of feature files for splitting output

```typespec
@Azure.ResourceManager.featureFiles(features: Enum)
```

##### Target

The service namespace
`Namespace`

##### Parameters

| Name | Type | Description |
| -------- | ------ | ----------------------------------- |
| features | `Enum` | The enum that contains the features |

#### `@identifiers`

This decorator is used to indicate the identifying properties of objects in the array, e.g. size
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
DecoratorContext,
DecoratorValidatorCallbacks,
Enum,
EnumMember,
EnumValue,
Interface,
Expand All @@ -17,6 +18,14 @@ export interface ResourceOperationOptions {
readonly omitTags?: boolean;
}

export interface ArmFeatureFileOptions {
readonly featureName: string;
readonly fileName: string;
readonly description: string;
readonly title?: string;
readonly termsOfService?: string;
}

/**
* Marks the operation as being a collection action
*/
Expand Down Expand Up @@ -348,6 +357,42 @@ export type ResourceBaseTypeDecorator = (
baseTypeIt: Type,
) => DecoratorValidatorCallbacks | void;

/**
* Decorator to define a set of feature files for splitting output
*
* @param target The service namespace
* @param features The enum that contains the features
*/
export type FeatureFilesDecorator = (
context: DecoratorContext,
target: Namespace,
features: Enum,
) => DecoratorValidatorCallbacks | void;

/**
* Decorator to define options for a specific feature file
*
* @param target The enum member that represents the feature
* @param options The options for the feature file
*/
export type FeatureFileOptionsDecorator = (
context: DecoratorContext,
target: EnumMember,
options: ArmFeatureFileOptions,
) => DecoratorValidatorCallbacks | void;

/**
* Decorator to associate a feature file with a model, interface, or namespace
*
* @param target The target to associate the feature file with
* @param featureName The feature to associate with the target
*/
export type FeatureFileDecorator = (
context: DecoratorContext,
target: Model | Operation | Interface | Namespace,
featureName: EnumMember,
) => DecoratorValidatorCallbacks | void;

export type AzureResourceManagerDecorators = {
armResourceCollectionAction: ArmResourceCollectionActionDecorator;
armProviderNameValue: ArmProviderNameValueDecorator;
Expand All @@ -372,4 +417,7 @@ export type AzureResourceManagerDecorators = {
armCommonTypesVersion: ArmCommonTypesVersionDecorator;
armVirtualResource: ArmVirtualResourceDecorator;
resourceBaseType: ResourceBaseTypeDecorator;
featureFiles: FeatureFilesDecorator;
featureFileOptions: FeatureFileOptionsDecorator;
featureFile: FeatureFileDecorator;
};
41 changes: 41 additions & 0 deletions packages/typespec-azure-resource-manager/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,44 @@ extern dec resourceBaseType(
* ```
*/
extern dec identifiers(entity: ModelProperty | Array<unknown>, properties: valueof string[]);

/**
* Options for defining a feature file and its associated output
*/
model ArmFeatureFileOptions {
/** The feature name */
featureName: string;

/** The associated file name for the features */
fileName: string;

/** The feature description in Swagger */
description: string;

/** The feature title in Swagger */
title?: string;

/** The feature terms of service in Swagger */
termsOfService?: string;
}

/**
* Decorator to define a set of feature files for splitting output
* @param target The service namespace
* @param features The enum that contains the features
*/
extern dec featureFiles(target: Namespace, features: Enum);

/**
* Decorator to define options for a specific feature file
* @param target The enum member that represents the feature
* @param options The options for the feature file
*/
extern dec featureFileOptions(target: EnumMember, options: valueof ArmFeatureFileOptions);

/**
* Decorator to associate a feature file with a model, interface, or namespace
* @param target The target to associate the feature file with
* @param featureName The feature to associate with the target
*/
extern dec featureFile(target: Model | Operation | Interface | Namespace, featureName: EnumMember);
2 changes: 2 additions & 0 deletions packages/typespec-azure-resource-manager/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { armCommonTypesVersionRule } from "./rules/arm-common-types-version.js";
import { armCustomResourceNoKey } from "./rules/arm-custom-resource-no-key.js";
import { armCustomResourceUsageDiscourage } from "./rules/arm-custom-resource-usage-discourage.js";
import { armDeleteResponseCodesRule } from "./rules/arm-delete-response-codes.js";
import { armFeatureFileUsageDiscourage } from "./rules/arm-feature-file-usage-discourage.js";
import { armNoPathCasingConflictsRule } from "./rules/arm-no-path-casing-conflicts.js";
import { armNoRecordRule } from "./rules/arm-no-record.js";
import { armPostResponseCodesRule } from "./rules/arm-post-response-codes.js";
Expand Down Expand Up @@ -61,6 +62,7 @@ const rules = [
versionProgressionRule,
armCustomResourceNoKey,
armCustomResourceUsageDiscourage,
armFeatureFileUsageDiscourage,
beyondNestingRule,
coreOperationsRule,
deleteOperationMissingRule,
Expand Down
20 changes: 20 additions & 0 deletions packages/typespec-azure-resource-manager/src/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import {
ArmResourceOperationsDecorator,
ArmVirtualResourceDecorator,
ExtensionResourceDecorator,
FeatureFileDecorator,
FeatureFileOptionsDecorator,
FeatureFilesDecorator,
IdentifiersDecorator,
LocationResourceDecorator,
ResourceBaseTypeDecorator,
Expand Down Expand Up @@ -1519,6 +1522,10 @@ export const [getResourceFeatureSet, setResourceFeatureSet] = useStateMap<
Map<string, ArmFeatureOptions>
>(ArmStateKeys.armFeatureSet);

export const [getFeatureFileSet, setFeatureFileSet] = useStateMap<Namespace, boolean>(
ArmStateKeys.armFeatureFileSet,
);

export const [getResourceFeatureOptions, setResourceFeatureOptions] = useStateMap<
EnumMember,
ArmFeatureOptions
Expand Down Expand Up @@ -1659,3 +1666,16 @@ export const $featureOptions: FeatureOptionsDecorator = (
) => {
setResourceFeatureOptions(context.program, entity, options);
};

// New Azure.ResourceManager namespace decorators
export const $featureFile: FeatureFileDecorator = $feature as unknown as FeatureFileDecorator;
export const $featureFiles: FeatureFilesDecorator = (
context: DecoratorContext,
entity: Namespace,
features: Enum,
) => {
setFeatureFileSet(context.program, entity, true);
$features(context, entity, features);
};
export const $featureFileOptions: FeatureFileOptionsDecorator =
$featureOptions as unknown as FeatureFileOptionsDecorator;
Loading
Loading