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
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