http-client-java, access/usage on namespace (#4417)

1. basic logic is that if there is not access/usage decorator on model,
check the decorator on its parent
2. a cache is put so that one namespace is only checked once
(`undefined` is also included as a result) <-- it is just performance

There is a corner case that

```ts
@usage(Usage.input)
namespace L1 {
  @usage(Usage.output)
  namespace L2 {
    model M {}
  }
}
```
that in some interpretation M may be input+output. But TCGC and the
logic in PR both gives output. I guess this be OK.

Local test (as TCGC seems not able to handle my case in cadl-ranch, that
models is defined in a sub namespace)
```
@supportedBy("dpg")
@scenarioService("/client/initialization")
@access(Access.public)
@usage(Usage.output)
namespace Client.AccessTest;

// public output
model OutputModel {
  name: string;
}

// public output
model OutputModel2 {
  name: string;
}

// internal input+output
@access(Access.internal)
@usage(Usage.input | Usage.output)
model Model3 {
  name: string;
}

@access(Access.internal)
namespace InternalOperations {
// internal
  op get(): OutputModel;
}
```
This commit is contained in:
Weidong Xu 2024-09-13 17:08:37 +08:00 коммит произвёл GitHub
Родитель 40762bf852
Коммит 72478a51b4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 48 добавлений и 53 удалений

Просмотреть файл

@ -368,23 +368,24 @@ export class CodeModelBuilder {
private processModels() {
const processedSdkModels: Set<SdkModelType | SdkEnumType> = new Set();
// lambda to mark model as public
const modelAsPublic = (model: SdkModelType | SdkEnumType) => {
const schema = this.processSchemaFromSdkType(model, "");
this.trackSchemaUsage(schema, {
usage: [SchemaContext.Public],
});
};
// cache resolved value of access/usage for the namespace
// the value can be set as undefined
// it resolves the value from that namespace and its parent namespaces
const accessCache: Map<Namespace, string | undefined> = new Map();
const usageCache: Map<Namespace, SchemaContext[] | undefined> = new Map();
const sdkModels: (SdkModelType | SdkEnumType)[] = getAllModels(this.sdkContext);
// process sdk models
for (const model of sdkModels) {
if (!processedSdkModels.has(model)) {
const access = getAccess(model.__raw);
const access = getAccess(model.__raw, accessCache);
if (access === "public") {
modelAsPublic(model);
const schema = this.processSchemaFromSdkType(model, "");
this.trackSchemaUsage(schema, {
usage: [SchemaContext.Public],
});
} else if (access === "internal") {
const schema = this.processSchemaFromSdkType(model, model.name);
@ -393,7 +394,7 @@ export class CodeModelBuilder {
});
}
const usage = getUsage(model.__raw);
const usage = getUsage(model.__raw, usageCache);
if (usage) {
const schema = this.processSchemaFromSdkType(model, "");

Просмотреть файл

@ -1,14 +1,6 @@
import { ApiVersions, Parameter } from "@autorest/codemodel";
import { getOperationLink } from "@azure-tools/typespec-azure-core";
import {
SdkClient,
SdkContext,
listOperationGroups,
listOperationsInOperationGroup,
} from "@azure-tools/typespec-client-generator-core";
import { Operation } from "@typespec/compiler";
import { Version } from "@typespec/versioning";
import { getAccess } from "./type-utils.js";
export class ClientContext {
baseUri: string;
@ -58,31 +50,4 @@ export class ClientContext {
}
return addedVersions;
}
preProcessOperations(sdkContext: SdkContext, client: SdkClient) {
const operationGroups = listOperationGroups(sdkContext, client);
const operations = listOperationsInOperationGroup(sdkContext, client);
for (const operation of operations) {
const opLink = getOperationLink(sdkContext.program, operation, "polling");
if (opLink && opLink.linkedOperation) {
const access = getAccess(opLink.linkedOperation);
if (access !== "public") {
this.ignoredOperations.add(opLink.linkedOperation);
}
}
}
for (const operationGroup of operationGroups) {
const operations = listOperationsInOperationGroup(sdkContext, operationGroup);
for (const operation of operations) {
const opLink = getOperationLink(sdkContext.program, operation, "polling");
if (opLink && opLink.linkedOperation) {
const access = getAccess(opLink.linkedOperation);
if (access !== "public") {
this.ignoredOperations.add(opLink.linkedOperation);
}
}
}
}
}
}

Просмотреть файл

@ -1,4 +1,3 @@
import { SchemaContext } from "@autorest/codemodel";
import { getUnionAsEnum } from "@azure-tools/typespec-azure-core";
import {
SdkDurationType,
@ -12,6 +11,7 @@ import {
EnumMember,
IntrinsicScalarName,
Model,
Namespace,
Program,
Scalar,
StringLiteral,
@ -27,6 +27,7 @@ import {
isTypeSpecValueTypeOf,
} from "@typespec/compiler";
import { DurationSchema } from "./common/schemas/time.js";
import { SchemaContext } from "./common/schemas/usage.js";
import { getNamespace } from "./utils.js";
/** Acts as a cache for processing inputs.
@ -227,15 +228,19 @@ export function modelIs(model: Model, name: string, namespace: string): boolean
return false;
}
export function getAccess(type: Type | undefined): string | undefined {
export function getAccess(
type: Type | undefined,
accessCache: Map<Namespace, string | undefined>
): string | undefined {
if (
type &&
(type.kind === "Model" ||
type.kind === "Operation" ||
type.kind === "Enum" ||
type.kind === "Union")
type.kind === "Union" ||
type.kind === "Namespace")
) {
return getDecoratorScopedValue(type, "$access", (it) => {
let access = getDecoratorScopedValue(type, "$access", (it) => {
const value = it.args[0].value;
if ("kind" in value && value.kind === "EnumMember") {
return value.name;
@ -243,6 +248,16 @@ export function getAccess(type: Type | undefined): string | undefined {
return undefined;
}
});
if (!access && type.namespace) {
// check (parent) namespace
if (accessCache.has(type.namespace)) {
access = accessCache.get(type.namespace);
} else {
access = getAccess(type.namespace, accessCache);
accessCache.set(type.namespace, access);
}
}
return access;
} else {
return undefined;
}
@ -252,15 +267,19 @@ export function isAllValueInteger(values: number[]): boolean {
return values.every((it) => Number.isInteger(it));
}
export function getUsage(type: Type | undefined): SchemaContext[] | undefined {
export function getUsage(
type: Type | undefined,
usageCache: Map<Namespace, SchemaContext[] | undefined>
): SchemaContext[] | undefined {
if (
type &&
(type.kind === "Model" ||
type.kind === "Operation" ||
type.kind === "Enum" ||
type.kind === "Union")
type.kind === "Union" ||
type.kind === "Namespace")
) {
return getDecoratorScopedValue(type, "$usage", (it) => {
let usage = getDecoratorScopedValue(type, "$usage", (it) => {
const value = it.args[0].value;
const values: EnumMember[] = [];
const ret: SchemaContext[] = [];
@ -288,6 +307,16 @@ export function getUsage(type: Type | undefined): SchemaContext[] | undefined {
}
return ret;
});
if (!usage && type.namespace) {
// check (parent) namespace
if (usageCache.has(type.namespace)) {
usage = usageCache.get(type.namespace);
} else {
usage = getUsage(type.namespace, usageCache);
usageCache.set(type.namespace, usage);
}
}
return usage;
} else {
return undefined;
}