From c3411b80bab8e6a56615dba048e832c648a63473 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Wed, 11 Dec 2024 17:35:03 -0800 Subject: [PATCH 1/3] Add Experimental Http Typekit --- packages/compiler/package.json | 4 + .../src/experimental/typekit/kits/literal.ts | 20 +- .../typekit/kits/model-property.ts | 16 +- .../src/experimental/typekit/kits/model.ts | 40 ++- .../src/experimental/typekit/kits/scalar.ts | 19 +- .../src/experimental/typekit/kits/type.ts | 11 +- .../typekit/kits/union-variant.ts | 22 +- .../src/experimental/typekit/kits/union.ts | 18 +- .../test/experimental/typekit/model.test.ts | 56 ++++ .../typekit/kits/http-operation.ts | 129 ++++++++ .../experimental/typekit/kits/http-request.ts | 111 +++++++ .../typekit/kits/http-response.ts | 69 ++++ .../typekit/kits/model-property.ts | 110 +++++++ .../typekit/http-operation.test.ts | 93 ++++++ .../experimental/typekit/http-request.test.ts | 300 ++++++++++++++++++ .../typekit/http-response.test.ts | 88 +++++ 16 files changed, 1074 insertions(+), 32 deletions(-) create mode 100644 packages/compiler/test/experimental/typekit/model.test.ts create mode 100644 packages/http/src/experimental/typekit/kits/http-operation.ts create mode 100644 packages/http/src/experimental/typekit/kits/http-request.ts create mode 100644 packages/http/src/experimental/typekit/kits/http-response.ts create mode 100644 packages/http/src/experimental/typekit/kits/model-property.ts create mode 100644 packages/http/test/experimental/typekit/http-operation.test.ts create mode 100644 packages/http/test/experimental/typekit/http-request.test.ts create mode 100644 packages/http/test/experimental/typekit/http-response.test.ts diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 1b482997d1..591edf0e38 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -45,6 +45,10 @@ "./experimental": { "types": "./dist/src/experimental/index.d.ts", "default": "./dist/src/experimental/index.js" + }, + "./experimental/typekit": { + "types": "./dist/src/experimental/typekit/index.d.ts", + "default": "./dist/src/experimental/typekit/index.js" } }, "browser": { diff --git a/packages/compiler/src/experimental/typekit/kits/literal.ts b/packages/compiler/src/experimental/typekit/kits/literal.ts index b0e5d7301b..a8d18e26fa 100644 --- a/packages/compiler/src/experimental/typekit/kits/literal.ts +++ b/packages/compiler/src/experimental/typekit/kits/literal.ts @@ -3,8 +3,7 @@ import type { BooleanLiteral, NumericLiteral, StringLiteral, Type } from "../../ import { defineKit } from "../define-kit.js"; /** @experimental */ -interface LiteralKit { - literal: { +export interface LiteralKit { /** * Create a literal type from a JavaScript value. * @@ -60,14 +59,25 @@ interface LiteralKit { * @param type The type to check. */ isBoolean(type: Type): type is BooleanLiteral; - }; +} + +interface TypekitExtension { + /** + * Utilities for working with literal types. + * + * Literal types are types that represent a single value, such as a string, + * number, or boolean. + * + * @experimental + */ + literal: LiteralKit; } declare module "../define-kit.js" { - interface Typekit extends LiteralKit {} + interface Typekit extends TypekitExtension {} } -defineKit({ +defineKit({ literal: { create(value) { if (typeof value === "string") { diff --git a/packages/compiler/src/experimental/typekit/kits/model-property.ts b/packages/compiler/src/experimental/typekit/kits/model-property.ts index 6495d8d532..53e6d3a1ec 100644 --- a/packages/compiler/src/experimental/typekit/kits/model-property.ts +++ b/packages/compiler/src/experimental/typekit/kits/model-property.ts @@ -3,7 +3,15 @@ import { getVisibilityForClass } from "../../../core/visibility/core.js"; import { EncodeData, getEncode, getFormat } from "../../../lib/decorators.js"; import { defineKit } from "../define-kit.js"; -/** @experimental */ + /** + * @experimental + * Utilities for working with model properties. + * + * For many reflection operations, the metadata being asked for may be found + * on the model property or the type of the model property. In such cases, + * these operations will return the metadata from the model property if it + * exists, or the type of the model property if it exists. + */ export interface ModelPropertyKit { /** * Check if the given `type` is a model property. @@ -34,7 +42,7 @@ export interface ModelPropertyKit { getVisibilityForClass(property: ModelProperty, visibilityClass: Enum): Set; } -interface TypeKit { +interface TypekitExtension { /** * Utilities for working with model properties. * @@ -47,10 +55,10 @@ interface TypeKit { } declare module "../define-kit.js" { - interface Typekit extends TypeKit {} + interface Typekit extends TypekitExtension {} } -defineKit({ +defineKit({ modelProperty: { is(type) { return type.kind === "ModelProperty"; diff --git a/packages/compiler/src/experimental/typekit/kits/model.ts b/packages/compiler/src/experimental/typekit/kits/model.ts index ce7e7b62ff..b40af4c96c 100644 --- a/packages/compiler/src/experimental/typekit/kits/model.ts +++ b/packages/compiler/src/experimental/typekit/kits/model.ts @@ -1,3 +1,4 @@ +import { getEffectiveModelType } from "../../../core/checker.js"; import type { Model, ModelProperty, SourceModel, Type } from "../../../core/types.js"; import { createRekeyableMap } from "../../../utils/misc.js"; import { defineKit } from "../define-kit.js"; @@ -32,8 +33,11 @@ interface ModelDescriptor { sourceModels?: SourceModel[]; } +/** + * Utilities for working with models. + * @experimental + */ export interface ModelKit { - model: { /** * Create a model type. * @@ -47,14 +51,39 @@ export interface ModelKit { * @param type The type to check. */ is(type: Type): type is Model; - }; + + /** + * If the input is anonymous (or the provided filter removes properties) + * and there exists a named model with the same set of properties + * (ignoring filtered properties), then return that named model. + * Otherwise, return the input unchanged. + * + * This can be used by emitters to find a better name for a set of + * properties after filtering. For example, given `{ @metadata prop: + * string} & SomeName`, and an emitter that wishes to discard properties + * marked with `@metadata`, the emitter can use this to recover that the + * best name for the remaining properties is `SomeName`. + * + * @param model The input model + * @param filter An optional filter to apply to the input model's + * properties. + */ + getEffectiveModel(model: Model, filter?: (property: ModelProperty) => boolean): Model; +} + +interface TypekitExtension { + /** + * Utilities for working with models. + * @experimental + */ + model: ModelKit; } declare module "../define-kit.js" { - interface Typekit extends ModelKit {} + interface Typekit extends TypekitExtension {} } -export const ModelKit = defineKit({ +export const ModelKit = defineKit({ model: { create(desc) { const properties = createRekeyableMap(Array.from(Object.entries(desc.properties))); @@ -76,5 +105,8 @@ export const ModelKit = defineKit({ is(type) { return type.kind === "Model"; }, + getEffectiveModel(model, filter?: (property: ModelProperty) => boolean) { + return getEffectiveModelType(this.program, model, filter); + }, }, }); diff --git a/packages/compiler/src/experimental/typekit/kits/scalar.ts b/packages/compiler/src/experimental/typekit/kits/scalar.ts index 1360a15fc7..ac45e81781 100644 --- a/packages/compiler/src/experimental/typekit/kits/scalar.ts +++ b/packages/compiler/src/experimental/typekit/kits/scalar.ts @@ -5,12 +5,12 @@ import { defineKit, Typekit } from "../define-kit.js"; // eslint-disable-next-line @typescript-eslint/no-unused-vars import type { ModelPropertyKit } from "./model-property.js"; -/** @experimental */ -interface ScalarKit { /** * Operations for scalar types like strings, numerics, booleans, dates, etc. + * @experimental */ - scalar: { +export interface ScalarKit { + /** * Check if `type` is any scalar type. * @@ -403,14 +403,21 @@ interface ScalarKit { * @param scalar The scalar to get the format for. */ getFormat(scalar: Scalar): string | undefined; - }; +} + +interface TypekitExtension { + /** + * Operations for scalar types like strings, numerics, booleans, dates, etc. + * @experimental + */ + scalar: ScalarKit; } declare module "../define-kit.js" { - interface Typekit extends ScalarKit {} + interface Typekit extends TypekitExtension {} } -defineKit({ +defineKit({ scalar: { is(type) { return type.kind === "Scalar"; diff --git a/packages/compiler/src/experimental/typekit/kits/type.ts b/packages/compiler/src/experimental/typekit/kits/type.ts index 074ee8e115..376e8b0773 100644 --- a/packages/compiler/src/experimental/typekit/kits/type.ts +++ b/packages/compiler/src/experimental/typekit/kits/type.ts @@ -3,7 +3,7 @@ import { defineKit, Typekit } from "../define-kit.js"; import { copyMap } from "../utils.js"; /** @experimental */ -export interface TypeKit { +export interface TypeTypekit { /** * Clones a type and adds it to the typekit's realm. * @param type Type to clone @@ -15,18 +15,19 @@ export interface TypeKit { finishType(type: Type): void; } -interface BaseTypeKit { +interface TypekitExtension { /** * Utilities for working with general types. + * @experimental */ - type: TypeKit; + type: TypeTypekit; } declare module "../define-kit.js" { - interface Typekit extends BaseTypeKit {} + interface Typekit extends TypekitExtension {} } -defineKit({ +defineKit({ type: { finishType(type: Type) { this.program.checker.finishType(type); diff --git a/packages/compiler/src/experimental/typekit/kits/union-variant.ts b/packages/compiler/src/experimental/typekit/kits/union-variant.ts index f4ce90d657..ce4ef37ca4 100644 --- a/packages/compiler/src/experimental/typekit/kits/union-variant.ts +++ b/packages/compiler/src/experimental/typekit/kits/union-variant.ts @@ -25,8 +25,15 @@ interface UnionVariantDescriptor { union?: Union; } +/** + * Utilities for working with union variants. + * + * Union variants are types that represent a single value within a union that can be one of + * several types. + * + * @experimental + */ export interface UnionVariantKit { - unionVariant: { /** * Create a union variant. * @@ -40,14 +47,21 @@ export interface UnionVariantKit { * @param type The type to check. */ is(type: Type): type is UnionVariant; - }; +} + +interface TypekitExtension { + /** + * Utilities for working with union variants. + * @experimental + */ + unionVariant: UnionVariantKit; } declare module "../define-kit.js" { - interface Typekit extends UnionVariantKit {} + interface Typekit extends TypekitExtension {} } -defineKit({ +defineKit({ unionVariant: { create(desc) { const variant: UnionVariant = this.program.checker.createType({ diff --git a/packages/compiler/src/experimental/typekit/kits/union.ts b/packages/compiler/src/experimental/typekit/kits/union.ts index 68812d8c37..c2abe01438 100644 --- a/packages/compiler/src/experimental/typekit/kits/union.ts +++ b/packages/compiler/src/experimental/typekit/kits/union.ts @@ -23,8 +23,11 @@ interface UnionDescriptor { variants?: Record | UnionVariant[]; } +/** + * Utilities for working with unions. + * @experimental + */ export interface UnionKit { - union: { /** * Create a union type. * @@ -55,14 +58,21 @@ export interface UnionKit { * @param type The union to check. */ isExtensible(type: Union): boolean; - }; +} + +interface TypekitExtension { + /** + * Utilities for working with unions. + * @experimental + */ + union: UnionKit; } declare module "../define-kit.js" { - interface Typekit extends UnionKit {} + interface Typekit extends TypekitExtension {} } -export const UnionKit = defineKit({ +export const UnionKit = defineKit({ union: { create(desc) { const union: Union = this.program.checker.createType({ diff --git a/packages/compiler/test/experimental/typekit/model.test.ts b/packages/compiler/test/experimental/typekit/model.test.ts new file mode 100644 index 0000000000..2ed498b457 --- /dev/null +++ b/packages/compiler/test/experimental/typekit/model.test.ts @@ -0,0 +1,56 @@ +import { expect, it } from "vitest"; +import { $ } from "../../../src/experimental/typekit/index.js"; +import { Operation } from "../../../src/index.js"; +import { getTypes } from "./utils.js"; + +it("can check if a type is a Model", async () => { + const { Foo } = await getTypes( + ` + model Foo {}; + `, + ["Foo"], + ); + + expect($.model.is(Foo)).toBe(true); +}); + +it("returns false whe the type is not a model", async () => { + const { Foo } = await getTypes( + ` + interface Foo {}; + `, + ["Foo"], + ); + + expect($.model.is(Foo)).toBe(false); +}); + +it("creates a new Model", async () => { + const foo = $.model.create({ + name: "Foo", + properties: {} + }) + + expect($.model.is(foo)).toBe(true); +}); + +it("can get the effective model type", async () => { + const { Foo, create } = await getTypes( + ` + model Foo { + id: string; + }; + + op create(...Foo): void; + `, + ["Foo", "create"], + ); + + const createParameters = (create as Operation).parameters; + const model = $.model.getEffectiveModel(createParameters); + + // Since Foo is spread they are not the same model + expect(createParameters).not.toBe(Foo); + // But Foo is the effective model + expect(model).toBe(Foo); +}); diff --git a/packages/http/src/experimental/typekit/kits/http-operation.ts b/packages/http/src/experimental/typekit/kits/http-operation.ts new file mode 100644 index 0000000000..471ddf4d71 --- /dev/null +++ b/packages/http/src/experimental/typekit/kits/http-operation.ts @@ -0,0 +1,129 @@ +import { ignoreDiagnostics, Operation, StringLiteral, Type, VoidType } from "@typespec/compiler"; +import { defineKit, Typekit } from "@typespec/compiler/experimental/typekit"; +import { getHttpOperation } from "../../../operations.js"; +import { HttpOperation, HttpOperationResponseContent, HttpStatusCodesEntry } from "../../../types.js"; + +/** + * Utilities for working with HTTP operations. + * @experimental + */ +export interface HttpOperationKit { + /** + * Get the corresponding HTTP operation for the given TypeSpec operation. The same + * TypeSpec operation will always return the exact same HttpOperation object. + * + * @param op The TypeSpec operation to get the HTTP operation metadata for. + */ + get(op: Operation): HttpOperation; + /** + * Get the responses for the given operation. This function will return an array of responses grouped by status code and content type. + * @param op operation to extract the HttpResponse from + */ + getResponses(op: Operation): FlatHttpResponse[]; + /** + * Get the Http Return type for the given operation. This function will resolve the returnType based on the Http Operation. + * @param op operation to get the return type for + */ + getReturnType(op: Operation, options?: { includeErrors?: boolean }): Type; +} + +/** + * Structure of a flat HTTP response, which is grouped by status code and content type. + */ +export interface FlatHttpResponse { + /** + * Response status code. + */ + statusCode: HttpStatusCodesEntry; + /** + * Content type. Might be undefined if the response does not have a body. + */ + contentType?: string; + /** + * Response content. + */ + responseContent: HttpOperationResponseContent; +} + +interface TypekitExtension { + /** + * Utilities for working with HTTP operations. + * @experimental + */ + httpOperation: HttpOperationKit; +} + +declare module "@typespec/compiler/experimental/typekit" { + interface Typekit extends TypekitExtension {} +} + +defineKit({ + httpOperation: { + get(op) { + return ignoreDiagnostics(getHttpOperation(this.program, op)); + }, + getReturnType(operation, options) { + let responses = this.httpOperation.getResponses(operation); + + if (!options?.includeErrors) { + responses = responses.filter((r) => !this.httpResponse.isErrorResponse(r.responseContent)); + } + + const voidType = { kind: "Intrinsic", name: "void" } as VoidType; + let httpReturnType: Type = voidType; + + if (!responses.length) { + return voidType; + } + + if (responses.length > 1) { + const res = [...new Set(responses.map((r) => r.responseContent.body?.type))]; + httpReturnType = this.union.create({ + variants: res.map((t) => { + return this.unionVariant.create({ + type: getEffectiveType(this, t), + }); + }), + }); + } else { + httpReturnType = getEffectiveType(this, responses[0].responseContent.body?.type); + } + + return httpReturnType; + }, + getResponses(operation) { + const responsesMap: FlatHttpResponse[] = []; + const httpOperation = this.httpOperation.get(operation); + for (const response of httpOperation.responses) { + for (const responseContent of response.responses) { + const contentTypeProperty = responseContent.properties.find( + (property) => property.kind === "contentType", + ); + + let contentType: string | undefined; + + if (contentTypeProperty) { + contentType = (contentTypeProperty.property.type as StringLiteral).value; + } else if (responseContent.body) { + contentType = "application/json"; + } + + responsesMap.push({ statusCode: response.statusCodes, contentType, responseContent }); + } + } + + return responsesMap; + }, + }, +}); + +function getEffectiveType(typekit: Typekit, type?: Type): Type { + if (type === undefined) { + return { kind: "Intrinsic", name: "void" } as VoidType; + } + if (typekit.model.is(type)) { + return typekit.model.getEffectiveModel(type); + } + + return type; +} diff --git a/packages/http/src/experimental/typekit/kits/http-request.ts b/packages/http/src/experimental/typekit/kits/http-request.ts new file mode 100644 index 0000000000..de034a4f7b --- /dev/null +++ b/packages/http/src/experimental/typekit/kits/http-request.ts @@ -0,0 +1,111 @@ +import { Model, ModelProperty } from "@typespec/compiler"; +import { defineKit } from "@typespec/compiler/experimental/typekit"; +import { HttpOperation } from "../../../types.js"; + +export type HttpRequestParameterKind = "query" | "header" | "path" | "contentType" | "body"; + +interface HttpRequestKit { + body: { + /** + * Checks the body is a property explicitly tagged with @body or @bodyRoot + * @param httpOperation the http operation to check + */ + isExplicit(httpOperation: HttpOperation): boolean; + }; + /** + * Gets a Model representing the body parameters of an http operation. + * @param httpOperation the http operation to get the body parameters from + */ + getBodyParameters(httpOperation: HttpOperation): Model | undefined; + /** + * Gets a Model representing the parameters of an http operation. + * @param httpOperation The Http operation to get the parameters from. + * @param kind A string to filters specific parameter kinds, or an array to combine multiple kinds. + */ + getParameters( + httpOperation: HttpOperation, + kind: HttpRequestParameterKind[] | HttpRequestParameterKind, + ): Model | undefined; +} + +interface TypekitExtension { + httpRequest: HttpRequestKit; +} + + +declare module "@typespec/compiler/experimental/typekit" { + interface Typekit extends TypekitExtension {} +} + +defineKit({ + httpRequest: { + body: { + isExplicit(httpOperation: HttpOperation) { + return ( + httpOperation.parameters.properties.find( + (p) => p.kind === "body" || p.kind === "bodyRoot", + ) !== undefined + ); + }, + }, + getBodyParameters(httpOperation: HttpOperation): Model | undefined { + const body = httpOperation.parameters.body; + + if (!body) { + return undefined; + } + + const bodyProperty = body.property; + + if (!bodyProperty) { + if (body.type.kind === "Model") { + return body.type; + } + throw new Error("Body property not found"); + } + + const bodyPropertyName = bodyProperty.name ? bodyProperty.name : "body"; + + return this.model.create({ + properties: { [bodyPropertyName]: bodyProperty }, + }); + }, + getParameters( + httpOperation: HttpOperation, + kind: HttpRequestParameterKind | HttpRequestParameterKind[], + ): Model | undefined { + const kinds = new Set(Array.isArray(kind) ? kind : [kind]); + const parameterProperties: ModelProperty[] = []; + + for (const kind of kinds) { + if (kind === "body") { + const bodyParams = Array.from( + this.httpRequest.getBodyParameters(httpOperation)?.properties.values() ?? [], + ); + if (bodyParams) { + parameterProperties.push(...bodyParams); + } + } else { + const params = httpOperation.parameters.properties + .filter((p) => p.kind === kind) + .map((p) => p.property); + parameterProperties.push(...params); + } + } + + if (parameterProperties.length === 0) { + return undefined; + } + + const properties = parameterProperties.reduce( + (acc, prop) => { + acc[prop.name] = prop; + return acc; + }, + {} as Record, + ); + + return this.model.create({ properties }); + }, + }, +}); diff --git a/packages/http/src/experimental/typekit/kits/http-response.ts b/packages/http/src/experimental/typekit/kits/http-response.ts new file mode 100644 index 0000000000..9a7ed996d9 --- /dev/null +++ b/packages/http/src/experimental/typekit/kits/http-response.ts @@ -0,0 +1,69 @@ +import { isErrorModel } from "@typespec/compiler"; +import { defineKit } from "@typespec/compiler/experimental/typekit"; +import { + HttpOperationResponseContent, + HttpStatusCodeRange, + HttpStatusCodesEntry, +} from "../../../types.js"; + +/** + * Utilities for working with HTTP responses. + * @experimental + */ +export interface HttpResponseKit { + /** + * Check if the response is an error response. + */ + isErrorResponse(response: HttpOperationResponseContent): boolean; + /** + * utilities to perform checks on status codes + */ + statusCode: { + /** + * Check if the status code is a single status code + * @param statusCode status code to check + */ + isSingle(statusCode: HttpStatusCodesEntry): statusCode is number; + /** + * Check if the status code is a range of status codes + * @param statusCode status code to check + */ + isRange(statusCode: HttpStatusCodesEntry): statusCode is HttpStatusCodeRange; + /** + * Check if the status code is a default status code + * @param statusCode status code to check + */ + isDefault(statusCode: HttpStatusCodesEntry): statusCode is "*"; + }; +} + +interface TypekitExtension { + /** + * Utilities for working with HTTP responses. + * @experimental + */ + httpResponse: HttpResponseKit; +} + +declare module "@typespec/compiler/experimental/typekit" { + interface Typekit extends TypekitExtension {} +} + +defineKit({ + httpResponse: { + isErrorResponse(response) { + return response.body ? isErrorModel(this.program, response.body.type) : false; + }, + statusCode: { + isSingle(statusCode) { + return typeof statusCode === "number"; + }, + isRange(statusCode) { + return typeof statusCode === "object" && "start" in statusCode && "end" in statusCode; + }, + isDefault(statusCode) { + return statusCode === "*"; + }, + }, + }, +}); diff --git a/packages/http/src/experimental/typekit/kits/model-property.ts b/packages/http/src/experimental/typekit/kits/model-property.ts new file mode 100644 index 0000000000..d6f706f4b6 --- /dev/null +++ b/packages/http/src/experimental/typekit/kits/model-property.ts @@ -0,0 +1,110 @@ +import { ModelProperty } from "@typespec/compiler"; +import { defineKit } from "@typespec/compiler/experimental/typekit"; +import { + getHeaderFieldOptions, + getPathParamOptions, + getQueryParamOptions, + isHeader, + isMultipartBodyProperty, + isPathParam, + isQueryParam, +} from "../../../decorators.js"; +import { HeaderFieldOptions, PathParameterOptions, QueryParameterOptions } from "../../../types.js"; + +/** + * Utilities for working with model properties in the context of Http. + * @experimental + */ +export interface HttpModelProperty { + /** + * Get the Http parameter options for a model property. + * @param prop a TypeSpec ModelProperty + */ + getHttpParamOptions( + prop: ModelProperty, + ): HeaderFieldOptions | PathParameterOptions | QueryParameterOptions | undefined; + /** + * Get the Http header options for a model property. + * @param prop a TypeSpec ModelProperty + */ + getHttpHeaderOptions(prop: ModelProperty): HeaderFieldOptions | undefined; + /** + * Get the Http path options for a model property. + * @param prop a TypeSpec ModelProperty + */ + getHttpPathOptions(prop: ModelProperty): PathParameterOptions | undefined; + /** + * Get the Http query options for a model property. + * @param prop a TypeSpec ModelProperty + */ + getHttpQueryOptions(prop: ModelProperty): QueryParameterOptions | undefined; + /** + * Check if a model property is an Http header. + * @param prop a TypeSpec ModelProperty + */ + isHttpHeader(prop: ModelProperty): boolean; + /** + * Check if a model property is an Http path parameter. + * @param prop a TypeSpec ModelProperty + */ + isHttpPathParam(prop: ModelProperty): boolean; + /** + * Check if a model property is an Http query parameter. + * @param prop a TypeSpec ModelProperty + */ + isHttpQueryParam(prop: ModelProperty): boolean; + /** + * Check if a model property is an Http multipart body. + * @param prop a TypeSpec ModelProperty + */ + isHttpMultipartBody(prop: ModelProperty): boolean; +} + +interface TypekitExtension { + modelProperty: HttpModelProperty; +} + +declare module "@typespec/compiler/experimental/typekit" { + interface ModelPropertyKit extends HttpModelProperty {} +} + +defineKit({ + modelProperty: { + getHttpParamOptions(prop: ModelProperty) { + if (isHeader(this.program, prop)) { + return getHeaderFieldOptions(this.program, prop); + } + + if (isPathParam(this.program, prop)) { + return getPathParamOptions(this.program, prop); + } + + if (isQueryParam(this.program, prop)) { + return getQueryParamOptions(this.program, prop); + } + + return undefined; + }, + getHttpHeaderOptions(prop: ModelProperty) { + return getHeaderFieldOptions(this.program, prop); + }, + getHttpPathOptions(prop) { + return getPathParamOptions(this.program, prop); + }, + getHttpQueryOptions(prop: ModelProperty) { + return getQueryParamOptions(this.program, prop); + }, + isHttpHeader(prop: ModelProperty) { + return isHeader(this.program, prop); + }, + isHttpPathParam(prop: ModelProperty) { + return isPathParam(this.program, prop); + }, + isHttpQueryParam(prop: ModelProperty) { + return isQueryParam(this.program, prop); + }, + isHttpMultipartBody(prop: ModelProperty) { + return isMultipartBodyProperty(this.program, prop); + }, + }, +}); diff --git a/packages/http/test/experimental/typekit/http-operation.test.ts b/packages/http/test/experimental/typekit/http-operation.test.ts new file mode 100644 index 0000000000..28a833a40e --- /dev/null +++ b/packages/http/test/experimental/typekit/http-operation.test.ts @@ -0,0 +1,93 @@ +import { Model, Operation } from "@typespec/compiler"; +import { BasicTestRunner } from "@typespec/compiler/testing"; +import { $ } from "@typespec/compiler/experimental/typekit"; +import { beforeEach, describe, expect, it } from "vitest"; +import { createHttpTestRunner } from "./../../test-host.js"; + +let runner: BasicTestRunner; + +beforeEach(async () => { + runner = await createHttpTestRunner(); +}); + +describe("httpOperation:getResponses", () => { + it("should get responses", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + @visibility("create") + id: int32; + age: int32; + name: string; + } + + @error + @test model Error { + message: string; + code: int32 + } + + @route("/foo") + @get + @test op getFoo(): Foo | Error; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const responses = $.httpOperation.getResponses(getFoo); + expect(responses).toHaveLength(2); + expect(responses[0].statusCode).toBe(200); + expect(responses[0].contentType).toBe("application/json"); + expect(responses[1].statusCode).toBe("*"); + expect(responses[1].contentType).toBe("application/json"); + }); + + it("should get responses with multiple status codes", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + @visibility("create") + id: int32; + age: int32; + name: string; + } + + @route("/foo") + @get + @test op getFoo(): Foo | void; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const responses = $.httpOperation.getResponses(getFoo); + expect(responses).toHaveLength(2); + expect(responses[0].statusCode).toBe(200); + expect(responses[0].contentType).toBe("application/json"); + expect(responses[1].statusCode).toBe(204); + expect(responses[1].contentType).toBe(undefined); + }); + + it("should get responses with multiple status codes and contentTypes", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + @visibility("create") + id: int32; + age: int32; + name: string; + } + + @error + @test model Error { + message: string; + code: int32 + } + + @route("/foo") + @get + @test op getFoo(): Foo | {...Foo, @header contentType: "text/plain"} | Error; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const responses = $.httpOperation.getResponses(getFoo); + expect(responses).toHaveLength(3); + expect(responses[0].statusCode).toBe(200); + expect(responses[0].contentType).toBe("application/json"); + expect(responses[1].statusCode).toBe(200); + expect(responses[1].contentType).toBe("text/plain"); + expect(responses[2].statusCode).toBe("*"); + expect(responses[2].contentType).toBe("application/json"); + }); +}); diff --git a/packages/http/test/experimental/typekit/http-request.test.ts b/packages/http/test/experimental/typekit/http-request.test.ts new file mode 100644 index 0000000000..f6e3b3178a --- /dev/null +++ b/packages/http/test/experimental/typekit/http-request.test.ts @@ -0,0 +1,300 @@ +import { Model, Operation } from "@typespec/compiler"; +import { BasicTestRunner } from "@typespec/compiler/testing"; +import { $ } from "@typespec/compiler/experimental/typekit"; +import { beforeEach, describe, expect, it } from "vitest"; +import { createHttpTestRunner } from "./../../test-host.js"; + +let runner: BasicTestRunner; + +beforeEach(async () => { + runner = await createHttpTestRunner(); +}); + +describe("HttpRequest Body Parameters", () => { + it("should get the body parameters model when spread", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + expect(body).toBeDefined(); + expect($.model.is(body)).toBe(true); + expect((body as Model).properties.size).toBe(3); + }); + + it("should get the body model params when body is defined explicitly as a property", async () => { + const { createFoo } = (await runner.compile(` + @route("/foo") + @post + @test op createFoo(@body foo: int32): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + expect(body).toBeDefined(); + expect($.model.is(body)).toBe(true); + expect(body.properties.size).toBe(1); + expect(body.properties.get("foo")!.name).toBe("foo"); + }); + + it("should get the body when spread and nested", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @path id: int32; + age: int32; + name: string; + options: { + @path token: string; + subProp: string; + } + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + expect(body).toBeDefined(); + expect((body as Model).properties.size).toBe(3); + const properties = Array.from(body.properties.values()) + .map((p) => p.name) + .join(","); + expect(properties).toBe("age,name,options"); + + const optionsParam = (body as Model).properties.get("options")!.type as Model; + const optionsProps = Array.from(optionsParam.properties.values()) + .map((p) => p.name) + .join(","); + + // TODO: Why do we get the path property token here? + expect(optionsProps).toEqual("token,subProp"); + }); + + it("should get the body when named body model", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(@body foo: Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + expect(body).toBeDefined(); + expect($.model.is(body)).toBe(true); + // Should have a single property called foo + expect(body.properties.size).toBe(1); + expect((body.properties.get("foo")?.type as Model).name).toBe("Foo"); + }); + + it("should get the named body body when combined", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @path id: int32; + age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(foo: Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + expect(body).toBeDefined(); + expect($.model.is(body)).toBe(true); + expect((body as Model).properties.size).toBe(1); + expect(((body as Model).properties.get("foo")?.type as any).name).toBe("Foo"); + }); +}); + +describe("HttpRequest Get Parameters", () => { + it("should only have body parameters", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)!; + const headers = $.httpRequest.getParameters(httpOperation, "header"); + const path = $.httpRequest.getParameters(httpOperation, "path"); + const query = $.httpRequest.getParameters(httpOperation, "query"); + expect(body).toBeDefined(); + expect(headers).toBeUndefined(); + expect(path).toBeUndefined(); + expect(query).toBeUndefined(); + }); + + it("should be able to get parameter options", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @path(#{allowReserved: true}) id: string; + @header({format: "csv"}) requestId: string[]; + @query(#{explode: true}) data: string[]; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const headers = $.httpRequest.getParameters(httpOperation, "header"); + const path = $.httpRequest.getParameters(httpOperation, "path"); + const query = $.httpRequest.getParameters(httpOperation, "query"); + + const requestIdProperty = headers!.properties.get("requestId"); + const idProperty = path!.properties.get("id"); + const dataProperty = query!.properties.get("data"); + + expect($.modelProperty.getHttpHeaderOptions(requestIdProperty!)).toStrictEqual({ + format: "csv", + name: "request-id", + type: "header", + }); + + expect($.modelProperty.getHttpPathOptions(idProperty!)).toStrictEqual({ + allowReserved: true, + explode: false, + name: "id", + style: "simple", + type: "path", + }); + + expect($.modelProperty.getHttpQueryOptions(dataProperty!)).toStrictEqual({ + explode: true, + format: "multi", + name: "data", + type: "query", + }); + }); + + it("should only have header parameters", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @path id: int32; + age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)! as Model; + const headers = $.httpRequest.getParameters(httpOperation, "header"); + const path = $.httpRequest.getParameters(httpOperation, "path")!; + const query = $.httpRequest.getParameters(httpOperation, "query"); + expect(body).toBeDefined(); + expect(body.properties.size).toBe(2); + expect(path).toBeDefined(); + expect(path.properties.size).toBe(1); + expect(path.properties.get("id")?.name).toBe("id"); + expect(headers).toBeUndefined(); + expect(query).toBeUndefined(); + }); + + it("should only have path parameters", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @header id: int32; + @header age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)! as Model; + const headers = $.httpRequest.getParameters(httpOperation, "header")!; + const path = $.httpRequest.getParameters(httpOperation, "path"); + const query = $.httpRequest.getParameters(httpOperation, "query"); + expect(body).toBeDefined(); + expect(body.properties.size).toBe(1); + expect(headers).toBeDefined(); + expect(headers.properties.size).toBe(2); + expect(headers.properties.get("id")?.name).toBe("id"); + expect(headers.properties.get("age")?.name).toBe("age"); + expect(path).toBeUndefined(); + expect(query).toBeUndefined(); + }); + + it("should only have query parameters", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @query id: int32; + @query age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const body = $.httpRequest.getBodyParameters(httpOperation)! as Model; + const headers = $.httpRequest.getParameters(httpOperation, "header"); + const path = $.httpRequest.getParameters(httpOperation, "path"); + const query = $.httpRequest.getParameters(httpOperation, "query")!; + expect(body).toBeDefined(); + expect(body.properties.size).toBe(1); + expect(query).toBeDefined(); + expect(query.properties.size).toBe(2); + expect(query.properties.get("id")?.name).toBe("id"); + expect(query.properties.get("age")?.name).toBe("age"); + expect(path).toBeUndefined(); + expect(headers).toBeUndefined(); + }); + + it("should have query and header parameters", async () => { + const { createFoo } = (await runner.compile(` + @test model Foo { + @query id: int32; + @header age: int32; + name: string; + } + + @route("/foo") + @post + @test op createFoo(...Foo): void; + `)) as { createFoo: Operation; Foo: Model }; + + const httpOperation = $.httpOperation.get(createFoo); + const headerAndQuery = $.httpRequest.getParameters(httpOperation, ["header", "query"]); + expect(headerAndQuery).toBeDefined(); + expect(headerAndQuery!.properties.size).toBe(2); + expect(headerAndQuery!.properties.get("id")?.name).toBe("id"); + expect(headerAndQuery!.properties.get("age")?.name).toBe("age"); + }); +}); diff --git a/packages/http/test/experimental/typekit/http-response.test.ts b/packages/http/test/experimental/typekit/http-response.test.ts new file mode 100644 index 0000000000..c3739591c6 --- /dev/null +++ b/packages/http/test/experimental/typekit/http-response.test.ts @@ -0,0 +1,88 @@ +import { Model, Operation } from "@typespec/compiler"; +import { BasicTestRunner } from "@typespec/compiler/testing"; +import { $ } from "@typespec/compiler/experimental/typekit"; +import { beforeEach, expect, it } from "vitest"; +import { createHttpTestRunner } from "./../../test-host.js"; + +let runner: BasicTestRunner; + +beforeEach(async () => { + runner = await createHttpTestRunner(); +}); + +it("should return true for an error response", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + } + + @error + @test model Error { + message: string; + code: int32 + } + + @route("/foo") + @get + @test op getFoo(): Foo | Error; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const responses = $.httpOperation.getResponses(getFoo); + expect(responses).toHaveLength(2); + expect($.httpResponse.isErrorResponse(responses[0].responseContent)).toBe(false); + expect($.httpResponse.isErrorResponse(responses[1].responseContent)).toBe(true); +}); + +it("should identify a single and default status code", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + } + + @error + @test model Error { + message: string; + code: int32 + } + + @route("/foo") + @get + @test op getFoo(): Foo | Error; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const response = $.httpOperation.getResponses(getFoo)[0]; + const error = $.httpOperation.getResponses(getFoo)[1]; + expect($.httpResponse.statusCode.isSingle(response.statusCode)).toBe(true); + expect($.httpResponse.statusCode.isDefault(error.statusCode)).toBe(true); +}) + +it("should identify a range status code", async () => { + const { getFoo } = (await runner.compile(` + @test model Foo { + id: int32; + age: int32; + name: string; + @minValue(455) @maxValue(495) @statusCode @statusCode _: int32 + } + + @error + @test model Error { + message: string; + code: int32 + } + + @route("/foo") + @get + @test op getFoo(): Foo | Error; + `)) as { getFoo: Operation; Foo: Model; Error: Model }; + + const response = $.httpOperation.getResponses(getFoo)[0]; + const error = $.httpOperation.getResponses(getFoo)[1]; + expect($.httpResponse.statusCode.isRange(response.statusCode)).toBe(true); + expect($.httpResponse.statusCode.isDefault(error.statusCode)).toBe(true); +}) + From aa42e3d36550180c000a670a99429cf9703977f1 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Wed, 11 Dec 2024 17:36:12 -0800 Subject: [PATCH 2/3] Format --- .../src/experimental/typekit/kits/literal.ts | 100 +-- .../typekit/kits/model-property.ts | 18 +- .../src/experimental/typekit/kits/model.ts | 28 +- .../src/experimental/typekit/kits/scalar.ts | 799 +++++++++--------- .../typekit/kits/union-variant.ts | 28 +- .../src/experimental/typekit/kits/union.ts | 60 +- .../test/experimental/typekit/model.test.ts | 6 +- .../typekit/kits/http-operation.ts | 40 +- .../experimental/typekit/kits/http-request.ts | 39 +- .../typekit/kits/http-response.ts | 40 +- .../typekit/http-operation.test.ts | 2 +- .../experimental/typekit/http-request.test.ts | 2 +- .../typekit/http-response.test.ts | 7 +- 13 files changed, 585 insertions(+), 584 deletions(-) diff --git a/packages/compiler/src/experimental/typekit/kits/literal.ts b/packages/compiler/src/experimental/typekit/kits/literal.ts index a8d18e26fa..ae6c5d0c38 100644 --- a/packages/compiler/src/experimental/typekit/kits/literal.ts +++ b/packages/compiler/src/experimental/typekit/kits/literal.ts @@ -4,70 +4,70 @@ import { defineKit } from "../define-kit.js"; /** @experimental */ export interface LiteralKit { - /** - * Create a literal type from a JavaScript value. - * - * @param value The JavaScript value to turn into a TypeSpec literal type. - */ - create(value: string | number | boolean): StringLiteral | NumericLiteral | BooleanLiteral; + /** + * Create a literal type from a JavaScript value. + * + * @param value The JavaScript value to turn into a TypeSpec literal type. + */ + create(value: string | number | boolean): StringLiteral | NumericLiteral | BooleanLiteral; - /** - * Create a string literal type from a JavaScript string value. - * - * @param value The string value. - */ - createString(value: string): StringLiteral; + /** + * Create a string literal type from a JavaScript string value. + * + * @param value The string value. + */ + createString(value: string): StringLiteral; - /** - * Create a numeric literal type from a JavaScript number value. - * - * @param value The numeric value. - */ - createNumeric(value: number): NumericLiteral; + /** + * Create a numeric literal type from a JavaScript number value. + * + * @param value The numeric value. + */ + createNumeric(value: number): NumericLiteral; - /** - * Create a boolean literal type from a JavaScript boolean value. - * - * @param value The boolean value. - */ - createBoolean(value: boolean): BooleanLiteral; + /** + * Create a boolean literal type from a JavaScript boolean value. + * + * @param value The boolean value. + */ + createBoolean(value: boolean): BooleanLiteral; - /** - * Check if `type` is a literal type. - * - * @param type The type to check. - */ - is(type: Type): type is StringLiteral | NumericLiteral | BooleanLiteral; + /** + * Check if `type` is a literal type. + * + * @param type The type to check. + */ + is(type: Type): type is StringLiteral | NumericLiteral | BooleanLiteral; - /** - * Check if `type` is a string literal type. - * - * @param type The type to check. - */ - isString(type: Type): type is StringLiteral; + /** + * Check if `type` is a string literal type. + * + * @param type The type to check. + */ + isString(type: Type): type is StringLiteral; - /** - * Check if `type` is a numeric literal type. - * - * @param type The type to check. - */ - isNumeric(type: Type): type is NumericLiteral; + /** + * Check if `type` is a numeric literal type. + * + * @param type The type to check. + */ + isNumeric(type: Type): type is NumericLiteral; - /** - * Check if `type` is a boolean literal type. - * - * @param type The type to check. - */ - isBoolean(type: Type): type is BooleanLiteral; + /** + * Check if `type` is a boolean literal type. + * + * @param type The type to check. + */ + isBoolean(type: Type): type is BooleanLiteral; } interface TypekitExtension { /** * Utilities for working with literal types. - * + * * Literal types are types that represent a single value, such as a string, * number, or boolean. - * + * * @experimental */ literal: LiteralKit; diff --git a/packages/compiler/src/experimental/typekit/kits/model-property.ts b/packages/compiler/src/experimental/typekit/kits/model-property.ts index 53e6d3a1ec..a8f941b367 100644 --- a/packages/compiler/src/experimental/typekit/kits/model-property.ts +++ b/packages/compiler/src/experimental/typekit/kits/model-property.ts @@ -3,15 +3,15 @@ import { getVisibilityForClass } from "../../../core/visibility/core.js"; import { EncodeData, getEncode, getFormat } from "../../../lib/decorators.js"; import { defineKit } from "../define-kit.js"; - /** - * @experimental - * Utilities for working with model properties. - * - * For many reflection operations, the metadata being asked for may be found - * on the model property or the type of the model property. In such cases, - * these operations will return the metadata from the model property if it - * exists, or the type of the model property if it exists. - */ +/** + * @experimental + * Utilities for working with model properties. + * + * For many reflection operations, the metadata being asked for may be found + * on the model property or the type of the model property. In such cases, + * these operations will return the metadata from the model property if it + * exists, or the type of the model property if it exists. + */ export interface ModelPropertyKit { /** * Check if the given `type` is a model property. diff --git a/packages/compiler/src/experimental/typekit/kits/model.ts b/packages/compiler/src/experimental/typekit/kits/model.ts index b40af4c96c..990e5c3ff5 100644 --- a/packages/compiler/src/experimental/typekit/kits/model.ts +++ b/packages/compiler/src/experimental/typekit/kits/model.ts @@ -38,21 +38,21 @@ interface ModelDescriptor { * @experimental */ export interface ModelKit { - /** - * Create a model type. - * - * @param desc The descriptor of the model. - */ - create(desc: ModelDescriptor): Model; + /** + * Create a model type. + * + * @param desc The descriptor of the model. + */ + create(desc: ModelDescriptor): Model; - /** - * Check if the given `type` is a model.. - * - * @param type The type to check. - */ - is(type: Type): type is Model; + /** + * Check if the given `type` is a model.. + * + * @param type The type to check. + */ + is(type: Type): type is Model; - /** + /** * If the input is anonymous (or the provided filter removes properties) * and there exists a named model with the same set of properties * (ignoring filtered properties), then return that named model. @@ -73,7 +73,7 @@ export interface ModelKit { interface TypekitExtension { /** - * Utilities for working with models. + * Utilities for working with models. * @experimental */ model: ModelKit; diff --git a/packages/compiler/src/experimental/typekit/kits/scalar.ts b/packages/compiler/src/experimental/typekit/kits/scalar.ts index ac45e81781..55ef4f702f 100644 --- a/packages/compiler/src/experimental/typekit/kits/scalar.ts +++ b/packages/compiler/src/experimental/typekit/kits/scalar.ts @@ -5,412 +5,411 @@ import { defineKit, Typekit } from "../define-kit.js"; // eslint-disable-next-line @typescript-eslint/no-unused-vars import type { ModelPropertyKit } from "./model-property.js"; +/** + * Operations for scalar types like strings, numerics, booleans, dates, etc. + * @experimental + */ +export interface ScalarKit { /** - * Operations for scalar types like strings, numerics, booleans, dates, etc. - * @experimental + * Check if `type` is any scalar type. + * + * @param type The type to check. */ -export interface ScalarKit { + is(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard boolean type. + * + * @param type The type to check. + */ + isBoolean(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard bytes type. + * + * @param type The type to check. + */ + isBytes(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard decimal type. + * + * @param type The type to check. + */ + isDecimal(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard decimal128 type. + * + * @param type The type to check. + */ + isDecimal128(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard duration type. + * + * @param type The type to check. + */ + isDuration(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard float type. + * + * @param type The type to check. + */ + isFloat(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard float32 type. + * + * @param type The type to check. + */ + isFloat32(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard float64 type. + * + * @param type The type to check. + */ + isFloat64(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard int8 type. + * + * @param type The type to check. + */ + isInt8(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard int16 type. + * + * @param type The type to check. + */ + isInt16(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard int32 type. + * + * @param type The type to check. + */ + isInt32(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard int64 type. + * + * @param type The type to check. + */ + isInt64(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard integer type. + * + * @param type The type to check. + */ + isInteger(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard offsetDateTime type. + * + * @param type The type to check. + */ + isOffsetDateTime(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard plainDate type. + * + * @param type The type to check. + */ + isPlainDate(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard plainTime type. + * + * @param type The type to check. + */ + isPlainTime(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard safeint type. + * + * @param type The type to check. + */ + isSafeint(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard uint8 type. + * + * @param type The type to check. + */ + isUint8(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard uint16 type. + * + * @param type The type to check. + */ + isUint16(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard uint32 type. + * + * @param type The type to check. + */ + isUint32(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard uint64 type. + * + * @param type The type to check. + */ + isUint64(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard url type. + * + * @param type The type to check. + */ + isUrl(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard utcDateTime type. + * + * @param type The type to check. + */ + isUtcDateTime(type: Type): type is Scalar; + + /** + * + * @param type The type to check. + */ + isNumeric(type: Type): type is Scalar; + + /** + * Check if `type` is exactly the standard string type. + * + * @param type The type to check. + */ + isString(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard boolean type. + * + * @param type The type to check. + */ + extendsBoolean(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard string type. + * + * @param type The type to check. + */ + extendsString(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard numeric type. + * + * @param type The type to check. + */ + extendsNumeric(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard bytes type. + * + * @param type The type to check. + */ + extendsBytes(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard decimal type. + * + * @param type The type to check. + */ + extendsDecimal(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard decimal128 type. + * + * @param type The type to check. + */ + extendsDecimal128(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard duration type. + * + * @param type The type to check. + */ + extendsDuration(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard float type. + * + * @param type The type to check. + */ + extendsFloat(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard float32 type. + * + * @param type The type to check. + */ + extendsFloat32(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard float64 type. + * + * @param type The type to check. + */ + extendsFloat64(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard int8 type. + * + * @param type The type to check. + */ + extendsInt8(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard int16 type. + * + * @param type The type to check. + */ + extendsInt16(type: Type): type is Scalar; - /** - * Check if `type` is any scalar type. - * - * @param type The type to check. - */ - is(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard boolean type. - * - * @param type The type to check. - */ - isBoolean(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard bytes type. - * - * @param type The type to check. - */ - isBytes(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard decimal type. - * - * @param type The type to check. - */ - isDecimal(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard decimal128 type. - * - * @param type The type to check. - */ - isDecimal128(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard duration type. - * - * @param type The type to check. - */ - isDuration(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard float type. - * - * @param type The type to check. - */ - isFloat(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard float32 type. - * - * @param type The type to check. - */ - isFloat32(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard float64 type. - * - * @param type The type to check. - */ - isFloat64(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard int8 type. - * - * @param type The type to check. - */ - isInt8(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard int16 type. - * - * @param type The type to check. - */ - isInt16(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard int32 type. - * - * @param type The type to check. - */ - isInt32(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard int64 type. - * - * @param type The type to check. - */ - isInt64(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard integer type. - * - * @param type The type to check. - */ - isInteger(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard offsetDateTime type. - * - * @param type The type to check. - */ - isOffsetDateTime(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard plainDate type. - * - * @param type The type to check. - */ - isPlainDate(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard plainTime type. - * - * @param type The type to check. - */ - isPlainTime(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard safeint type. - * - * @param type The type to check. - */ - isSafeint(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard uint8 type. - * - * @param type The type to check. - */ - isUint8(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard uint16 type. - * - * @param type The type to check. - */ - isUint16(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard uint32 type. - * - * @param type The type to check. - */ - isUint32(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard uint64 type. - * - * @param type The type to check. - */ - isUint64(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard url type. - * - * @param type The type to check. - */ - isUrl(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard utcDateTime type. - * - * @param type The type to check. - */ - isUtcDateTime(type: Type): type is Scalar; - - /** - * - * @param type The type to check. - */ - isNumeric(type: Type): type is Scalar; - - /** - * Check if `type` is exactly the standard string type. - * - * @param type The type to check. - */ - isString(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard boolean type. - * - * @param type The type to check. - */ - extendsBoolean(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard string type. - * - * @param type The type to check. - */ - extendsString(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard numeric type. - * - * @param type The type to check. - */ - extendsNumeric(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard bytes type. - * - * @param type The type to check. - */ - extendsBytes(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard decimal type. - * - * @param type The type to check. - */ - extendsDecimal(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard decimal128 type. - * - * @param type The type to check. - */ - extendsDecimal128(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard duration type. - * - * @param type The type to check. - */ - extendsDuration(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard float type. - * - * @param type The type to check. - */ - extendsFloat(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard float32 type. - * - * @param type The type to check. - */ - extendsFloat32(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard float64 type. - * - * @param type The type to check. - */ - extendsFloat64(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard int8 type. - * - * @param type The type to check. - */ - extendsInt8(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard int16 type. - * - * @param type The type to check. - */ - extendsInt16(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard int32 type. - * - * @param type The type to check. - */ - extendsInt32(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard int64 type. - * - * @param type The type to check. - */ - extendsInt64(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard integer type. - * - * @param type The type to check. - */ - extendsInteger(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard offsetDateTime type. - * - * @param type The type to check. - */ - extendsOffsetDateTime(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard plainDate type. - * - * @param type The type to check. - */ - extendsPlainDate(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard plainTime type. - * - * @param type The type to check. - */ - extendsPlainTime(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard safeint type. - * - * @param type The type to check. - */ - extendsSafeint(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard uint8 type. - * - * @param type The type to check. - */ - extendsUint8(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard uint16 type. - * - * @param type The type to check. - */ - extendsUint16(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard uint32 type. - * - * @param type The type to check. - */ - extendsUint32(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard uint64 type. - * - * @param type The type to check. - */ - extendsUint64(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard url type. - * - * @param type The type to check. - */ - extendsUrl(type: Type): type is Scalar; - - /** - * Check if `type` extends the standard utcDateTime type. - * - * @param type The type to check. - */ - extendsUtcDateTime(type: Type): type is Scalar; - - /** - * Get the standard built-in base type of a scalar. For all built-in scalar - * types (numeric, string, int32, etc.) this will just return the scalar - * type. For user-defined scalars, this will return the first base scalar - * that is built-in. For user-defined scalars without a standard base type, - * this will return null. - * - * @param type The scalar to check. - */ - getStdBase(type: Scalar): Scalar | null; - - /** - * Get the encoding information for a scalar type. Returns undefined if no - * encoding data is specified. - * - * Note: This will return the encoding data for the scalar type itself, not - * the model property that uses the scalar type. If this scalar might be - * referenced from a model property, use {@link modelProperty.getEncoding} - * instead. - * - * @param scalar The scalar to get the encoding data for. - */ - getEncoding(scalar: Scalar): EncodeData | undefined; - - /** - * Get the well-known format for a string scalar. Returns undefined if no - * format is specified. - * - * Note: This will return the format data for the scalar type itself, not - * the model property that uses the scalar type. If this scalar might be - * referenced from a model property, use {@link ModelPropertyKit.getEncoding} - * instead. - * - * @param scalar The scalar to get the format for. - */ - getFormat(scalar: Scalar): string | undefined; + /** + * Check if `type` extends the standard int32 type. + * + * @param type The type to check. + */ + extendsInt32(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard int64 type. + * + * @param type The type to check. + */ + extendsInt64(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard integer type. + * + * @param type The type to check. + */ + extendsInteger(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard offsetDateTime type. + * + * @param type The type to check. + */ + extendsOffsetDateTime(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard plainDate type. + * + * @param type The type to check. + */ + extendsPlainDate(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard plainTime type. + * + * @param type The type to check. + */ + extendsPlainTime(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard safeint type. + * + * @param type The type to check. + */ + extendsSafeint(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard uint8 type. + * + * @param type The type to check. + */ + extendsUint8(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard uint16 type. + * + * @param type The type to check. + */ + extendsUint16(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard uint32 type. + * + * @param type The type to check. + */ + extendsUint32(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard uint64 type. + * + * @param type The type to check. + */ + extendsUint64(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard url type. + * + * @param type The type to check. + */ + extendsUrl(type: Type): type is Scalar; + + /** + * Check if `type` extends the standard utcDateTime type. + * + * @param type The type to check. + */ + extendsUtcDateTime(type: Type): type is Scalar; + + /** + * Get the standard built-in base type of a scalar. For all built-in scalar + * types (numeric, string, int32, etc.) this will just return the scalar + * type. For user-defined scalars, this will return the first base scalar + * that is built-in. For user-defined scalars without a standard base type, + * this will return null. + * + * @param type The scalar to check. + */ + getStdBase(type: Scalar): Scalar | null; + + /** + * Get the encoding information for a scalar type. Returns undefined if no + * encoding data is specified. + * + * Note: This will return the encoding data for the scalar type itself, not + * the model property that uses the scalar type. If this scalar might be + * referenced from a model property, use {@link modelProperty.getEncoding} + * instead. + * + * @param scalar The scalar to get the encoding data for. + */ + getEncoding(scalar: Scalar): EncodeData | undefined; + + /** + * Get the well-known format for a string scalar. Returns undefined if no + * format is specified. + * + * Note: This will return the format data for the scalar type itself, not + * the model property that uses the scalar type. If this scalar might be + * referenced from a model property, use {@link ModelPropertyKit.getEncoding} + * instead. + * + * @param scalar The scalar to get the format for. + */ + getFormat(scalar: Scalar): string | undefined; } interface TypekitExtension { - /** - * Operations for scalar types like strings, numerics, booleans, dates, etc. - * @experimental - */ - scalar: ScalarKit; + /** + * Operations for scalar types like strings, numerics, booleans, dates, etc. + * @experimental + */ + scalar: ScalarKit; } declare module "../define-kit.js" { diff --git a/packages/compiler/src/experimental/typekit/kits/union-variant.ts b/packages/compiler/src/experimental/typekit/kits/union-variant.ts index ce4ef37ca4..e814045c09 100644 --- a/packages/compiler/src/experimental/typekit/kits/union-variant.ts +++ b/packages/compiler/src/experimental/typekit/kits/union-variant.ts @@ -27,26 +27,26 @@ interface UnionVariantDescriptor { /** * Utilities for working with union variants. - * + * * Union variants are types that represent a single value within a union that can be one of * several types. - * + * * @experimental */ export interface UnionVariantKit { - /** - * Create a union variant. - * - * @param desc The descriptor of the union variant. - */ - create(desc: UnionVariantDescriptor): UnionVariant; + /** + * Create a union variant. + * + * @param desc The descriptor of the union variant. + */ + create(desc: UnionVariantDescriptor): UnionVariant; - /** - * Check if the given `type` is a union. - * - * @param type The type to check. - */ - is(type: Type): type is UnionVariant; + /** + * Check if the given `type` is a union. + * + * @param type The type to check. + */ + is(type: Type): type is UnionVariant; } interface TypekitExtension { diff --git a/packages/compiler/src/experimental/typekit/kits/union.ts b/packages/compiler/src/experimental/typekit/kits/union.ts index c2abe01438..e50dfb72d4 100644 --- a/packages/compiler/src/experimental/typekit/kits/union.ts +++ b/packages/compiler/src/experimental/typekit/kits/union.ts @@ -28,36 +28,36 @@ interface UnionDescriptor { * @experimental */ export interface UnionKit { - /** - * Create a union type. - * - * @param desc The descriptor of the union. - */ - create(desc: UnionDescriptor): Union; - - /** - * Check if the given `type` is a union. - * - * @param type The type to check. - */ - is(type: Type): type is Union; - - /** - * Check if the union is a valid enum. Specifically, this checks if the - * union has a name (since there are no enum expressions), and whether each - * of the variant types is a valid enum member value. - * - * @param type The union to check. - */ - isValidEnum(type: Union): boolean; - - /** - * Check if a union is extensible. Extensible unions are unions which contain a variant - * that is a supertype of all the other types. This means that the subtypes of the common - * supertype are known example values, but others may be present. - * @param type The union to check. - */ - isExtensible(type: Union): boolean; + /** + * Create a union type. + * + * @param desc The descriptor of the union. + */ + create(desc: UnionDescriptor): Union; + + /** + * Check if the given `type` is a union. + * + * @param type The type to check. + */ + is(type: Type): type is Union; + + /** + * Check if the union is a valid enum. Specifically, this checks if the + * union has a name (since there are no enum expressions), and whether each + * of the variant types is a valid enum member value. + * + * @param type The union to check. + */ + isValidEnum(type: Union): boolean; + + /** + * Check if a union is extensible. Extensible unions are unions which contain a variant + * that is a supertype of all the other types. This means that the subtypes of the common + * supertype are known example values, but others may be present. + * @param type The union to check. + */ + isExtensible(type: Union): boolean; } interface TypekitExtension { diff --git a/packages/compiler/test/experimental/typekit/model.test.ts b/packages/compiler/test/experimental/typekit/model.test.ts index 2ed498b457..1a1fdfba91 100644 --- a/packages/compiler/test/experimental/typekit/model.test.ts +++ b/packages/compiler/test/experimental/typekit/model.test.ts @@ -28,13 +28,13 @@ it("returns false whe the type is not a model", async () => { it("creates a new Model", async () => { const foo = $.model.create({ name: "Foo", - properties: {} - }) + properties: {}, + }); expect($.model.is(foo)).toBe(true); }); -it("can get the effective model type", async () => { +it("can get the effective model type", async () => { const { Foo, create } = await getTypes( ` model Foo { diff --git a/packages/http/src/experimental/typekit/kits/http-operation.ts b/packages/http/src/experimental/typekit/kits/http-operation.ts index 471ddf4d71..23e153ed71 100644 --- a/packages/http/src/experimental/typekit/kits/http-operation.ts +++ b/packages/http/src/experimental/typekit/kits/http-operation.ts @@ -1,30 +1,34 @@ import { ignoreDiagnostics, Operation, StringLiteral, Type, VoidType } from "@typespec/compiler"; import { defineKit, Typekit } from "@typespec/compiler/experimental/typekit"; import { getHttpOperation } from "../../../operations.js"; -import { HttpOperation, HttpOperationResponseContent, HttpStatusCodesEntry } from "../../../types.js"; +import { + HttpOperation, + HttpOperationResponseContent, + HttpStatusCodesEntry, +} from "../../../types.js"; /** * Utilities for working with HTTP operations. * @experimental */ export interface HttpOperationKit { - /** - * Get the corresponding HTTP operation for the given TypeSpec operation. The same - * TypeSpec operation will always return the exact same HttpOperation object. - * - * @param op The TypeSpec operation to get the HTTP operation metadata for. - */ - get(op: Operation): HttpOperation; - /** - * Get the responses for the given operation. This function will return an array of responses grouped by status code and content type. - * @param op operation to extract the HttpResponse from - */ - getResponses(op: Operation): FlatHttpResponse[]; - /** - * Get the Http Return type for the given operation. This function will resolve the returnType based on the Http Operation. - * @param op operation to get the return type for - */ - getReturnType(op: Operation, options?: { includeErrors?: boolean }): Type; + /** + * Get the corresponding HTTP operation for the given TypeSpec operation. The same + * TypeSpec operation will always return the exact same HttpOperation object. + * + * @param op The TypeSpec operation to get the HTTP operation metadata for. + */ + get(op: Operation): HttpOperation; + /** + * Get the responses for the given operation. This function will return an array of responses grouped by status code and content type. + * @param op operation to extract the HttpResponse from + */ + getResponses(op: Operation): FlatHttpResponse[]; + /** + * Get the Http Return type for the given operation. This function will resolve the returnType based on the Http Operation. + * @param op operation to get the return type for + */ + getReturnType(op: Operation, options?: { includeErrors?: boolean }): Type; } /** diff --git a/packages/http/src/experimental/typekit/kits/http-request.ts b/packages/http/src/experimental/typekit/kits/http-request.ts index de034a4f7b..df398586da 100644 --- a/packages/http/src/experimental/typekit/kits/http-request.ts +++ b/packages/http/src/experimental/typekit/kits/http-request.ts @@ -5,34 +5,33 @@ import { HttpOperation } from "../../../types.js"; export type HttpRequestParameterKind = "query" | "header" | "path" | "contentType" | "body"; interface HttpRequestKit { - body: { - /** - * Checks the body is a property explicitly tagged with @body or @bodyRoot - * @param httpOperation the http operation to check - */ - isExplicit(httpOperation: HttpOperation): boolean; - }; - /** - * Gets a Model representing the body parameters of an http operation. - * @param httpOperation the http operation to get the body parameters from - */ - getBodyParameters(httpOperation: HttpOperation): Model | undefined; + body: { /** - * Gets a Model representing the parameters of an http operation. - * @param httpOperation The Http operation to get the parameters from. - * @param kind A string to filters specific parameter kinds, or an array to combine multiple kinds. + * Checks the body is a property explicitly tagged with @body or @bodyRoot + * @param httpOperation the http operation to check */ - getParameters( - httpOperation: HttpOperation, - kind: HttpRequestParameterKind[] | HttpRequestParameterKind, - ): Model | undefined; + isExplicit(httpOperation: HttpOperation): boolean; + }; + /** + * Gets a Model representing the body parameters of an http operation. + * @param httpOperation the http operation to get the body parameters from + */ + getBodyParameters(httpOperation: HttpOperation): Model | undefined; + /** + * Gets a Model representing the parameters of an http operation. + * @param httpOperation The Http operation to get the parameters from. + * @param kind A string to filters specific parameter kinds, or an array to combine multiple kinds. + */ + getParameters( + httpOperation: HttpOperation, + kind: HttpRequestParameterKind[] | HttpRequestParameterKind, + ): Model | undefined; } interface TypekitExtension { httpRequest: HttpRequestKit; } - declare module "@typespec/compiler/experimental/typekit" { interface Typekit extends TypekitExtension {} } diff --git a/packages/http/src/experimental/typekit/kits/http-response.ts b/packages/http/src/experimental/typekit/kits/http-response.ts index 9a7ed996d9..db91ae9b29 100644 --- a/packages/http/src/experimental/typekit/kits/http-response.ts +++ b/packages/http/src/experimental/typekit/kits/http-response.ts @@ -11,30 +11,30 @@ import { * @experimental */ export interface HttpResponseKit { + /** + * Check if the response is an error response. + */ + isErrorResponse(response: HttpOperationResponseContent): boolean; + /** + * utilities to perform checks on status codes + */ + statusCode: { /** - * Check if the response is an error response. + * Check if the status code is a single status code + * @param statusCode status code to check */ - isErrorResponse(response: HttpOperationResponseContent): boolean; + isSingle(statusCode: HttpStatusCodesEntry): statusCode is number; /** - * utilities to perform checks on status codes + * Check if the status code is a range of status codes + * @param statusCode status code to check */ - statusCode: { - /** - * Check if the status code is a single status code - * @param statusCode status code to check - */ - isSingle(statusCode: HttpStatusCodesEntry): statusCode is number; - /** - * Check if the status code is a range of status codes - * @param statusCode status code to check - */ - isRange(statusCode: HttpStatusCodesEntry): statusCode is HttpStatusCodeRange; - /** - * Check if the status code is a default status code - * @param statusCode status code to check - */ - isDefault(statusCode: HttpStatusCodesEntry): statusCode is "*"; - }; + isRange(statusCode: HttpStatusCodesEntry): statusCode is HttpStatusCodeRange; + /** + * Check if the status code is a default status code + * @param statusCode status code to check + */ + isDefault(statusCode: HttpStatusCodesEntry): statusCode is "*"; + }; } interface TypekitExtension { diff --git a/packages/http/test/experimental/typekit/http-operation.test.ts b/packages/http/test/experimental/typekit/http-operation.test.ts index 28a833a40e..30680b031f 100644 --- a/packages/http/test/experimental/typekit/http-operation.test.ts +++ b/packages/http/test/experimental/typekit/http-operation.test.ts @@ -1,6 +1,6 @@ import { Model, Operation } from "@typespec/compiler"; -import { BasicTestRunner } from "@typespec/compiler/testing"; import { $ } from "@typespec/compiler/experimental/typekit"; +import { BasicTestRunner } from "@typespec/compiler/testing"; import { beforeEach, describe, expect, it } from "vitest"; import { createHttpTestRunner } from "./../../test-host.js"; diff --git a/packages/http/test/experimental/typekit/http-request.test.ts b/packages/http/test/experimental/typekit/http-request.test.ts index f6e3b3178a..3a3c11c78b 100644 --- a/packages/http/test/experimental/typekit/http-request.test.ts +++ b/packages/http/test/experimental/typekit/http-request.test.ts @@ -1,6 +1,6 @@ import { Model, Operation } from "@typespec/compiler"; -import { BasicTestRunner } from "@typespec/compiler/testing"; import { $ } from "@typespec/compiler/experimental/typekit"; +import { BasicTestRunner } from "@typespec/compiler/testing"; import { beforeEach, describe, expect, it } from "vitest"; import { createHttpTestRunner } from "./../../test-host.js"; diff --git a/packages/http/test/experimental/typekit/http-response.test.ts b/packages/http/test/experimental/typekit/http-response.test.ts index c3739591c6..f713c18db8 100644 --- a/packages/http/test/experimental/typekit/http-response.test.ts +++ b/packages/http/test/experimental/typekit/http-response.test.ts @@ -1,6 +1,6 @@ import { Model, Operation } from "@typespec/compiler"; -import { BasicTestRunner } from "@typespec/compiler/testing"; import { $ } from "@typespec/compiler/experimental/typekit"; +import { BasicTestRunner } from "@typespec/compiler/testing"; import { beforeEach, expect, it } from "vitest"; import { createHttpTestRunner } from "./../../test-host.js"; @@ -58,7 +58,7 @@ it("should identify a single and default status code", async () => { const error = $.httpOperation.getResponses(getFoo)[1]; expect($.httpResponse.statusCode.isSingle(response.statusCode)).toBe(true); expect($.httpResponse.statusCode.isDefault(error.statusCode)).toBe(true); -}) +}); it("should identify a range status code", async () => { const { getFoo } = (await runner.compile(` @@ -84,5 +84,4 @@ it("should identify a range status code", async () => { const error = $.httpOperation.getResponses(getFoo)[1]; expect($.httpResponse.statusCode.isRange(response.statusCode)).toBe(true); expect($.httpResponse.statusCode.isDefault(error.statusCode)).toBe(true); -}) - +}); From ce6a056a944ea501827947c55fe097034b1c791c Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Wed, 11 Dec 2024 17:39:03 -0800 Subject: [PATCH 3/3] Document changes --- ...ature-experimental-http-typekit-2024-11-11-17-37-15.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/feature-experimental-http-typekit-2024-11-11-17-37-15.md diff --git a/.chronus/changes/feature-experimental-http-typekit-2024-11-11-17-37-15.md b/.chronus/changes/feature-experimental-http-typekit-2024-11-11-17-37-15.md new file mode 100644 index 0000000000..af79dcc3c3 --- /dev/null +++ b/.chronus/changes/feature-experimental-http-typekit-2024-11-11-17-37-15.md @@ -0,0 +1,8 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" + - "@typespec/http" +--- + +Add Experimental Typekit helpers for @typespec/http