Skip to content

Commit

Permalink
URI template support in RLC (#2814)
Browse files Browse the repository at this point in the history
* Update the routes cases

* Update the UTs

* Remove changes

* Update the wrap type

* Update the wordings

* Update the code for generated code

* Regenerate the cases

* Export the helper

* Update the cadl-ranch cases

* update the change

* Regen all integration tests

* Remove useless code

* Update to the latest

* Fix build issue

* Generate backward compatible code in  compatible mode

* Update the integration cases

* Refactor the code for serialize kind and wrapper type

* Update the description for parameter

* Refine the parameter build function name and description

* Update the integration test

* fix ci

* fix rlc unit test

* Add warning for un-supported-format-cases

* Refactor the code to enum

* Update the description for legacy code

* Update the description for legacy code

* Revert change in autorest repo

* Refactor the rlc-common cases

* Update packages/autorest.typescript/src/restLevelClient/transforms/transformParameterTypes.ts

* Refactor the code with correct types

* Remove the useless codes

* Update the comments and refresh smoke testing

* Fix the UT issues

* Fix the cadl-ranch integration issues

* Update the documents

* fix ci git tree

* Update operations.spec.ts

* Fix the default value in UT mdular

* Regen the integration with new function name

* Update the integrations and routes

* Add test cases for query continuation cases

* Update the descriptions

* Update the compatibilityMode guide

* Update the ci failures

* Update the api view files

* Format files

* Update UTs in RLC

* Fix the smoke testing

* Remove the build helper for explode and styles

* Refactor the code a little

* Update the descriptions for parameters

* Update the readme with new option

* Fix the UTs and smoke testings

* Format rlc common codes

* update the collection format

* Format files

* Update the description for tsv format

---------

Co-authored-by: Jiao Di (MSFT) <[email protected]>
Co-authored-by: kazrael2119 <[email protected]>
  • Loading branch information
3 people authored Nov 13, 2024
1 parent 4ca2bb3 commit c216584
Show file tree
Hide file tree
Showing 33 changed files with 1,258 additions and 349 deletions.
6 changes: 1 addition & 5 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,7 @@
"name": "[TypeSpec] Debug generate integration code",
"request": "launch",
"cwd": "${workspaceFolder}/packages/typespec-ts",
"runtimeArgs": [
"node",
"./test/commands/gen-cadl-ranch.js",
"--tag=modular"
],
"runtimeArgs": ["tsx", "./test/commands/gen-cadl-ranch.js", "--tag=rlc"],
"runtimeExecutable": "npx",
"skipFiles": ["<node_internals>/**"],
"type": "pwa-node",
Expand Down
4 changes: 4 additions & 0 deletions packages/rlc-common/src/buildClientDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as path from "path";

import {
buildMethodDefinitions,
getGeneratedWrapperTypes,
getPathParamDefinitions
} from "./helpers/operationHelpers.js";
import { PathMetadata, Paths, RLCModel } from "./interfaces.js";
Expand Down Expand Up @@ -168,6 +169,9 @@ function getPathFirstRoutesInterfaceDefinition(
sourcefile
);
const pathParams = paths[key].pathParameters;
getGeneratedWrapperTypes(pathParams).forEach((p) =>
options.importedParameters.add(p.name ?? p.type)
);
signatures.push({
docs: [
`Resource for '${key
Expand Down
2 changes: 1 addition & 1 deletion packages/rlc-common/src/buildObjectTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ function getPolymorphicTypeAlias(
* Builds the interface for the current object schema. If it is a polymorphic
* root node it will suffix it with Base.
*/
function getObjectInterfaceDeclaration(
export function getObjectInterfaceDeclaration(
model: RLCModel,
baseName: string,
objectSchema: ObjectSchema,
Expand Down
101 changes: 78 additions & 23 deletions packages/rlc-common/src/buildParameterTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import {
ParameterMetadata,
ParameterMetadatas,
RLCModel,
Schema
Schema,
SchemaContext
} from "./interfaces.js";
import {
getImportModuleName,
getParameterBaseName,
getParameterTypeName
} from "./helpers/nameConstructors.js";
import { getImportSpecifier } from "./helpers/importsUtil.js";
import { getObjectInterfaceDeclaration } from "./buildObjectTypes.js";
import { getGeneratedWrapperTypes } from "./helpers/operationHelpers.js";

export function buildParameterTypes(model: RLCModel) {
const project = new Project();
Expand Down Expand Up @@ -58,12 +61,14 @@ export function buildParameterTypes(model: RLCModel) {
? `${baseParameterName}RequestParameters${nameSuffix}`
: topParamName;
const queryParameterDefinitions = buildQueryParameterDefinition(
model,
parameter,
baseParameterName,
internalReferences,
i
);
const pathParameterDefinitions = buildPathParameterDefinitions(
model,
parameter,
baseParameterName,
parametersFile,
Expand Down Expand Up @@ -103,7 +108,7 @@ export function buildParameterTypes(model: RLCModel) {
parametersFile.addInterfaces([
...(bodyParameterDefinition ?? []),
...(queryParameterDefinitions ?? []),
...(pathParameterDefinitions ? [pathParameterDefinitions] : []),
...(pathParameterDefinitions ?? []),
...(headerParameterDefinitions ? [headerParameterDefinitions] : []),
...(contentTypeParameterDefinition
? [contentTypeParameterDefinition]
Expand Down Expand Up @@ -177,6 +182,7 @@ export function buildParameterTypes(model: RLCModel) {
}

function buildQueryParameterDefinition(
model: RLCModel,
parameters: ParameterMetadatas,
baseName: string,
internalReferences: Set<string>,
Expand All @@ -198,6 +204,18 @@ function buildQueryParameterDefinition(
const propertiesDefinition = queryParameters.map((qp) =>
getPropertyFromSchema(qp.param)
);
// Get wrapper types for query parameters
const wrapperTypesDefinition = getGeneratedWrapperTypes(queryParameters).map(
(wrapObj) => {
return getObjectInterfaceDeclaration(
model,
wrapObj.name,
wrapObj,
[SchemaContext.Input],
new Set<string>()
);
}
);

const hasRequiredParameters = propertiesDefinition.some(
(p) => !p.hasQuestionToken
Expand Down Expand Up @@ -227,7 +245,7 @@ function buildQueryParameterDefinition(
// Mark the queryParameter interface for importing
internalReferences.add(queryParameterInterfaceName);

return [propertiesInterface, parameterInterface];
return [...wrapperTypesDefinition, propertiesInterface, parameterInterface];
}

function getPropertyFromSchema(schema: Schema): PropertySignatureStructure {
Expand All @@ -242,42 +260,79 @@ function getPropertyFromSchema(schema: Schema): PropertySignatureStructure {
}

function buildPathParameterDefinitions(
model: RLCModel,
parameters: ParameterMetadatas,
baseName: string,
parametersFile: SourceFile,
internalReferences: Set<string>,
requestIndex: number
): InterfaceDeclarationStructure | undefined {
): InterfaceDeclarationStructure[] | undefined {
const pathParameters = (parameters.parameters || []).filter(
(p) => p.type === "path"
);
if (!pathParameters.length) {
return undefined;
}
const allDefinitions: InterfaceDeclarationStructure[] = [];

buildClientPathParameters();
buildMethodWrapParameters();
return allDefinitions;
function buildClientPathParameters() {
// we only have client-level path parameters if the source is from swagger
if (model.options?.sourceFrom === "TypeSpec") {
return;
}
const clientPathParams = pathParameters.length > 0 ? pathParameters : [];
const nameSuffix = requestIndex > 0 ? `${requestIndex}` : "";
const pathParameterInterfaceName = `${baseName}PathParam${nameSuffix}`;

const nameSuffix = requestIndex > 0 ? `${requestIndex}` : "";
const pathParameterInterfaceName = `${baseName}PathParam${nameSuffix}`;
const pathInterface = getPathInterfaceDefinition(
clientPathParams,
baseName
);

const pathInterface = getPathInterfaceDefinition(pathParameters, baseName);
if (pathInterface) {
parametersFile.addInterface(pathInterface);
}

if (pathInterface) {
parametersFile.addInterface(pathInterface);
}
internalReferences.add(pathParameterInterfaceName);

internalReferences.add(pathParameterInterfaceName);
allDefinitions.push({
isExported: true,
kind: StructureKind.Interface,
name: pathParameterInterfaceName,
properties: [
{
name: "pathParameters",
type: `${baseName}PathParameters`,
kind: StructureKind.PropertySignature
}
]
});
}

return {
isExported: true,
kind: StructureKind.Interface,
name: pathParameterInterfaceName,
properties: [
{
name: "pathParameters",
type: `${baseName}PathParameters`,
kind: StructureKind.PropertySignature
}
]
};
function buildMethodWrapParameters() {
if (model.options?.sourceFrom === "Swagger") {
return;
}
// we only have method-level path parameters if the source is from typespec
const methodPathParams = pathParameters.length > 0 ? pathParameters : [];

// we only need to build the wrapper types if the path parameters are objects
const wrapperTypesDefinition = getGeneratedWrapperTypes(
methodPathParams
).map((wrap) => {
return getObjectInterfaceDeclaration(
model,
wrap.name,
wrap,
[SchemaContext.Input],
new Set<string>()
);
});
allDefinitions.push(...wrapperTypesDefinition);
}
}

function getPathInterfaceDefinition(
Expand Down
26 changes: 26 additions & 0 deletions packages/rlc-common/src/helpers/operationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
import {
Methods,
ObjectSchema,
ParameterMetadata,
PathParameter,
RLCModel,
Schema,
SchemaContext
} from "../interfaces.js";
import { NameType, normalizeName, pascalCase } from "./nameUtils.js";
Expand Down Expand Up @@ -121,3 +123,27 @@ function hasSchemaContextObject(model: RLCModel, schemaUsage: SchemaContext[]) {

return objectSchemas.length > 0;
}

export function getGeneratedWrapperTypes(
params: ParameterMetadata[] | PathParameter[]
): Schema[] {
const wrapperTypes = params
.map((qp) =>
isParameterMetadata(qp) ? qp.param.wrapperType : qp.wrapperType
)
.filter((v) => v !== undefined);
const wrapperFromObjects = wrapperTypes.filter(
(wrap) => wrap.type === "object"
);
const wrapperFromUnions = wrapperTypes
.filter((wrap) => wrap.type === "union")
.flatMap((wrapperType) => wrapperType?.enum ?? [])
.filter((v) => v.type === "object");
return [...wrapperFromUnions, ...wrapperFromObjects];
}

function isParameterMetadata(
param: ParameterMetadata | PathParameter
): param is ParameterMetadata {
return (param as any).param !== undefined;
}
9 changes: 8 additions & 1 deletion packages/rlc-common/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export type PathParameter = {
type: string;
description?: string;
value?: string | number | boolean;
wrapperType?: Schema;
};

export interface OperationHelperDetail {
Expand Down Expand Up @@ -249,6 +250,7 @@ export interface RLCOptions {
experimentalExtensibleEnums?: boolean;
clearOutputFolder?: boolean;
ignorePropertyNameNormalize?: boolean;
compatibilityQueryMultiFormat?: boolean;
}

export interface ServiceInfo {
Expand Down Expand Up @@ -361,7 +363,12 @@ export interface ParameterBodySchema extends Schema {
export interface ParameterMetadata {
type: "query" | "path" | "header";
name: string;
param: Schema;
param: ParameterSchema;
}

export interface ParameterSchema extends Schema {
// the detailed wrapper type for the parameter and codegen needs to build this type directly
wrapperType?: Schema;
}

export interface OperationResponse {
Expand Down
Loading

0 comments on commit c216584

Please sign in to comment.