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 namespace as target for @access decorator #1305

Merged
merged 10 commits into from
Aug 8, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Add Namespace as target for @access decorator
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ toc_max_heading_level: 3
### `@access` {#@Azure.ClientGenerator.Core.access}

Set explicit access for operations, models and enums.
When setting access for namespaces,
the access info will be propagated to the models defined in the namespace.
If the model has an access override, the model override takes precedence.
When setting access for models,
the access info wll not be propagated to models' properties, base models or sub models.
When setting access for an operation,
Expand All @@ -28,7 +31,7 @@ the access result is undefined.

#### Target

`Model | Operation | Enum | Union`
`Model | Operation | Enum | Union | Namespace`

#### Parameters

Expand Down
5 changes: 4 additions & 1 deletion packages/typespec-client-generator-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ npm install @azure-tools/typespec-client-generator-core
#### `@access`

Set explicit access for operations, models and enums.
When setting access for namespaces,
the access info will be propagated to the models defined in the namespace.
If the model has an access override, the model override takes precedence.
When setting access for models,
the access info wll not be propagated to models' properties, base models or sub models.
When setting access for an operation,
Expand All @@ -46,7 +49,7 @@ the access result is undefined.

##### Target

`Model | Operation | Enum | Union`
`Model | Operation | Enum | Union | Namespace`

##### Parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ export type UsageDecorator = (

/**
* Set explicit access for operations, models and enums.
* When setting access for namespaces,
* the access info will be propagated to the models defined in the namespace.
* If the model has an access override, the model override takes precedence.
* When setting access for models,
* the access info wll not be propagated to models' properties, base models or sub models.
* When setting access for an operation,
Expand Down Expand Up @@ -407,7 +410,7 @@ export type UsageDecorator = (
*/
export type AccessDecorator = (
context: DecoratorContext,
target: Model | Operation | Enum | Union,
target: Model | Operation | Enum | Union | Namespace,
value: EnumMember,
scope?: string
) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ enum Access {

/**
* Set explicit access for operations, models and enums.
* When setting access for namespaces,
* the access info will be propagated to the models defined in the namespace.
* If the model has an access override, the model override takes precedence.
* When setting access for models,
* the access info wll not be propagated to models' properties, base models or sub models.
* When setting access for an operation,
Expand Down Expand Up @@ -398,7 +401,7 @@ enum Access {
* ```
*/
extern dec access(
target: Model | Operation | Enum | Union,
target: Model | Operation | Enum | Union | Namespace,
value: EnumMember,
scope?: valueof string
);
Expand Down
22 changes: 13 additions & 9 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ const accessKey = createStateSymbol("access");

export const $access: AccessDecorator = (
context: DecoratorContext,
entity: Model | Enum | Operation | Union,
entity: Model | Enum | Operation | Union | Namespace,
value: EnumMember,
scope?: LanguageScopes
) => {
Expand All @@ -951,16 +951,19 @@ export const $access: AccessDecorator = (

export function getAccessOverride(
context: TCGCContext,
entity: Model | Enum | Operation | Union
entity: Model | Enum | Operation | Union | Namespace
): AccessFlags | undefined {
return getScopedDecoratorData(context, accessKey, entity);
const accessOverride = getScopedDecoratorData(context, accessKey, entity);

if (!accessOverride && entity.namespace) {
return getAccessOverride(context, entity.namespace);
}

return accessOverride;
}

export function getAccess(
context: TCGCContext,
entity: Model | Enum | Operation | Union
): AccessFlags {
const override = getScopedDecoratorData(context, accessKey, entity);
export function getAccess(context: TCGCContext, entity: Model | Enum | Operation | Union) {
const override = getAccessOverride(context, entity);
if (override || entity.kind === "Operation") {
return override || "public";
joheredi marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -970,12 +973,13 @@ export function getAccess(
return getSdkModel(context, entity).access;
case "Enum":
return getSdkEnum(context, entity).access;
case "Union":
case "Union": {
const type = getSdkUnion(context, entity);
if (type.kind === "enum" || type.kind === "model") {
return type.access;
}
return "public";
}
}
}

Expand Down
113 changes: 104 additions & 9 deletions packages/typespec-client-generator-core/test/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Enum,
Interface,
Model,
ModelProperty,
Namespace,
Operation,
ignoreDiagnostics,
Expand Down Expand Up @@ -1690,16 +1691,110 @@ describe("typespec-client-generator-core: decorators", () => {
});

describe("@access", () => {
it("mark an operation as internal", async () => {
const { test } = (await runner.compile(`
@service({title: "Test Service"}) namespace TestService;
@test
@access(Access.internal)
op test(): void;
`)) as { test: Operation };
describe("namespace access override", () => {
it("should inherit access from parent namespace", async () => {
const { Test } = (await runner.compile(`
@access(Access.public)
@service({title: "Test Service"}) namespace TestService;
@test
model Test {
prop: string;
}
`)) as { Test: Operation };

const actual = getAccess(runner.context, test);
strictEqual(actual, "internal");
const actual = getAccess(runner.context, Test);
strictEqual(actual, "public");
});

it("should tag anonymous models with default access", async () => {
const { Test, prop } = (await runner.compile(`
@access(Access.public)
@service({title: "Test Service"}) namespace TestService;
@test
model Test {
@test
prop: {
foo: string;
}
}
`)) as { Test: Operation; prop: ModelProperty };

const actual = getAccess(runner.context, Test);
const actualAnonymous = getAccess(runner.context, prop.type as Model);
strictEqual(actual, "public");
strictEqual(actualAnonymous, "public");
});

it("should tag as internal anonymous models with default access", async () => {
const { Test, prop } = (await runner.compile(`
@access(Access.internal)
@service({title: "Test Service"}) namespace TestService;
@test
model Test {
@test
prop: {
foo: string;
}
}
`)) as { Test: Operation; prop: ModelProperty };

const actual = getAccess(runner.context, Test);
const actualAnonymous = getAccess(runner.context, prop.type as Model);
strictEqual(actual, "internal");
strictEqual(actualAnonymous, "internal");
});

it("should honor the granular override over the namespace one", async () => {
const { Test } = (await runner.compile(`
@access(Access.public)
@service({title: "Test Service"}) namespace TestService;
@access(Access.internal)
@test
model Test {
prop: string;
}
`)) as { Test: Operation };

const actual = getAccess(runner.context, Test);
strictEqual(actual, "internal");
});

it("locally mark an operation as internal", async () => {
const { test } = (await runner.compile(`
@access(Access.public)
@service({title: "Test Service"}) namespace TestService;
@test
@access(Access.internal)
op test(): void;
`)) as { test: Operation };

const actual = getAccess(runner.context, test);
strictEqual(actual, "internal");
});

it("locally mark an operation as public", async () => {
const { test } = (await runner.compile(`
@access(Access.public)
@service({title: "Test Service"}) namespace TestService;
@test
op test(): void;
`)) as { test: Operation };

const actual = getAccess(runner.context, test);
strictEqual(actual, "public");
});

it("mark an operation as internal through the namespace", async () => {
const { test } = (await runner.compile(`
@access(Access.internal)
@service({title: "Test Service"}) namespace TestService;
@test
op test(): void;
`)) as { test: Operation };

const actual = getAccess(runner.context, test);
strictEqual(actual, "internal");
});
});

it("default calculated value of operation is undefined, default value of calculated model is undefined", async () => {
Expand Down
Loading