Skip to content

Commit

Permalink
[tcgc] create endpoint type (#474)
Browse files Browse the repository at this point in the history
  • Loading branch information
iscai-msft committed Mar 22, 2024
1 parent 3b30c1e commit 63bbe2f
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 152 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/endpoint_design-2024-2-21-17-38-14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

create SdkEndpointType to encapsulate templating and url
12 changes: 9 additions & 3 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ export interface SdkClientType<TServiceOperation extends SdkServiceOperation> {
methods: SdkMethod<TServiceOperation>[];
apiVersions: string[];
nameSpace: string; // fully qualified
endpoint: string;
hasParameterizedEndpoint: boolean;
arm: boolean;
}

Expand Down Expand Up @@ -92,7 +90,8 @@ export type SdkType =
| SdkConstantType
| SdkUnionType
| SdkModelType
| SdkCredentialType;
| SdkCredentialType
| SdkEndpointType;

export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
Expand Down Expand Up @@ -291,6 +290,12 @@ export interface SdkCredentialType extends SdkTypeBase {
scheme: HttpAuth;
}

export interface SdkEndpointType extends SdkTypeBase {
kind: "endpoint";
serverUrl?: string;
templateArguments: SdkPathParameter[];
}

export interface SdkModelPropertyTypeBase {
__raw?: ModelProperty;
type: SdkType;
Expand All @@ -315,6 +320,7 @@ export interface SdkEndpointParameter extends SdkModelPropertyTypeBase {
urlEncode: boolean;
onClient: true;
serializedName?: string;
type: SdkEndpointType;
}

export interface SdkCredentialParameter extends SdkModelPropertyTypeBase {
Expand Down
6 changes: 6 additions & 0 deletions packages/typespec-client-generator-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export const $lib = createTypeSpecLibrary({
default: paramMessage`Multiple services found in definition. Only one service is supported, so we will choose the first one ${"service"}`,
},
},
"server-param-not-path": {
severity: "error",
messages: {
default: paramMessage`Template argument ${"templateArgumentName"} is not a path parameter, it is a ${"templateArgumentType"}. It has to be a path.`,
},
},
},
});

Expand Down
129 changes: 5 additions & 124 deletions packages/typespec-client-generator-core/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
getService,
isErrorModel,
} from "@typespec/compiler";
import { HttpOperation, getHeaderFieldName, getServers, isContentTypeHeader } from "@typespec/http";
import { HttpOperation, getHeaderFieldName, isContentTypeHeader } from "@typespec/http";
import { resolveVersions } from "@typespec/versioning";
import {
getAccess,
Expand All @@ -21,7 +21,6 @@ import {
SdkClient,
SdkClientType,
SdkContext,
SdkEndpointParameter,
SdkEnumType,
SdkHeaderParameter,
SdkHttpOperation,
Expand Down Expand Up @@ -54,10 +53,8 @@ import {
getClientNamespaceStringHelper,
getDocHelper,
getHashForType,
getSdkTypeBaseHelper,
isAcceptHeader,
isNullable,
updateWithApiVersionInformation,
} from "./internal-utils.js";
import {
getClientNamespaceString,
Expand All @@ -72,6 +69,7 @@ import {
getAllModelsWithDiagnostics,
getClientTypeWithDiagnostics,
getSdkCredentialParameter,
getSdkEndpointParameter,
getSdkModelPropertyType,
} from "./types.js";

Expand Down Expand Up @@ -600,116 +598,6 @@ function getSdkServiceMethod<
return getSdkBasicServiceMethod<TOptions, TServiceOperation>(context, operation);
}

function getDefaultSdkEndpointParameter<
TOptions extends object,
TServiceOperation extends SdkServiceOperation,
>(
context: SdkContext<TOptions, TServiceOperation>,
client: SdkClient,
serverUrl?: string
): SdkEndpointParameter[] {
let type: SdkType;
if (serverUrl) {
// this is a fixed endpoint so we model it as a constant type
type = {
...getSdkTypeBaseHelper(context, client.service, "constant"),
value: serverUrl,
valueType: {
kind: "string",
encode: "string",
nullable: false,
},
};
} else {
// this is a parameterized endpoint
type = {
...getSdkTypeBaseHelper(context, client.service, "string"),
encode: "string",
};
}
const name = "endpoint";
return [
{
kind: "endpoint",
nameInClient: name,
name,
description: "Service host",
onClient: true,
urlEncode: false,
apiVersions: getAvailableApiVersions(context, client.type),
type: type,
optional: false,
isApiVersionParam: false,
nullable: false,
},
];
}

interface EndpointAndEndpointParameters {
endpoint: string;
properties: SdkEndpointParameter[];
hasParameterizedEndpoint: boolean;
}

function getEndpointAndEndpointParameters<
TOptions extends object,
TServiceOperation extends SdkServiceOperation,
>(
context: SdkContext<TOptions, TServiceOperation>,
client: SdkClient
): [EndpointAndEndpointParameters, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
const servers = getServers(context.program, client.service);
if (servers === undefined) {
return diagnostics.wrap({
endpoint: "",
properties: getDefaultSdkEndpointParameter<TOptions, TServiceOperation>(context, client),
hasParameterizedEndpoint: false,
});
}
if (servers.length > 1) {
return diagnostics.wrap({
endpoint: "{endpoint}",
properties: getDefaultSdkEndpointParameter<TOptions, TServiceOperation>(context, client),
hasParameterizedEndpoint: true,
});
}
if (servers[0].parameters.size === 0) {
return diagnostics.wrap({
endpoint: servers[0].url,
properties: getDefaultSdkEndpointParameter<TOptions, TServiceOperation>(
context,
client,
servers[0].url
),
hasParameterizedEndpoint: false,
});
}
const properties: SdkEndpointParameter[] = [];
for (const param of servers[0].parameters.values()) {
const sdkParam = diagnostics.pipe(
getSdkModelPropertyType(context, param, { isEndpointParam: true })
);
if (sdkParam.kind !== "path") {
throw new Error("blah");
}
properties.push({
...sdkParam,
kind: "endpoint",
urlEncode: false,
optional: false,
...updateWithApiVersionInformation(context, param),
onClient: true,
description: sdkParam.description ?? servers[0].description,
});
}
return diagnostics.wrap({
endpoint: servers[0].url,
properties,
hasParameterizedEndpoint: true,
});
}

function getClientDefaultApiVersion<
TOptions extends object,
TServiceOperation extends SdkServiceOperation,
Expand All @@ -731,10 +619,9 @@ function getSdkInitializationType<
): [SdkInitializationType, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
const credentialParam = getSdkCredentialParameter(context, client);
const endpointInfo = diagnostics.pipe(
getEndpointAndEndpointParameters<TOptions, TServiceOperation>(context, client)
);
const properties: SdkParameter[] = endpointInfo.properties;
const properties: SdkParameter[] = [
diagnostics.pipe(getSdkEndpointParameter(context, client)), // there will always be an endpoint parameter
];
if (credentialParam) {
properties.push(credentialParam);
}
Expand Down Expand Up @@ -809,10 +696,6 @@ function createSdkClientType<

// NOTE: getSdkMethods recursively calls createSdkClientType
const methods = diagnostics.pipe(getSdkMethods(context, client, baseClientType));

const endpointInfo = diagnostics.pipe(
getEndpointAndEndpointParameters<TOptions, TServiceOperation>(context, client)
);
const docWrapper = getDocHelper(context, baseClientType.type);
const sdkClientType: SdkClientType<TServiceOperation> = {
kind: "client",
Expand All @@ -825,8 +708,6 @@ function createSdkClientType<
initialization: isClient
? diagnostics.pipe(getSdkInitializationType<TOptions, TServiceOperation>(context, client)) // MUST call this after getSdkMethods has been called
: undefined,
endpoint: endpointInfo.endpoint,
hasParameterizedEndpoint: endpointInfo.hasParameterizedEndpoint,
arm: client.arm,
};
context.__clients!.push(sdkClientType);
Expand Down
76 changes: 75 additions & 1 deletion packages/typespec-client-generator-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ import {
SdkDatetimeType,
SdkDictionaryType,
SdkDurationType,
SdkEndpointParameter,
SdkEndpointType,
SdkEnumType,
SdkEnumValueType,
SdkModelPropertyType,
SdkModelType,
SdkPathParameter,
SdkTupleType,
SdkType,
SdkUnionType,
Expand Down Expand Up @@ -996,10 +999,18 @@ export function getSdkModelPropertyType(
optional: type.optional,
});
} else if (isPathParam(program, type) || options.isEndpointParam) {
// we don't url encode if the type can be assigned to url
const urlEncode = !ignoreDiagnostics(
program.checker.isTypeAssignableTo(
type.type.projectionBase ?? type.type,
program.checker.getStdType("url"),
type.type
)
);
return diagnostics.wrap({
...base,
kind: "path",
urlEncode: true,
urlEncode,
serializedName: getPathParamName(program, type),
...updateWithApiVersionInformation(context, type),
optional: false,
Expand Down Expand Up @@ -1062,6 +1073,69 @@ export function getSdkModelPropertyType(
}
}

export function getSdkEndpointParameter(
context: TCGCContext,
client: SdkClient
): [SdkEndpointParameter, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
const servers = getServers(context.program, client.service);
let type: SdkEndpointType;
let optional: boolean = false;
if (servers === undefined || servers.length > 1) {
// if there is no defined server url, or if there are more than one
// we will return a mandatory endpoint parameter in initialization
type = {
kind: "endpoint",
nullable: false,
templateArguments: [],
};
} else {
// this means we have one server
const templateArguments: SdkPathParameter[] = [];
type = {
kind: "endpoint",
nullable: false,
serverUrl: servers[0].url,
templateArguments,
};
for (const param of servers[0].parameters.values()) {
const sdkParam = diagnostics.pipe(
getSdkModelPropertyType(context, param, { isEndpointParam: true })
);
if (sdkParam.kind === "path") {
templateArguments.push(sdkParam);
sdkParam.description = sdkParam.description ?? servers[0].description;
sdkParam.onClient = true;
} else {
diagnostics.add(
createDiagnostic({
code: "server-param-not-path",
target: param,
format: {
templateArgumentName: sdkParam.name,
templateArgumentType: sdkParam.kind,
},
})
);
}
}
optional = !!servers[0].url.length && templateArguments.every((param) => param.optional);
}
return diagnostics.wrap({
kind: "endpoint",
type,
nameInClient: "endpoint",
name: "endpoint",
description: "Service host",
onClient: true,
urlEncode: false,
apiVersions: getAvailableApiVersions(context, client.service),
optional,
isApiVersionParam: false,
nullable: false,
});
}

function addPropertiesToModelType(
context: TCGCContext,
type: Model,
Expand Down
Loading

0 comments on commit 63bbe2f

Please sign in to comment.