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

x-ms-identifiers alternatives #1983

Merged
merged 21 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8e8eac8
introduce identifiers decorator and recognize key as identifiers
AlitzelMendez Dec 12, 2024
5a3b5d6
summary of changes
AlitzelMendez Dec 12, 2024
9e3e0d8
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Dec 12, 2024
9be6ae0
avoid adding x-ms-identifiers unexpected
AlitzelMendez Dec 16, 2024
d7cce7a
update examples with x-ms-identifiers comming from keys
AlitzelMendez Dec 17, 2024
131d8ef
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Dec 17, 2024
28c371d
missing identifiers on openapi.json files
AlitzelMendez Dec 17, 2024
db897fd
Merge branch 'x-ms-identifiers-alternatives' of https://github.com/Al…
AlitzelMendez Dec 17, 2024
f37457e
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Dec 26, 2024
37fcd7a
Feedback: id/name are default keys so no need to show them or trigger…
AlitzelMendez Dec 26, 2024
170f819
Update packages/typespec-azure-resource-manager/src/lib.ts
AlitzelMendez Dec 26, 2024
ed89e10
Add more documentation
AlitzelMendez Dec 26, 2024
8a6684d
fix test & more docs
AlitzelMendez Dec 26, 2024
4f358d2
Merge branch 'x-ms-identifiers-alternatives' of https://github.com/Al…
AlitzelMendez Dec 26, 2024
d8ade8c
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Jan 3, 2025
c3086cc
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Jan 6, 2025
c580cf2
Documentation
AlitzelMendez Jan 7, 2025
3d6a152
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Jan 8, 2025
92ffae6
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Jan 9, 2025
cd50b98
fix typo
AlitzelMendez Jan 10, 2025
173cef7
Merge branch 'main' into x-ms-identifiers-alternatives
AlitzelMendez Jan 10, 2025
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,8 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-autorest"
- "@azure-tools/typespec-azure-resource-manager"
---

Use the @identifiers decorator to identify and utilize identifiers for x-ms-identifiers. Additionally, use the @key decorator to identify identifiers.
16 changes: 15 additions & 1 deletion packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "@azure-tools/typespec-azure-core";
import {
getArmCommonTypeOpenAPIRef,
getArmIdentifiers,
isArmCommonType,
isAzureResource,
isConditionallyFlattened,
Expand Down Expand Up @@ -1833,6 +1834,10 @@ export async function getOpenAPIForService(
);
}

function ifArmIdentifiersDefault(armIdentifiers: string[]) {
return armIdentifiers.every((identifier) => identifier === "id" || identifier === "name");
}

function getSchemaForUnionVariant(
variant: UnionVariant,
schemaContext: SchemaContext,
Expand Down Expand Up @@ -2373,9 +2378,18 @@ export async function getOpenAPIForService(
visibility: context.visibility | Visibility.Item,
}),
};
if (!ifArrayItemContainsIdentifier(program, typespecType as any)) {

const armIdentifiers = getArmIdentifiers(program, typespecType);
markcowl marked this conversation as resolved.
Show resolved Hide resolved
if (
armIdentifiers !== undefined &&
armIdentifiers.length > 0 &&
!ifArmIdentifiersDefault(armIdentifiers)
) {
array["x-ms-identifiers"] = armIdentifiers;
} else if (!ifArrayItemContainsIdentifier(program, typespecType as any)) {
array["x-ms-identifiers"] = [];
}

return applyIntrinsicDecorators(typespecType, array);
}
return undefined;
Expand Down
174 changes: 174 additions & 0 deletions packages/typespec-autorest/test/openapi-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,180 @@ describe("typespec-autorest: extension decorator", () => {
});
});

describe("typespec-azure: identifiers decorator", () => {
AlitzelMendez marked this conversation as resolved.
Show resolved Hide resolved
it("ignores name/id keys for x-ms-identifiers", async () => {
const oapi = await openApiFor(
`
model Pet {
@key
name: string;
@key
id: int32;
}
model PetList {
value: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.value["x-ms-identifiers"], undefined);
});
it("uses identifiers decorator for properties", async () => {
const oapi = await openApiFor(
`
model Pet {
name: string;
age: int32;
}
model PetList {
@identifiers(["age"])
value: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.value["x-ms-identifiers"], ["age"]);
});
it("identifies keys correctly as x-ms-identifiers", async () => {
const oapi = await openApiFor(
`
model Pet {
name: string;
@key
age: int32;
}
model PetList {
value: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.value["x-ms-identifiers"], ["age"]);
});
it("prioritizes identifiers decorator over keys", async () => {
const oapi = await openApiFor(
`
model Pet {
name: string;
@key
age: int32;
}
model PetList {
@identifiers([])
value: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.value["x-ms-identifiers"], []);
});
it("supports multiple identifiers", async () => {
const oapi = await openApiFor(
`
model Pet {
name: string;
age: int32;
}
model PetList {
@identifiers(["name", "age"])
value: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.value["x-ms-identifiers"], ["name", "age"]);
});
it("supports inner properties in identifiers decorator", async () => {
const oapi = await openApiFor(
`
model Pet {
dogs: Dog;
}

model Dog {
breed: string;
}

model PetList {
@identifiers(["dogs/breed"])
pets: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.pets["x-ms-identifiers"], ["dogs/breed"]);
});
it("supports inner properties for keys", async () => {
const oapi = await openApiFor(
`
model Pet {
dogs: Dog;
cats: Cat;
}

model Dog {
@key
breed: string;
}

model Cat
{
features: Features;
}

model Features {
@key
color:string;
size:int32;
}

model PetList {
pets: Pet[]
}

@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.pets["x-ms-identifiers"], [
"dogs/breed",
"cats/features/color",
]);
});
it("supports multiple keys", async () => {
const oapi = await openApiFor(
`
model Pet {
@key
name: string;
@key
age: int32;
}
model PetList {
pets: Pet[]
}
@route("/Pets")
@get op list(): PetList;
`,
);
ok(oapi.paths["/Pets"].get);
deepStrictEqual(oapi.definitions.PetList.properties.pets["x-ms-identifiers"], ["name", "age"]);
});
});

describe("typespec-autorest: multipart formData", () => {
it("expands model into formData parameters", async () => {
const oapi = await openApiFor(`
Expand Down
29 changes: 29 additions & 0 deletions packages/typespec-azure-resource-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Available ruleSets:
- [`@armResourceUpdate`](#@armresourceupdate)
- [`@armVirtualResource`](#@armvirtualresource)
- [`@extensionResource`](#@extensionresource)
- [`@identifiers`](#@identifiers)
- [`@locationResource`](#@locationresource)
- [`@resourceBaseType`](#@resourcebasetype)
- [`@resourceGroupResource`](#@resourcegroupresource)
Expand Down Expand Up @@ -353,6 +354,34 @@ See more details on [different Azure Resource Manager resource type here.](https

None

#### `@identifiers`

AlitzelMendez marked this conversation as resolved.
Show resolved Hide resolved
This decorator is used to indicate the identifying properties of objects in the array, e.g. size
The properties that are used as identifiers for the object needs to be provided as a list of strings.

```typespec
@Azure.ResourceManager.identifiers(properties: string[])
```

##### Target

`ModelProperty`

##### Parameters

| Name | Type | Description |
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
| properties | `string[]` | The list of properties that are used as identifiers for the object. This needs to be provided as a list of strings. |

##### Examples

```typespec
model Pet {
@identifiers(["size"])
dog: Dog;
}
```

#### `@locationResource`

`@locationResource` marks an Azure Resource Manager resource model as a location based resource.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type {
import {
DecoratorContext,
EnumMember,
EnumValue,
Interface,
Model,
ModelProperty,
Namespace,
Operation,
Type,
Expand Down Expand Up @@ -266,6 +267,12 @@ export type ResourceBaseTypeDecorator = (
baseType: Type,
) => void;

export type IdentifiersDecorator = (
context: DecoratorContext,
target: ModelProperty,
properties: string[],
) => void;

export type AzureResourceManagerDecorators = {
armResourceCollectionAction: ArmResourceCollectionActionDecorator;
armProviderNameValue: ArmProviderNameValueDecorator;
Expand All @@ -288,6 +295,7 @@ export type AzureResourceManagerDecorators = {
armCommonTypesVersion: ArmCommonTypesVersionDecorator;
armVirtualResource: ArmVirtualResourceDecorator;
resourceBaseType: ResourceBaseTypeDecorator;
identifiers: IdentifiersDecorator;
};

export type AzureResourceManagerLegacyDecorators = {
Expand Down
16 changes: 16 additions & 0 deletions packages/typespec-azure-resource-manager/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,19 @@ extern dec resourceBaseType(
target: Model,
baseType: "Tenant" | "Subscription" | "ResourceGroup" | "Location" | "Extension"
);

/**
* This decorator is used to indicate the identifying properties of objects in the array, e.g. size
* The properties that are used as identifiers for the object needs to be provided as a list of strings.
*
* @param properties The list of properties that are used as identifiers for the object. This needs to be provided as a list of strings.
*
* @example
* ```typespec
* model Pet {
* @identifiers(["size"])
* dog: Dog;
* }
* ```
*/
extern dec identifiers(entity: ModelProperty, properties: string[]);
4 changes: 4 additions & 0 deletions packages/typespec-azure-resource-manager/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export const $lib = createTypeSpecLibrary({
messages: {
armUpdateProviderNamespace:
"The parameter to @armUpdateProviderNamespace must be an operation with a 'provider' parameter.",
armIdentifiersIncorrectEntity:
"The @identifiers decorator must be applied to a property that is an array of objects",
armIdentifiersProperties:
"The @identifiers decorator expects a parameter that is an array of strings or an empty array.",
},
},
"arm-resource-circular-ancestry": {
Expand Down
Loading
Loading