@typespec/http-server-js
Version:
TypeSpec HTTP server code generator for JavaScript
197 lines • 7.84 kB
JavaScript
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
import { isErrorModel } from "@typespec/compiler";
import { canonicalizeHttpOperation } from "../http/operation.js";
import { parseCase } from "../util/case.js";
import { getAllProperties } from "../util/extends.js";
import { bifilter, indent } from "../util/iter.js";
import { keywordSafe } from "../util/keywords.js";
import { emitDocumentation } from "./documentation.js";
import { emitTypeReference, isValueLiteralType } from "./reference.js";
import { emitUnionType } from "./union.js";
/**
* Emit an interface declaration.
*
* @param ctx - The emitter context.
* @param iface - The interface to emit.
* @param module - The module that this interface is written into.
*/
export function* emitInterface(ctx, iface, module) {
const name = parseCase(iface.name).pascalCase;
yield* emitDocumentation(ctx, iface);
yield `export interface ${name}<Context = unknown> {`;
yield* indent(emitOperationGroup(ctx, iface.operations.values(), module));
yield "}";
yield "";
}
/**
* Emit a list of operation signatures.
*
* @param ctx - The emitter context.
* @param operations - The operations to emit.
* @param module - The module that the operations are written into.
*/
export function* emitOperationGroup(ctx, operations, module) {
for (const op of operations) {
yield* emitOperation(ctx, op, module);
yield "";
}
}
/**
* Emit a single operation signature.
*
* @param ctx - The emitter context.
* @param op - The operation to emit.
* @param module - The module that the operation is written into.
*/
export function* emitOperation(ctx, op, module) {
op = canonicalizeHttpOperation(ctx, op);
const opNameCase = parseCase(op.name);
const opName = opNameCase.camelCase;
const allParameters = getAllProperties(op.parameters);
const hasOptions = allParameters.some((p) => p.optional);
const returnTypeReference = emitTypeReference(ctx, op.returnType, op, module, {
altName: opNameCase.pascalCase + "Result",
});
const returnType = `Promise<${returnTypeReference}>`;
const params = [];
for (const param of allParameters) {
// If the type is a value literal, then we consider it a _setting_ and not a parameter.
// This allows us to exclude metadata parameters (such as contentType) from the generated interface.
if (param.optional || isValueLiteralType(param.type))
continue;
const paramNameCase = parseCase(param.name);
const paramName = keywordSafe(paramNameCase.camelCase);
const outputTypeReference = emitTypeReference(ctx, param.type, param, module, {
altName: opNameCase.pascalCase + paramNameCase.pascalCase,
});
params.push(`${paramName}: ${outputTypeReference}`);
}
const paramsDeclarationLine = params.join(", ");
yield* emitDocumentation(ctx, op);
if (hasOptions) {
const optionsTypeName = opNameCase.pascalCase + "Options";
emitOptionsType(ctx, op, module, optionsTypeName);
const paramsFragment = params.length > 0 ? `${paramsDeclarationLine}, ` : "";
// prettier-ignore
yield `${opName}(ctx: Context, ${paramsFragment}options?: ${optionsTypeName}): ${returnType};`;
yield "";
}
else {
// prettier-ignore
yield `${opName}(ctx: Context, ${paramsDeclarationLine}): ${returnType};`;
yield "";
}
}
/**
* Emit a declaration for an options type including the optional parameters of an operation.
*
* @param ctx - The emitter context.
* @param operation - The operation to emit the options type for.
* @param module - The module that the options type is written into.
* @param optionsTypeName - The name of the options type.
*/
export function emitOptionsType(ctx, operation, module, optionsTypeName) {
module.imports.push({
binder: [optionsTypeName],
from: ctx.syntheticModule,
});
const options = [...operation.parameters.properties.values()].filter((p) => p.optional);
ctx.syntheticModule.declarations.push([
`export interface ${optionsTypeName} {`,
...options.flatMap((p) => [
` ${keywordSafe(parseCase(p.name).camelCase)}?: ${emitTypeReference(ctx, p.type, p, module, {
altName: optionsTypeName + parseCase(p.name).pascalCase,
})};`,
]),
"}",
"",
]);
}
const DEFAULT_NO_VARIANT_RETURN_TYPE = "never";
const DEFAULT_NO_VARIANT_SPLIT = {
kind: "ordinary",
typeReference: DEFAULT_NO_VARIANT_RETURN_TYPE,
target: undefined,
};
export function isInfallible(split) {
return ((split.kind === "ordinary" && split.typeReference === "never") ||
(split.kind === "union" && split.variants.length === 0));
}
export function splitReturnType(ctx, type, module, altBaseName) {
const successAltName = altBaseName + "Response";
const errorAltName = altBaseName + "ErrorResponse";
if (type.kind === "Union") {
const [successVariants, errorVariants] = bifilter(type.variants.values(), (v) => !isErrorModel(ctx.program, v.type));
const successTypeReference = successVariants.length === 0
? DEFAULT_NO_VARIANT_RETURN_TYPE
: successVariants.length === 1
? emitTypeReference(ctx, successVariants[0].type, successVariants[0], module, {
altName: successAltName,
})
: emitUnionType(ctx, successVariants, module);
const errorTypeReference = errorVariants.length === 0
? DEFAULT_NO_VARIANT_RETURN_TYPE
: errorVariants.length === 1
? emitTypeReference(ctx, errorVariants[0].type, errorVariants[0], module, {
altName: errorAltName,
})
: emitUnionType(ctx, errorVariants, module);
const successSplit = successVariants.length > 1
? {
kind: "union",
variants: successVariants,
typeReference: successTypeReference,
target: undefined,
}
: {
kind: "ordinary",
typeReference: successTypeReference,
target: successVariants[0]?.type,
};
const errorSplit = errorVariants.length > 1
? {
kind: "union",
variants: errorVariants,
typeReference: errorTypeReference,
// target: module.cursor.resolveRelativeItemPath(errorTypeReference),
target: undefined,
}
: {
kind: "ordinary",
typeReference: errorTypeReference,
target: errorVariants[0]?.type,
};
return [successSplit, errorSplit];
}
else {
// No splitting, just figure out if the type is an error type or not and make the other infallible.
if (isErrorModel(ctx.program, type)) {
const typeReference = emitTypeReference(ctx, type, type, module, {
altName: altBaseName + "ErrorResponse",
});
return [
DEFAULT_NO_VARIANT_SPLIT,
{
kind: "ordinary",
typeReference,
target: type,
},
];
}
else {
const typeReference = emitTypeReference(ctx, type, type, module, {
altName: altBaseName + "SuccessResponse",
});
return [
{
kind: "ordinary",
typeReference,
target: type,
},
DEFAULT_NO_VARIANT_SPLIT,
];
}
}
}
//# sourceMappingURL=interface.js.map