Skip to content

Commit

Permalink
Support void type in RLC and Modular (#2521)
Browse files Browse the repository at this point in the history
* generate fleet management package with modular

* update

* Update tspconfig.yaml

* Enable fleet testing and fix the void issue

* remove fleet test

* Support void as body response and request

* Revert change for void

* Add uts for rlc

* Update the void UT message

* Refactor the code

* Update the void case

* Update the messages

---------

Co-authored-by: kazrael2119 <98569699+kazrael2119@users.noreply.github.com>
  • Loading branch information
MaryGao and kazrael2119 authored May 16, 2024
1 parent b190ea0 commit bf90528
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 11 deletions.
2 changes: 1 addition & 1 deletion packages/typespec-ts/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const libDef = {
"invalid-schema": {
severity: "error",
messages: {
default: paramMessage`Couldn't get schema for type ${"type"}`
default: paramMessage`Couldn't get schema for type ${"type"} with property ${"property"}`
}
},
"union-null": {
Expand Down
16 changes: 11 additions & 5 deletions packages/typespec-ts/src/modular/buildCodeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import {
isNullType,
getEncode,
isTemplateDeclarationOrInstance,
UsageFlags
UsageFlags,
isVoidType
} from "@typespec/compiler";
import {
getAuthentication,
Expand Down Expand Up @@ -633,9 +634,11 @@ function emitResponse(
? undefined
: getType(context, metadata.finalResult);
} else {
type = getType(context, innerResponse.body.type, {
usage: UsageFlags.Output
});
type = isVoidType(innerResponse.body.type)
? undefined
: getType(context, innerResponse.body.type, {
usage: UsageFlags.Output
});
}
}
const statusCodes: (number | "default")[] = [];
Expand Down Expand Up @@ -845,7 +848,10 @@ function emitBasicOperation(
}

let bodyParameter: any | undefined;
if (httpOperation.parameters.body === undefined) {
if (
httpOperation.parameters.body === undefined ||
isVoidType(httpOperation.parameters.body.type)
) {
bodyParameter = undefined;
} else {
bodyParameter = emitBodyParameter(context, httpOperation);
Expand Down
4 changes: 2 additions & 2 deletions packages/typespec-ts/src/transform/transformParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
SchemaContext,
ApiVersionInfo
} from "@azure-tools/rlc-common";
import { ignoreDiagnostics, Type } from "@typespec/compiler";
import { ignoreDiagnostics, isVoidType, Type } from "@typespec/compiler";
import {
getHttpOperation,
HttpOperation,
Expand Down Expand Up @@ -259,7 +259,7 @@ function transformBodyParameters(
(parameters.bodyType ?? parameters.bodyParameter?.type) && inputBodyType
? inputBodyType
: parameters.bodyType ?? parameters.bodyParameter?.type;
if (!bodyType) {
if (!bodyType || isVoidType(bodyType)) {
return;
}
return transformRequestBody(
Expand Down
4 changes: 2 additions & 2 deletions packages/typespec-ts/src/transform/transformResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
getLroLogicalResponseName,
Imports
} from "@azure-tools/rlc-common";
import { getDoc, ignoreDiagnostics } from "@typespec/compiler";
import { getDoc, ignoreDiagnostics, isVoidType } from "@typespec/compiler";
import {
getHttpOperation,
HttpOperation,
Expand Down Expand Up @@ -174,7 +174,7 @@ function transformBody(
let fromCore = false;
for (const data of response.responses) {
const body = data?.body;
if (!body) {
if (!body || isVoidType(body.type)) {
continue;
}
const hasBinaryContent = body.contentTypes.some((contentType) =>
Expand Down
5 changes: 4 additions & 1 deletion packages/typespec-ts/src/utils/modelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ export function getSchemaForType(
}
reportDiagnostic(program, {
code: "invalid-schema",
format: { type: type.kind },
format: {
type: type.kind,
property: options?.relevantProperty?.name ?? ""
},
target: type
});
return undefined;
Expand Down
90 changes: 90 additions & 0 deletions packages/typespec-ts/test/modularUnit/operations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,96 @@ import { assertEqualContent } from "../util/testUtil.js";
import { Diagnostic } from "@typespec/compiler";

describe("operations", () => {
describe("void parameter/return type", () => {
it("void request body should be omitted", async () => {
const tspContent = `
op read(@body param: void): void;
`;

const operationFiles =
await emitModularOperationsFromTypeSpec(tspContent);
assert.ok(operationFiles);
assert.equal(operationFiles?.length, 1);
await assertEqualContent(
operationFiles?.[0]?.getFullText()!,
`
import { TestingContext as Client } from "../rest/index.js";
import { StreamableMethod, operationOptionsToRequestParameters, createRestError } from "@azure-rest/core-client";
export function _readSend(context: Client, options: ReadOptionalParams = { requestOptions: {} }): StreamableMethod<Read204Response> {
return context.path("/", ).post({...operationOptionsToRequestParameters(options)}) ;
}
export async function _readDeserialize(result: Read204Response): Promise<void> {
if(result.status !== "204"){
throw createRestError(result);
}
return;
}
export async function read(context: Client, options: ReadOptionalParams = { requestOptions: {} }): Promise<void> {
const result = await _readSend(context, options);
return _readDeserialize(result);
}`
);
});

it("void response body should be omitted", async () => {
const tspContent = `
op read(): { @body _: void;};
`;

const operationFiles =
await emitModularOperationsFromTypeSpec(tspContent);
assert.ok(operationFiles);
assert.equal(operationFiles?.length, 1);
await assertEqualContent(
operationFiles?.[0]?.getFullText()!,
`
import { TestingContext as Client } from "../rest/index.js";
import { StreamableMethod, operationOptionsToRequestParameters, createRestError } from "@azure-rest/core-client";
export function _readSend(context: Client, options: ReadOptionalParams = { requestOptions: {} }): StreamableMethod<Read204Response> {
return context.path("/", ).get({...operationOptionsToRequestParameters(options)}) ;
}
export async function _readDeserialize(result: Read204Response): Promise<void> {
if(result.status !== "204"){
throw createRestError(result);
}
return;
}
export async function read(context: Client, options: ReadOptionalParams = { requestOptions: {} }): Promise<void> {
const result = await _readSend(context, options);
return _readDeserialize(result);
}`
);
});

it("should throw exception if property type as void", async () => {
try {
const tspContent = `
model Foo {
param: void;
}
op read(...Foo): {};
`;

await emitModularOperationsFromTypeSpec(tspContent);
assert.fail("Should throw diagnostic errors");
} catch (e: any) {
assert.equal(e[0]?.code, "@azure-tools/typespec-ts/invalid-schema");
assert.equal(
e[0]?.message,
"Couldn't get schema for type Intrinsic with property param"
);
assert.equal(e[0]?.target?.name, "void");
}
});
});
describe("nullable header", () => {
it("required & optional & nullable headers", async () => {
const tspContent = `
Expand Down
17 changes: 17 additions & 0 deletions packages/typespec-ts/test/unit/modelsGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ describe("Input/output model type", () => {
);
}

describe("void generation", async () => {
it("should throw exception for property with void type", async () => {
try {
const tspType = "void";
const typeScriptType = "void";
await verifyPropertyType(tspType, typeScriptType);
assert.fail("Should throw exception");
} catch (err: any) {
assert.equal(
err[0].message,
"Couldn't get schema for type Intrinsic with property prop"
);
assert.equal(err[0]?.target?.name, "void");
}
});
});

describe("null generation", async () => {
it("should generate null only", async () => {
const tspType = "null";
Expand Down
16 changes: 16 additions & 0 deletions packages/typespec-ts/test/unit/parametersGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,20 @@ describe("Parameters.ts", () => {
);
});
});

describe("void as request body", () => {
it("void request body should be emitted", async () => {
const parameters = await emitParameterFromTypeSpec(`
op read(@body param: void): void;`);
assert.ok(parameters);
await assertEqualContent(
parameters?.content!,
`
import { RequestParameters } from "@azure-rest/core-client";
export type ReadParameters = RequestParameters;
`
);
});
});
});
18 changes: 18 additions & 0 deletions packages/typespec-ts/test/unit/responsesGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ describe("Responses.ts", () => {
});

describe("body generation", () => {
it("void as response body should be omitted", async () => {
const parameters = await emitResponsesFromTypeSpec(`
@post op read(): {@body body: void; @statusCode _: 204; };
`);
assert.ok(parameters);
await assertEqualContent(
parameters?.content!,
`
import { HttpResponse } from "@azure-rest/core-client";
/** There is no content to send for this request, but the headers may be useful. */
export interface Read204Response extends HttpResponse {
status: "204";
}
`
);
});

it("unknown array response generation", async () => {
const parameters = await emitResponsesFromTypeSpec(`
@post op read(): unknown[];
Expand Down

0 comments on commit bf90528

Please sign in to comment.