Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Extend `@access` to also apply to `ModelProperty`s
5 changes: 3 additions & 2 deletions packages/typespec-client-generator-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Available ruleSets:

#### `@access`

Override access for operations, models and enums.
Override access for operations, models, enums and model property.
When setting access for namespaces,
the access info will be propagated to the models and operations defined in the namespace.
If the model has an access override, the model override takes precedence.
Expand All @@ -127,14 +127,15 @@ parent models, discriminated sub models.
The override access should not be narrow than the access calculated by operation,
and different override access should not conflict with each other,
otherwise a warning will be added to diagnostics list.
Model property's access will default to public unless there is an override.

```typespec
@Azure.ClientGenerator.Core.access(value: EnumMember, scope?: valueof string)
```

##### Target

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

##### Parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export type UsageDecorator = (
) => void;

/**
* Override access for operations, models and enums.
* Override access for operations, models, enums and model property.
* When setting access for namespaces,
* the access info will be propagated to the models and operations defined in the namespace.
* If the model has an access override, the model override takes precedence.
Expand All @@ -216,6 +216,7 @@ export type UsageDecorator = (
* The override access should not be narrow than the access calculated by operation,
* and different override access should not conflict with each other,
* otherwise a warning will be added to diagnostics list.
* Model property's access will default to public unless there is an override.
*
* @param value The access info you want to set for this model or operation.
* @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
Expand Down Expand Up @@ -346,7 +347,7 @@ export type UsageDecorator = (
*/
export type AccessDecorator = (
context: DecoratorContext,
target: Model | Operation | Enum | Union | Namespace,
target: ModelProperty | Model | Operation | Enum | Union | Namespace,
value: EnumMember,
scope?: string,
) => void;
Expand Down
5 changes: 3 additions & 2 deletions packages/typespec-client-generator-core/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ enum Access {
}

/**
* Override access for operations, models and enums.
* Override access for operations, models, enums and model property.
* When setting access for namespaces,
* the access info will be propagated to the models and operations defined in the namespace.
* If the model has an access override, the model override takes precedence.
Expand All @@ -214,6 +214,7 @@ enum Access {
* The override access should not be narrow than the access calculated by operation,
* and different override access should not conflict with each other,
* otherwise a warning will be added to diagnostics list.
* Model property's access will default to public unless there is an override.
* @param value The access info you want to set for this model or operation.
* @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters.
* You can use "!" to specify negation such as "!(java, python)" or "!java, !python".
Expand Down Expand Up @@ -343,7 +344,7 @@ enum Access {
* ```
*/
extern dec access(
target: Model | Operation | Enum | Union | Namespace,
target: ModelProperty | Model | Operation | Enum | Union | Namespace,
value: EnumMember,
scope?: valueof string
);
Expand Down
13 changes: 8 additions & 5 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ const accessKey = createStateSymbol("access");

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

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

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

return accessOverride;
}

export function getAccess(context: TCGCContext, entity: Model | Enum | Operation | Union) {
export function getAccess(
context: TCGCContext,
entity: Model | Enum | Operation | Union | ModelProperty,
) {
const override = getAccessOverride(context, entity);
if (override || entity.kind === "Operation") {
if (override || entity.kind === "Operation" || entity.kind === "ModelProperty") {
return override || "public";
}

Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-client-generator-core/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ function getSdkHttpParameters(
correspondingMethodParams,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, httpOperation.operation)}.body`,
decorators: diagnostics.pipe(getTypeDecorators(context, tspBody.type)),
access: "public",
};
}
if (retval.bodyParam) {
Expand Down Expand Up @@ -311,6 +312,7 @@ function createContentTypeOrAcceptHeader(
optional: optional,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, httpOperation.operation)}.${name}`,
decorators: [],
access: "public",
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export interface SdkModelPropertyTypeBase<TType extends SdkTypeBase = SdkType>
optional: boolean;
crossLanguageDefinitionId: string;
visibility?: Visibility[];
access: AccessFlags;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-client-generator-core/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ function getEndpointTypeFromSingleServer<
apiVersions: context.getApiVersionsForType(client.__raw.type),
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.__raw.service)}.endpoint`,
decorators: [],
access: "public",
},
],
decorators: [],
Expand Down Expand Up @@ -1051,6 +1052,7 @@ function getSdkEndpointParameter<TServiceOperation extends SdkServiceOperation =
isApiVersionParam: false,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, rawClient.service)}.endpoint`,
decorators: [],
access: "public",
});
}

Expand Down
20 changes: 18 additions & 2 deletions packages/typespec-client-generator-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
} from "@typespec/http";
import { isStream } from "@typespec/streams";
import {
getAccess,
getAccessOverride,
getAlternateType,
getClientNamespace,
Expand Down Expand Up @@ -783,6 +784,7 @@ function addDiscriminatorToModelType(
flatten: false, // discriminator properties can not be flattened
crossLanguageDefinitionId: `${model.crossLanguageDefinitionId}.${name}`,
decorators: [],
access: "public",
});
model.discriminatorProperty = model.properties[0];
}
Expand Down Expand Up @@ -1217,6 +1219,7 @@ export function getSdkCredentialParameter(
isApiVersionParam: false,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.credential`,
decorators: [],
access: "public",
};
}

Expand Down Expand Up @@ -1253,6 +1256,7 @@ export function getSdkModelPropertyTypeBase(
crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type, operation),
decorators: diagnostics.pipe(getTypeDecorators(context, type)),
visibility: getSdkVisibility(context, type),
access: getAccess(context, type),
});
}

Expand Down Expand Up @@ -1494,7 +1498,7 @@ interface PropagationOptions {
isOverride?: boolean;
}

function updateUsageOrAccess(
export function updateUsageOrAccess(
context: TCGCContext,
value: UsageFlags | AccessFlags,
type?: SdkType,
Expand Down Expand Up @@ -1622,7 +1626,19 @@ function updateUsageOrAccess(
if (property.kind === "property" && isReadOnly(property) && value === UsageFlags.Input) {
continue;
}
diagnostics.pipe(updateUsageOrAccess(context, value, property.type, options));
if (typeof value === "number") {
diagnostics.pipe(updateUsageOrAccess(context, value, property.type, options));
} else {
// by default, we set property access value to parent. If there's an override though, we override.
let propertyAccess = value;
if (property.__raw) {
const propertyAccessOverride = getAccessOverride(context, property.__raw);
if (propertyAccessOverride) {
propertyAccess = propertyAccessOverride;
}
}
diagnostics.pipe(updateUsageOrAccess(context, propertyAccess, property.type, options));
}
options.ignoreSubTypeStack.pop();
}
return diagnostics.wrap(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -875,3 +875,78 @@ it("disableUsageAccessPropagationToBase true discriminator propagation", async (
strictEqual(models[4].access, "internal");
strictEqual(models[4].name, "Salmon");
});

describe("model property access", () => {
it("normal model property", async () => {
await runner.compileWithBuiltInService(`
model Test {
prop: string;
}

op test(@body body: Test): void;
`);

const models = runner.context.sdkPackage.models;
strictEqual(models[0].properties[0].access, "public");
});

it("normal parameter", async () => {
await runner.compileWithBuiltInService(`
op test(a: string): void;
`);

const parameters = runner.context.sdkPackage.clients[0].methods[0].parameters;
strictEqual(parameters[0].access, "public");
});

it("model property with override", async () => {
await runner.compileWithBuiltInService(`
model Test {
@access(Access.internal)
prop: string;
}

op test(@body body: Test): void;
`);

const models = runner.context.sdkPackage.models;
strictEqual(models[0].properties[0].access, "internal");
});

it("parameter with override", async () => {
await runner.compileWithBuiltInService(`
op test(@access(Access.internal) a: string): void;
`);

const parameters = runner.context.sdkPackage.clients[0].methods[0].parameters;
strictEqual(parameters[0].access, "internal");
});

it("model property with override propagation", async () => {
await runner.compileWithBuiltInService(`
model Foo {
@access(Access.internal)
foo: Bar;

@access(Access.internal)
baz: Baz;
}

model Bar {
prop: string;
}

model Baz {
prop: string
}

op test(@body body: Foo): Baz;
`);

const models = runner.context.sdkPackage.models;
strictEqual(models[0].properties[0].access, "internal");
strictEqual(models[0].properties[1].access, "internal");
strictEqual(models[1].access, "internal");
strictEqual(models[2].access, "public");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ toc_max_heading_level: 3

### `@access` {#@Azure.ClientGenerator.Core.access}

Override access for operations, models and enums.
Override access for operations, models, enums and model property.
When setting access for namespaces,
the access info will be propagated to the models and operations defined in the namespace.
If the model has an access override, the model override takes precedence.
Expand All @@ -21,14 +21,15 @@ parent models, discriminated sub models.
The override access should not be narrow than the access calculated by operation,
and different override access should not conflict with each other,
otherwise a warning will be added to diagnostics list.
Model property's access will default to public unless there is an override.

```typespec
@Azure.ClientGenerator.Core.access(value: EnumMember, scope?: valueof string)
```

#### Target

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

#### Parameters

Expand Down
Loading