From 49261943cc5fd08e579148ec256cf74ec9cc4a33 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 11 May 2026 12:26:24 -0400 Subject: [PATCH 1/6] Improve documentation generated for options --- packages/http-client-js/README.md | 2 + packages/http-server-csharp/README.md | 14 + packages/http-server-js/README.md | 8 + packages/json-schema/README.md | 4 + packages/openapi3/README.md | 38 +- .../tspd/src/ref-doc/emitters/markdown.ts | 113 ++- .../tspd/src/ref-doc/emitters/starlight.ts | 117 +++ packages/tspd/src/ref-doc/experimental.ts | 8 +- packages/tspd/src/ref-doc/extractor.ts | 201 ++++- packages/tspd/src/ref-doc/types.ts | 29 + .../http-client-js/reference/emitter.md | 2 + .../emitters/json-schema/reference/emitter.md | 4 + .../emitters/openapi3/reference/emitter.md | 38 +- .../http-server-csharp/reference/emitter.md | 14 + .../http-server-js/reference/emitter.md | 8 + .../http/reference/streams/data-types.md | 818 ++++++++++++++++++ .../http/reference/streams/decorators.md | 581 +++++++++++++ 17 files changed, 1978 insertions(+), 21 deletions(-) create mode 100644 website/src/content/docs/docs/libraries/http/reference/streams/data-types.md create mode 100644 website/src/content/docs/docs/libraries/http/reference/streams/decorators.md diff --git a/packages/http-client-js/README.md b/packages/http-client-js/README.md index 22fb437d909..9da489c22c3 100644 --- a/packages/http-client-js/README.md +++ b/packages/http-client-js/README.md @@ -46,4 +46,6 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo **Type:** `string` +**Default:** `"test-package"` + Name of the package as it will be in package.json diff --git a/packages/http-server-csharp/README.md b/packages/http-server-csharp/README.md index 3d6a8e83350..413512cd4b0 100644 --- a/packages/http-server-csharp/README.md +++ b/packages/http-server-csharp/README.md @@ -52,36 +52,48 @@ Skips formatting of generated C# Types. By default, C# files are formatted using **Type:** `"models" | "all"` +**Default:** `"all"` + Chooses which service artifacts to emit. choices include 'models' or 'all' artifacts. ### `emit-mocks` **Type:** `"mocks-and-project-files" | "mocks-only" | "none"` +**Default:** `"none"` + Emits mock implementations of business logic, setup code, and project files, enabling the service to respond to requests before a real implementation is provided ### `use-swaggerui` **Type:** `boolean` +**Default:** `false` + Configure a Swagger UI endpoint in the development configuration ### `openapi-path` **Type:** `string` +**Default:** `null` + Use openapi at the given path for generating SwaggerUI endpoints. By default, this will be 'openapi/openapi.yaml' if the 'use-swaggerui' option is enabled. ### `overwrite` **Type:** `boolean` +**Default:** `false` + When generating mock and project files, overwrite any existing files with the same name. ### `project-name` **Type:** `string` +**Default:** `"ServiceProject"` + The name of the generated project. ### `http-port` @@ -100,4 +112,6 @@ The service https port when hosting the project locally. **Type:** `"array" | "enumerable"` +**Default:** `"array"` + Specifies the collection type to use: 'array' or 'enumerable'. The default is 'array'. diff --git a/packages/http-server-js/README.md b/packages/http-server-js/README.md index 002dcdf8ccb..90709ae69b2 100644 --- a/packages/http-server-js/README.md +++ b/packages/http-server-js/README.md @@ -48,6 +48,8 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo **Type:** `boolean` +**Default:** `false` + If set to `true`, the emitter will generate a router that exposes an Express.js middleware function in addition to the ordinary Node.js HTTP server router. If this option is not set to `true`, the `expressMiddleware` property will not be present on the generated router. @@ -56,18 +58,24 @@ If this option is not set to `true`, the `expressMiddleware` property will not b **Type:** `"temporal-polyfill" | "temporal" | "date-duration"` +**Default:** `"temporal-polyfill"` + The type of datetime models to use for TypeSpecs DateTime and Duration types. ### `omit-unreachable-types` **Type:** `boolean` +**Default:** `false` + By default, the emitter will create interfaces that represent all models in the service namespace. If this option is set to `true`, the emitter will only emit those types that are reachable from an HTTP operation. ### `no-format` **Type:** `boolean` +**Default:** `false` + If set to `true`, the emitter will not format the generated code using Prettier. ## Functionality and generated code diff --git a/packages/json-schema/README.md b/packages/json-schema/README.md index b27ddf8e22c..1e3af685da6 100644 --- a/packages/json-schema/README.md +++ b/packages/json-schema/README.md @@ -97,6 +97,8 @@ When true, emit all references as json schema files, even if the referenced type **Type:** `boolean` +**Default:** `false` + If true, then for models emitted as object schemas we default `unevaluatedProperties` to `{ not: {} }`, if not explicitly specified elsewhere. Default: `false` @@ -105,6 +107,8 @@ Default: `false` **Type:** `"ignore" | "oneOf" | "anyOf"` +**Default:** `"ignore"` + Strategy for emitting models with the @discriminator decorator: - ignore: Emit as regular object schema (default). Derived models use allOf to reference their base model. diff --git a/packages/openapi3/README.md b/packages/openapi3/README.md index 6512a4c1a86..d0ec89cf3e1 100644 --- a/packages/openapi3/README.md +++ b/packages/openapi3/README.md @@ -44,10 +44,15 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo ### `file-type` -**Type:** `string,array` +**Type:** `"yaml" | "json" | ("yaml" | "json")[]` If the content should be serialized as YAML or JSON. Can be a single value or an array to emit multiple formats. Default 'yaml', if not specified infer from the `output-file` extension +**Options:** + +- `"yaml" | "json"` +- `("yaml" | "json")[]` + ### `output-file` **Type:** `string` @@ -86,12 +91,16 @@ Example Multiple service with versioning ### `openapi-versions` -**Type:** `array` +**Type:** `"3.0.0" | "3.1.0" | "3.2.0"` + +**Default:** `["3.0.0"]` ### `new-line` **Type:** `"crlf" | "lf"` +**Default:** `"lf"` + Set the newline character for emitting files. ### `omit-unreachable-types` @@ -105,6 +114,8 @@ By default all types declared under the service namespace will be included. With **Type:** `"inline-only" | "never"` +**Default:** `"never"` + If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it. This extension is meant for debugging and should not be depended on. @@ -112,6 +123,8 @@ This extension is meant for debugging and should not be depended on. **Type:** `"double-int" | "int64"` +**Default:** `"int64"` + How to handle safeint type. Options are: - `double-int`: Will produce `type: integer, format: double-int` @@ -123,6 +136,8 @@ Default: `int64` **Type:** `boolean` +**Default:** `false` + If true, then for models emitted as object schemas we default `additionalProperties` to false for OpenAPI 3.0, and `unevaluatedProperties` to false for OpenAPI 3.1, if not explicitly specified elsewhere. Default: `false` @@ -138,7 +153,24 @@ See https://github.com/OAI/OpenAPI-Specification/discussions/4622 for discussion ### `operation-id-strategy` -**Type:** `undefined` +**Type:** `"parent-container" | "fqn" | "explicit-only" | object { kind, separator }` + +**Options:** + +- `"parent-container" | "fqn" | "explicit-only"` (default: `"parent-container"`) + + Determines how to generate operation IDs when `@operationId` is not used. + Avaliable options are: + +- `parent-container`: Uses the parent namespace and operation name to generate the ID. +- `fqn`: Uses the fully qualified name of the operation to generate the ID. +- `explicit-only`: Only use explicitly defined operation IDs. +- `object { kind, separator }` + +| Name | Type | Default | Description | +| ----------- | ------------------------------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"parent-container" \| "fqn" \| "explicit-only"` | `"parent-container"` | Determines how to generate operation IDs when `@operationId` is not used.
Avaliable options are:
- `parent-container`: Uses the parent namespace and operation name to generate the ID.
- `fqn`: Uses the fully qualified name of the operation to generate the ID.
- `explicit-only`: Only use explicitly defined operation IDs. | +| `separator` | `string` | | Separator used to join segment in the operation name. | ## Decorators diff --git a/packages/tspd/src/ref-doc/emitters/markdown.ts b/packages/tspd/src/ref-doc/emitters/markdown.ts index f6fb83f1a9f..97a14796016 100644 --- a/packages/tspd/src/ref-doc/emitters/markdown.ts +++ b/packages/tspd/src/ref-doc/emitters/markdown.ts @@ -11,6 +11,7 @@ import { DecoratorRefDoc, DeprecationNotice, EmitterOptionRefDoc, + EmitterOptionVariantRefDoc, EnumRefDoc, ExampleRefDoc, InterfaceRefDoc, @@ -268,11 +269,22 @@ export class MarkdownRenderer { content.push(this.templateParameters(union.templateParameters)); } + if (union.variants.size > 0) { + content.push(this.unionVariants(union)); + } + content.push(this.examples(union.examples)); return this.typeSection(union, content); } + unionVariants(union: UnionRefDoc): MarkdownDoc { + const rows = [...union.variants.values()].map((v) => { + return [v.name, this.ref(v.type.type), v.doc]; + }); + return section("Variants", [table([["Name", "Type", "Description"], ...rows])]); + } + scalar(scalar: ScalarRefDoc): MarkdownDoc { const content: MarkdownDoc = ["", scalar.doc, codeblock(scalar.signature, "typespec"), ""]; @@ -401,17 +413,104 @@ export class MarkdownRenderer { ]), ); for (const option of options) { - content.push( - section(`${inlinecode(option.name)}`, [ - `**Type:** ${inlinecode(option.type)}`, - "", - option.doc, - ]), - ); + content.push(this.emitterOption(option)); } return section("Emitter options", content); } + emitterOptionTypeDisplay(option: { type: string; allowedValues?: readonly string[] }): string { + if (option.allowedValues && option.allowedValues.length > 0) { + return inlinecode(option.allowedValues.join(" | ")); + } + return inlinecode(option.type); + } + + emitterOptionTypeDisplayInTable(option: { + type: string; + allowedValues?: readonly string[]; + }): string { + if (option.allowedValues && option.allowedValues.length > 0) { + return inlinecode(option.allowedValues.join(" | ")); + } + return inlinecode(option.type); + } + + emitterOption(option: EmitterOptionRefDoc): MarkdownDoc { + const content: MarkdownDoc = []; + + // Type line + content.push(`**Type:** ${this.emitterOptionTypeDisplay(option)}`); + + // Default value + if (option.default !== undefined) { + content.push("", `**Default:** ${inlinecode(option.default)}`); + } + + // Description + if (option.doc) { + content.push("", option.doc); + } + + // Variants (oneOf) + if (option.variants && option.variants.length > 0) { + content.push("", "**Options:**", ""); + for (const variant of option.variants) { + content.push(this.emitterOptionVariant(variant)); + } + } + + // Nested options + if (option.nestedOptions && option.nestedOptions.length > 0) { + content.push("", "**Properties:**", ""); + const rows: string[][] = [["Name", "Type", "Default", "Description"]]; + for (const nested of option.nestedOptions) { + rows.push([ + inlinecode(nested.name), + this.emitterOptionTypeDisplayInTable(nested), + nested.default !== undefined ? inlinecode(nested.default) : "", + nested.doc, + ]); + } + content.push(table(rows)); + } + + return section(`${inlinecode(option.name)}`, content); + } + + emitterOptionVariant(variant: EmitterOptionVariantRefDoc): MarkdownDoc { + const content: MarkdownDoc = []; + + // Variant type/values + const typeDisplay = this.emitterOptionTypeDisplay(variant); + + let header = `- ${typeDisplay}`; + if (variant.default !== undefined) { + header += ` (default: ${inlinecode(variant.default)})`; + } + content.push(header); + + if (variant.doc) { + content.push("", ` ${variant.doc}`); + } + + // Nested options for object variants + if (variant.nestedOptions && variant.nestedOptions.length > 0) { + content.push(""); + const rows: string[][] = [["Name", "Type", "Default", "Description"]]; + for (const nested of variant.nestedOptions) { + rows.push([ + inlinecode(nested.name), + this.emitterOptionTypeDisplayInTable(nested), + nested.default !== undefined ? inlinecode(nested.default) : "", + nested.doc, + ]); + } + content.push(" ", table(rows)); + } + + return content; + } + linterUsage(refDoc: TypeSpecRefDoc) { if (refDoc.linter === undefined) { return []; diff --git a/packages/tspd/src/ref-doc/emitters/starlight.ts b/packages/tspd/src/ref-doc/emitters/starlight.ts index dbbec06888d..f43142108ea 100644 --- a/packages/tspd/src/ref-doc/emitters/starlight.ts +++ b/packages/tspd/src/ref-doc/emitters/starlight.ts @@ -3,6 +3,7 @@ import { LinterRuleRefDoc, NamedTypeRefDoc, RefDocEntity, + SubExportRefDoc, TypeSpecLibraryRefDoc, TypeSpecRefDoc, } from "../types.js"; @@ -56,6 +57,16 @@ export function renderToAstroStarlightMarkdown( files["linter.md"] = linter; } + // Render sub-exports + if (refDoc.subExports) { + for (const [exportPath, subExport] of refDoc.subExports) { + const subFiles = renderSubExport(renderer, refDoc, exportPath, subExport, options); + for (const [name, content] of Object.entries(subFiles)) { + files[name] = content; + } + } + } + return files; } @@ -270,6 +281,112 @@ function renderLinter( return renderMarkdowDoc(content, 2); } +function renderSubExport( + renderer: StarlightRenderer, + refDoc: TypeSpecRefDoc, + exportPath: string, + subExport: SubExportRefDoc, + options: RenderToStarlightMarkdownOptions, +): Record { + const files: Record = {}; + // Use the export path as a directory prefix (e.g., "./streams" -> "streams/") + const dirPrefix = exportPath.replace(/^\.\//, "") + "/"; + const displayName = exportPath.replace(/^\.\//, ""); + + // Decorators + if (subExport.namespaces.some((x) => x.decorators.length > 0)) { + const content: MarkdownDoc = [ + "---", + `title: "Decorators (${displayName})"`, + `description: "Decorators exported by ${refDoc.name}/${displayName}"`, + "toc_min_heading_level: 2", + "toc_max_heading_level: 3", + ]; + if (options.llmstxt) { + content.push("llmstxt: true"); + } + content.push("---"); + content.push(renderer.decoratorsSection(subExport)); + files[`${dirPrefix}decorators.md`] = renderMarkdowDoc(content, 2); + } + + // Interfaces and Operations + if (subExport.namespaces.some((x) => x.operations.length > 0 || x.interfaces.length > 0)) { + const content: MarkdownDoc = [ + "---", + `title: "Interfaces and Operations (${displayName})"`, + `description: "Interfaces and Operations exported by ${refDoc.name}/${displayName}"`, + ]; + if (options.llmstxt) { + content.push("llmstxt: true"); + } + content.push("---"); + content.push( + groupByNamespace(subExport.namespaces, (namespace) => { + if (namespace.operations.length === 0 && namespace.interfaces.length === 0) { + return undefined; + } + const nsContent: MarkdownDoc = []; + for (const iface of namespace.interfaces) { + nsContent.push(renderer.interface(iface), ""); + } + for (const operation of namespace.operations) { + nsContent.push(renderer.operation(operation), ""); + } + return nsContent; + }), + ); + files[`${dirPrefix}interfaces.md`] = renderMarkdowDoc(content, 2); + } + + // Data types (models, enums, unions, scalars) + if ( + subExport.namespaces.some( + (x) => + x.models.length > 0 || x.enums.length > 0 || x.unions.length > 0 || x.scalars.length > 0, + ) + ) { + const content: MarkdownDoc = [ + "---", + `title: "Data types (${displayName})"`, + `description: "Data types exported by ${refDoc.name}/${displayName}"`, + ]; + if (options.llmstxt) { + content.push("llmstxt: true"); + } + content.push("---"); + content.push( + groupByNamespace(subExport.namespaces, (namespace) => { + const modelCount = + namespace.models.length + + namespace.enums.length + + namespace.unions.length + + namespace.scalars.length; + if (modelCount === 0) { + return undefined; + } + const nsContent: MarkdownDoc = []; + for (const model of namespace.models) { + nsContent.push(renderer.model(model), ""); + } + for (const e of namespace.enums) { + nsContent.push(renderer.enum(e), ""); + } + for (const union of namespace.unions) { + nsContent.push(renderer.union(union), ""); + } + for (const scalar of namespace.scalars) { + nsContent.push(renderer.scalar(scalar), ""); + } + return nsContent; + }), + ); + files[`${dirPrefix}data-types.md`] = renderMarkdowDoc(content, 2); + } + + return files; +} + export class StarlightRenderer extends MarkdownRenderer { headingTitle(item: NamedTypeRefDoc): string { // Set an explicit anchor id. diff --git a/packages/tspd/src/ref-doc/experimental.ts b/packages/tspd/src/ref-doc/experimental.ts index a7cd57538bc..188dd252b3a 100644 --- a/packages/tspd/src/ref-doc/experimental.ts +++ b/packages/tspd/src/ref-doc/experimental.ts @@ -35,8 +35,14 @@ export async function generateLibraryDocs( await mkdir(outputDir, { recursive: true }); const config = await prettier.resolveConfig(libraryPath); for (const [name, content] of Object.entries(files)) { + const filePath = joinPaths(outputDir, name); + // Ensure parent directory exists for sub-export files + const dir = filePath.substring(0, filePath.lastIndexOf("/")); + if (dir !== outputDir) { + await mkdir(dir, { recursive: true }); + } const formatted = await formatMarkdown(name, content, config); - await writeFile(joinPaths(outputDir, name), formatted); + await writeFile(filePath, formatted); } const readme = await formatMarkdown( joinPaths(libraryPath, "README.md"), diff --git a/packages/tspd/src/ref-doc/extractor.ts b/packages/tspd/src/ref-doc/extractor.ts index 98f0e0529cf..c1cadac9ac4 100644 --- a/packages/tspd/src/ref-doc/extractor.ts +++ b/packages/tspd/src/ref-doc/extractor.ts @@ -36,6 +36,7 @@ import { Type, TypeSpecLibrary, Union, + UnionVariant, type PackageJson, } from "@typespec/compiler"; import { SyntaxKind, type DocUnknownTagNode } from "@typespec/compiler/ast"; @@ -46,6 +47,7 @@ import { DecoratorRefDoc, DeprecationNotice, EmitterOptionRefDoc, + EmitterOptionVariantRefDoc, EnumMemberRefDoc, EnumRefDoc, ExampleRefDoc, @@ -61,9 +63,11 @@ import { RefDocEntity, ReferencableElement, ScalarRefDoc, + SubExportRefDoc, TypeSpecLibraryRefDoc, TypeSpecRefDocBase, UnionRefDoc, + UnionVariantRefDoc, } from "./types.js"; import { getQualifier, getTypeSignature } from "./utils/type-signature.js"; @@ -123,6 +127,12 @@ export async function extractLibraryRefDocs( } } + // Extract sub-exports + const subExports = await extractSubExports(libraryPath, pkgJson, diagnostics); + if (subExports.size > 0) { + refDoc.subExports = subExports; + } + return diagnostics.wrap(refDoc); } @@ -131,6 +141,49 @@ async function readPackageJson(libraryPath: string): Promise { return JSON.parse(buffer.toString()); } +async function extractSubExports( + libraryPath: string, + pkgJson: PackageJson, + diagnostics: { pipe: (result: [T, readonly Diagnostic[]]) => T; add: (d: Diagnostic) => void }, +): Promise> { + const subExports = new Map(); + const exports = (pkgJson as any).exports; + if (!exports || typeof exports !== "object") { + return subExports; + } + + for (const [exportPath, exportValue] of Object.entries(exports)) { + // Skip the main export — already handled + if (exportPath === ".") continue; + + // Only process exports that have a typespec condition + const tspEntry = exportValue?.typespec; + if (!tspEntry) continue; + + const main = resolvePath(libraryPath, tspEntry); + try { + const program = await compile(NodeHost, main, { + parseOptions: { comments: true, docs: true }, + }); + const subRefDoc = diagnostics.pipe(extractRefDocs(program)); + // Only include if it actually has content + if (subRefDoc.namespaces.length > 0) { + subExports.set(exportPath, { + path: exportPath, + ...subRefDoc, + }); + } + for (const diag of program.diagnostics ?? []) { + diagnostics.add(diag); + } + } catch { + // Skip sub-exports that fail to compile + } + } + + return subExports; +} + export interface ExtractRefDocOptions { namespaces?: { include?: string[]; @@ -525,6 +578,25 @@ function extractUnionRefDocs(program: Program, type: Union & { name: string }): templateParameters: extractTemplateParameterDocs(program, type), doc: doc, examples: extractExamples(type), + variants: new Map( + [...type.variants.values()] + .filter((v): v is UnionVariant & { name: string } => typeof v.name === "string") + .map((v) => [v.name, extractUnionVariantRefDocs(program, v)]), + ), + }; +} + +function extractUnionVariantRefDocs( + program: Program, + type: UnionVariant & { name: string }, +): UnionVariantRefDoc { + const doc = extractMainDoc(program, type); + return { + ...extractBase(program, type), + signature: getTypeSignature(type), + type, + doc: doc, + examples: extractExamples(type), }; } @@ -658,16 +730,131 @@ function extractEmitterOptionsRefDoc( options: JSONSchemaType>, ): EmitterOptionRefDoc[] { return Object.entries(options.properties).map(([name, value]: [string, any]) => { - return { - name, - type: value.enum - ? value.enum.map((x: string | number) => (typeof x === "string" ? `"${x}"` : x)).join(" | ") - : value.type, - doc: value.description ?? "", - }; + return extractEmitterOptionInfo(name, value); }); } +function extractEmitterOptionInfo(name: string, prop: any): EmitterOptionRefDoc { + // Handle oneOf: extract variants + if (prop.oneOf) { + return extractOneOfEmitterOption(name, prop); + } + + const option: Mutable = { + name, + type: resolveEmitterOptionType(prop), + doc: resolveDescription(prop.description), + }; + + if (prop.enum) { + option.allowedValues = prop.enum.map((x: string | number) => + typeof x === "string" ? `"${x}"` : String(x), + ); + } else if (prop.type === "array" && prop.items?.enum) { + option.allowedValues = prop.items.enum.map((x: string | number) => + typeof x === "string" ? `"${x}"` : String(x), + ); + } + + if (prop.default !== undefined) { + option.default = JSON.stringify(prop.default); + } + + // Nested object properties + if (prop.type === "object" && prop.properties) { + option.nestedOptions = Object.entries(prop.properties).map( + ([subName, subProp]: [string, any]) => extractEmitterOptionInfo(subName, subProp), + ); + } + + return option; +} + +function extractOneOfEmitterOption(name: string, prop: any): EmitterOptionRefDoc { + const rawVariants: any[] = prop.oneOf; + + const variants: EmitterOptionVariantRefDoc[] = []; + + for (const variant of rawVariants) { + const v: Mutable = { + type: resolveEmitterOptionType(variant), + }; + + if (variant.enum) { + v.allowedValues = variant.enum.map((x: string | number) => + typeof x === "string" ? `"${x}"` : String(x), + ); + } + + if (variant.default !== undefined) { + v.default = JSON.stringify(variant.default); + } + + if (variant.description) { + v.doc = resolveDescription(variant.description); + } + + if (variant.type === "object" && variant.properties) { + v.nestedOptions = Object.entries(variant.properties).map( + ([subName, subProp]: [string, any]) => extractEmitterOptionInfo(subName, subProp), + ); + } + + variants.push(v); + } + + const option: Mutable = { + name, + type: rawVariants.map((v: any) => resolveEmitterOptionType(v)).join(" | "), + doc: resolveDescription(prop.description), + variants, + }; + + if (prop.default !== undefined) { + option.default = JSON.stringify(prop.default); + } + + return option; +} + +function resolveEmitterOptionType(prop: any): string { + if (prop.oneOf) { + return prop.oneOf.map((s: any) => resolveEmitterOptionType(s)).join(" | "); + } + + if (prop.type === "array") { + if (prop.items) { + if (prop.items.enum) { + const values = prop.items.enum + .map((x: string | number) => (typeof x === "string" ? `"${x}"` : String(x))) + .join(" | "); + return `(${values})[]`; + } + const itemType = prop.items.type ?? "unknown"; + return `${itemType}[]`; + } + return "array"; + } + + if (prop.type === "object" && prop.properties) { + const keys = Object.keys(prop.properties); + return `object { ${keys.join(", ")} }`; + } + + if (prop.enum) { + return prop.enum + .map((x: string | number) => (typeof x === "string" ? `"${x}"` : String(x))) + .join(" | "); + } + + return prop.type ?? "unknown"; +} + +function resolveDescription(description: string | string[] | undefined): string { + if (description === undefined) return ""; + return Array.isArray(description) ? description.join("\n") : description; +} + function extractLinterRefDoc(libName: string, linter: LinterResolvedDefinition): LinterRefDoc { return { ruleSets: linter.ruleSets && extractLinterRuleSetsRefDoc(libName, linter.ruleSets), diff --git a/packages/tspd/src/ref-doc/types.ts b/packages/tspd/src/ref-doc/types.ts index b150882ef64..62fd2bbcf22 100644 --- a/packages/tspd/src/ref-doc/types.ts +++ b/packages/tspd/src/ref-doc/types.ts @@ -13,6 +13,7 @@ import type { Scalar, Type, Union, + UnionVariant, } from "@typespec/compiler"; export type TypeSpecRefDoc = TypeSpecLibraryRefDoc; @@ -37,6 +38,14 @@ export type TypeSpecLibraryRefDoc = TypeSpecRefDocBase & { /** Documentation about the linter rules and ruleset provided in this library. */ readonly linter?: LinterRefDoc; + + /** Documentation for sub-exports (e.g., "./streams", "./testing"). Keyed by export path. */ + readonly subExports?: ReadonlyMap; +}; + +export type SubExportRefDoc = TypeSpecRefDocBase & { + /** The export path (e.g., "./streams") */ + readonly path: string; }; export type TypeSpecRefDocBase = { @@ -69,6 +78,21 @@ export type EmitterOptionRefDoc = { readonly name: string; readonly type: string; readonly doc: string; + readonly default?: string; + readonly allowedValues?: readonly string[]; + /** Nested options for object-typed options. */ + readonly nestedOptions?: readonly EmitterOptionRefDoc[]; + /** When present, this option is a union of multiple variants (oneOf). */ + readonly variants?: readonly EmitterOptionVariantRefDoc[]; +}; + +export type EmitterOptionVariantRefDoc = { + readonly type: string; + readonly doc?: string; + readonly default?: string; + readonly allowedValues?: readonly string[]; + /** Nested options for object-typed variants. */ + readonly nestedOptions?: readonly EmitterOptionRefDoc[]; }; export type RefDocEntity = @@ -182,6 +206,11 @@ export type UnionRefDoc = NamedTypeRefDoc & { readonly type: Union; readonly templateParameters?: readonly TemplateParameterRefDoc[]; + readonly variants: ReadonlyMap; +}; + +export type UnionVariantRefDoc = NamedTypeRefDoc & { + readonly type: UnionVariant; }; export type ScalarRefDoc = NamedTypeRefDoc & { diff --git a/website/src/content/docs/docs/emitters/clients/http-client-js/reference/emitter.md b/website/src/content/docs/docs/emitters/clients/http-client-js/reference/emitter.md index 26247a08933..1c62897fe35 100644 --- a/website/src/content/docs/docs/emitters/clients/http-client-js/reference/emitter.md +++ b/website/src/content/docs/docs/emitters/clients/http-client-js/reference/emitter.md @@ -40,4 +40,6 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo **Type:** `string` +**Default:** `"test-package"` + Name of the package as it will be in package.json diff --git a/website/src/content/docs/docs/emitters/json-schema/reference/emitter.md b/website/src/content/docs/docs/emitters/json-schema/reference/emitter.md index 9951ce71bfc..609cc318b8f 100644 --- a/website/src/content/docs/docs/emitters/json-schema/reference/emitter.md +++ b/website/src/content/docs/docs/emitters/json-schema/reference/emitter.md @@ -73,6 +73,8 @@ When true, emit all references as json schema files, even if the referenced type **Type:** `boolean` +**Default:** `false` + If true, then for models emitted as object schemas we default `unevaluatedProperties` to `{ not: {} }`, if not explicitly specified elsewhere. Default: `false` @@ -81,6 +83,8 @@ Default: `false` **Type:** `"ignore" | "oneOf" | "anyOf"` +**Default:** `"ignore"` + Strategy for emitting models with the @discriminator decorator: - ignore: Emit as regular object schema (default). Derived models use allOf to reference their base model. diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/emitter.md b/website/src/content/docs/docs/emitters/openapi3/reference/emitter.md index a15c4c7e603..f1c6d3539bf 100644 --- a/website/src/content/docs/docs/emitters/openapi3/reference/emitter.md +++ b/website/src/content/docs/docs/emitters/openapi3/reference/emitter.md @@ -38,10 +38,15 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo ### `file-type` -**Type:** `string,array` +**Type:** `"yaml" | "json" | ("yaml" | "json")[]` If the content should be serialized as YAML or JSON. Can be a single value or an array to emit multiple formats. Default 'yaml', if not specified infer from the `output-file` extension +**Options:** + +- `"yaml" | "json"` +- `("yaml" | "json")[]` + ### `output-file` **Type:** `string` @@ -80,12 +85,16 @@ Example Multiple service with versioning ### `openapi-versions` -**Type:** `array` +**Type:** `"3.0.0" | "3.1.0" | "3.2.0"` + +**Default:** `["3.0.0"]` ### `new-line` **Type:** `"crlf" | "lf"` +**Default:** `"lf"` + Set the newline character for emitting files. ### `omit-unreachable-types` @@ -99,6 +108,8 @@ By default all types declared under the service namespace will be included. With **Type:** `"inline-only" | "never"` +**Default:** `"never"` + If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it. This extension is meant for debugging and should not be depended on. @@ -106,6 +117,8 @@ This extension is meant for debugging and should not be depended on. **Type:** `"double-int" | "int64"` +**Default:** `"int64"` + How to handle safeint type. Options are: - `double-int`: Will produce `type: integer, format: double-int` @@ -117,6 +130,8 @@ Default: `int64` **Type:** `boolean` +**Default:** `false` + If true, then for models emitted as object schemas we default `additionalProperties` to false for OpenAPI 3.0, and `unevaluatedProperties` to false for OpenAPI 3.1, if not explicitly specified elsewhere. Default: `false` @@ -132,4 +147,21 @@ See https://github.com/OAI/OpenAPI-Specification/discussions/4622 for discussion ### `operation-id-strategy` -**Type:** `undefined` +**Type:** `"parent-container" | "fqn" | "explicit-only" | object { kind, separator }` + +**Options:** + +- `"parent-container" | "fqn" | "explicit-only"` (default: `"parent-container"`) + + Determines how to generate operation IDs when `@operationId` is not used. + Avaliable options are: + +- `parent-container`: Uses the parent namespace and operation name to generate the ID. +- `fqn`: Uses the fully qualified name of the operation to generate the ID. +- `explicit-only`: Only use explicitly defined operation IDs. +- `object { kind, separator }` + +| Name | Type | Default | Description | +| ----------- | ------------------------------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"parent-container" \| "fqn" \| "explicit-only"` | `"parent-container"` | Determines how to generate operation IDs when `@operationId` is not used.
Avaliable options are:
- `parent-container`: Uses the parent namespace and operation name to generate the ID.
- `fqn`: Uses the fully qualified name of the operation to generate the ID.
- `explicit-only`: Only use explicitly defined operation IDs. | +| `separator` | `string` | | Separator used to join segment in the operation name. | diff --git a/website/src/content/docs/docs/emitters/servers/http-server-csharp/reference/emitter.md b/website/src/content/docs/docs/emitters/servers/http-server-csharp/reference/emitter.md index 18f71229d4d..f13b5543953 100644 --- a/website/src/content/docs/docs/emitters/servers/http-server-csharp/reference/emitter.md +++ b/website/src/content/docs/docs/emitters/servers/http-server-csharp/reference/emitter.md @@ -46,36 +46,48 @@ Skips formatting of generated C# Types. By default, C# files are formatted using **Type:** `"models" | "all"` +**Default:** `"all"` + Chooses which service artifacts to emit. choices include 'models' or 'all' artifacts. ### `emit-mocks` **Type:** `"mocks-and-project-files" | "mocks-only" | "none"` +**Default:** `"none"` + Emits mock implementations of business logic, setup code, and project files, enabling the service to respond to requests before a real implementation is provided ### `use-swaggerui` **Type:** `boolean` +**Default:** `false` + Configure a Swagger UI endpoint in the development configuration ### `openapi-path` **Type:** `string` +**Default:** `null` + Use openapi at the given path for generating SwaggerUI endpoints. By default, this will be 'openapi/openapi.yaml' if the 'use-swaggerui' option is enabled. ### `overwrite` **Type:** `boolean` +**Default:** `false` + When generating mock and project files, overwrite any existing files with the same name. ### `project-name` **Type:** `string` +**Default:** `"ServiceProject"` + The name of the generated project. ### `http-port` @@ -94,4 +106,6 @@ The service https port when hosting the project locally. **Type:** `"array" | "enumerable"` +**Default:** `"array"` + Specifies the collection type to use: 'array' or 'enumerable'. The default is 'array'. diff --git a/website/src/content/docs/docs/emitters/servers/http-server-js/reference/emitter.md b/website/src/content/docs/docs/emitters/servers/http-server-js/reference/emitter.md index 98148e30624..84d22500d7f 100644 --- a/website/src/content/docs/docs/emitters/servers/http-server-js/reference/emitter.md +++ b/website/src/content/docs/docs/emitters/servers/http-server-js/reference/emitter.md @@ -40,6 +40,8 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo **Type:** `boolean` +**Default:** `false` + If set to `true`, the emitter will generate a router that exposes an Express.js middleware function in addition to the ordinary Node.js HTTP server router. If this option is not set to `true`, the `expressMiddleware` property will not be present on the generated router. @@ -48,16 +50,22 @@ If this option is not set to `true`, the `expressMiddleware` property will not b **Type:** `"temporal-polyfill" | "temporal" | "date-duration"` +**Default:** `"temporal-polyfill"` + The type of datetime models to use for TypeSpecs DateTime and Duration types. ### `omit-unreachable-types` **Type:** `boolean` +**Default:** `false` + By default, the emitter will create interfaces that represent all models in the service namespace. If this option is set to `true`, the emitter will only emit those types that are reachable from an HTTP operation. ### `no-format` **Type:** `boolean` +**Default:** `false` + If set to `true`, the emitter will not format the generated code using Prettier. diff --git a/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md b/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md new file mode 100644 index 00000000000..0ce46791e81 --- /dev/null +++ b/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md @@ -0,0 +1,818 @@ +--- +title: "Data types (streams)" +description: "Data types exported by @typespec/http/streams" +llmstxt: true +--- + +## TypeSpec.Http + +### `AcceptedResponse` {#TypeSpec.Http.AcceptedResponse} + +The request has been accepted for processing, but processing has not yet completed. + +```typespec +model TypeSpec.Http.AcceptedResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `202` | The status code. | + +### `ApiKeyAuth` {#TypeSpec.Http.ApiKeyAuth} + +An API key is a token that a client provides when making API calls. The key can be sent in the query string: + +``` +GET /something?api_key=abcdef12345 +``` + +or as a request header + +``` +GET /something HTTP/1.1 +X-API-Key: abcdef12345 +``` + +or as a cookie + +``` +GET /something HTTP/1.1 +Cookie: X-API-KEY=abcdef12345 +``` + +```typespec +model TypeSpec.Http.ApiKeyAuth +``` + +#### Template Parameters + +| Name | Description | +| -------- | --------------------------- | +| Location | The location of the API key | +| Name | The name of the API key | + +#### Properties + +| Name | Type | Description | +| ---- | ------------------------------- | ----------- | +| type | `TypeSpec.Http.AuthType.apiKey` | | +| in | `Location` | | +| name | `Name` | | + +### `AuthorizationCodeFlow` {#TypeSpec.Http.AuthorizationCodeFlow} + +Authorization Code flow + +```typespec +model TypeSpec.Http.AuthorizationCodeFlow +``` + +#### Properties + +| Name | Type | Description | +| ---------------- | ------------------------------------------------ | --------------------------------- | +| type | `TypeSpec.Http.OAuth2FlowType.authorizationCode` | authorization code flow | +| authorizationUrl | `string` | the authorization URL | +| tokenUrl | `string` | the token URL | +| refreshUrl? | `string` | the refresh URL | +| scopes? | `string[]` | list of scopes for the credential | + +### `BadRequestResponse` {#TypeSpec.Http.BadRequestResponse} + +The server could not understand the request due to invalid syntax. + +```typespec +model TypeSpec.Http.BadRequestResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `400` | The status code. | + +### `BasicAuth` {#TypeSpec.Http.BasicAuth} + +Basic authentication is a simple authentication scheme built into the HTTP protocol. +The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. +For example, to authorize as demo / `p@55w0rd` the client would send + +``` +Authorization: Basic ZGVtbzpwQDU1dzByZA== +``` + +```typespec +model TypeSpec.Http.BasicAuth +``` + +#### Properties + +| Name | Type | Description | +| ------ | ----------------------------- | ------------------- | +| type | `TypeSpec.Http.AuthType.http` | Http authentication | +| scheme | `"Basic"` | basic auth scheme | + +### `BearerAuth` {#TypeSpec.Http.BearerAuth} + +Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. +The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request. +The client must send this token in the Authorization header when making requests to protected resources: + +``` +Authorization: Bearer +``` + +```typespec +model TypeSpec.Http.BearerAuth +``` + +#### Properties + +| Name | Type | Description | +| ------ | ----------------------------- | ------------------- | +| type | `TypeSpec.Http.AuthType.http` | Http authentication | +| scheme | `"Bearer"` | bearer auth scheme | + +### `Body` {#TypeSpec.Http.Body} + +Defines a model with a single property of the given type, marked with `@body`. + +This can be useful in situations where you cannot use a bare type as the body +and it is awkward to add a property. + +```typespec +model TypeSpec.Http.Body +``` + +#### Template Parameters + +| Name | Description | +| ---- | ---------------------------------------- | +| Type | The type of the model's `body` property. | + +#### Properties + +| Name | Type | Description | +| ---- | ------ | ----------- | +| body | `Type` | | + +### `ClientCredentialsFlow` {#TypeSpec.Http.ClientCredentialsFlow} + +Client credentials flow + +```typespec +model TypeSpec.Http.ClientCredentialsFlow +``` + +#### Properties + +| Name | Type | Description | +| ----------- | ------------------------------------------------ | --------------------------------- | +| type | `TypeSpec.Http.OAuth2FlowType.clientCredentials` | client credential flow | +| tokenUrl | `string` | the token URL | +| refreshUrl? | `string` | the refresh URL | +| scopes? | `string[]` | list of scopes for the credential | + +### `ConflictResponse` {#TypeSpec.Http.ConflictResponse} + +The request conflicts with the current state of the server. + +```typespec +model TypeSpec.Http.ConflictResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `409` | The status code. | + +### `CookieOptions` {#TypeSpec.Http.CookieOptions} + +Cookie Options. + +```typespec +model TypeSpec.Http.CookieOptions +``` + +#### Properties + +| Name | Type | Description | +| ----- | -------- | ------------------- | +| name? | `string` | Name in the cookie. | + +### `CreatedResponse` {#TypeSpec.Http.CreatedResponse} + +The request has succeeded and a new resource has been created as a result. + +```typespec +model TypeSpec.Http.CreatedResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `201` | The status code. | + +### `File` {#TypeSpec.Http.File} + +A file in an HTTP request, response, or multipart payload. + +Files have a special meaning that the HTTP library understands. When the body of an HTTP request, response, +or multipart payload is _effectively_ an instance of `TypeSpec.Http.File` or any type that extends it, the +operation is treated as a file upload or download. + +When using file bodies, the fields of the file model are defined to come from particular locations by default: + +- `contentType`: The `Content-Type` header of the request, response, or multipart payload (CANNOT be overridden or changed). +- `contents`: The body of the request, response, or multipart payload (CANNOT be overridden or changed). +- `filename`: The `filename` parameter value of the `Content-Disposition` header of the response or multipart payload + (MAY be overridden or changed). + +A File may be used as a normal structured JSON object in a request or response, if the request specifies an explicit +`Content-Type` header. In this case, the entire File model is serialized as if it were any other model. In a JSON payload, +it will have a structure like: + +``` +{ + "contentType": , + "filename": , + "contents": +} +``` + +The `contentType` _within_ the file defines what media types the data inside the file can be, but if the specification +defines a `Content-Type` for the payload as HTTP metadata, that `Content-Type` metadata defines _how the file is +serialized_. See the examples below for more information. + +NOTE: The `filename` and `contentType` fields are optional. Furthermore, the default location of `filename` +(`Content-Disposition: ; filename=`) is only valid in HTTP responses and multipart payloads. If +you wish to send the `filename` in a request, you must use HTTP metadata decorators to describe the location of the +`filename` field. You can combine the metadata decorators with `@visibility` to control when the `filename` location +is overridden, as shown in the examples below. + +```typespec +model TypeSpec.Http.File +``` + +#### Template Parameters + +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------- | +| ContentType | The allowed media (MIME) types of the file contents. | +| Contents | The type of the file contents. This can be `string`, `bytes`, or any scalar that extends them. | + +#### Examples + +```tsp +// Download a file +@get op download(): File; + +// Upload a file +@post op upload(@bodyRoot file: File): void; +``` + +```tsp +// Upload and download files in a multipart payload +op multipartFormDataUpload( + @multipartBody fields: { + files: HttpPart[]; + }, +): void; + +op multipartFormDataDownload(): { + @multipartBody formFields: { + files: HttpPart[]; + }; +}; +``` + +```tsp +// Declare a custom type of text file, where the filename goes in the path +// in requests. +model SpecFile extends File<"application/json" | "application/yaml", string> { + // Provide a header that contains the name of the file when created or updated + @header("x-filename") + @path + filename: string; +} + +@get op downloadSpec(@path name: string): SpecFile; + +@post op uploadSpec(@bodyRoot spec: SpecFile): void; +``` + +```tsp +// Declare a custom type of binary file +model ImageFile extends File { + contentType: "image/png" | "image/jpeg"; + @path filename: string; +} + +@get op downloadImage(@path name: string): ImageFile; + +@post op uploadImage(@bodyRoot image: ImageFile): void; +``` + +````tsp +// Use a File as a structured JSON object. The HTTP library will warn you that the File will be serialized as JSON, +// so you should suppress the warning if it's really what you want instead of a binary file upload/download. + +// The response body is a JSON object like `{"contentType":,"filename":,"contents":}` +@get op downloadTextFileJson(): { + @header contentType: "application/json", + @body file: File<"text/plain", string>, +}; + +// The request body is a JSON object like `{"contentType":,"filename":,"contents":}` +@post op uploadBinaryFileJson( + @header contentType: "application/json", + @body file: File<"image/png", bytes>, +): void; + +#### Properties +| Name | Type | Description | +|------|------|-------------| +| contentType? | `ContentType` | The allowed media (MIME) types of the file contents.

In file bodies, this value comes from the `Content-Type` header of the request or response. In JSON bodies,
this value is serialized as a field in the response.

NOTE: this is not _necessarily_ the same as the `Content-Type` header of the request or response, but
it will be for file bodies. It may be different if the file is serialized as a JSON object. It always refers to the
_contents_ of the file, and not necessarily the way the file itself is transmitted or serialized. | +| filename? | `string` | The name of the file, if any.

In file bodies, this value comes from the `filename` parameter of the `Content-Disposition` header of the response
or multipart payload. In JSON bodies, this value is serialized as a field in the response.

NOTE: By default, `filename` cannot be sent in request payloads and can only be sent in responses and multipart
payloads, as the `Content-Disposition` header is not valid in requests. If you want to send the `filename` in a request,
you must extend the `File` model and override the `filename` property with a different location defined by HTTP metadata
decorators. | +| contents | `Contents` | The contents of the file.

In file bodies, this value comes from the body of the request, response, or multipart payload. In JSON bodies,
this value is serialized as a field in the response. | + +### `ForbiddenResponse` {#TypeSpec.Http.ForbiddenResponse} + +Access is forbidden. +```typespec +model TypeSpec.Http.ForbiddenResponse +```` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `403` | The status code. | + +### `HeaderOptions` {#TypeSpec.Http.HeaderOptions} + +Header options. + +```typespec +model TypeSpec.Http.HeaderOptions +``` + +#### Properties + +| Name | Type | Description | +| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name? | `string` | Name of the header when sent over HTTP. | +| explode? | `boolean` | Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)

\| Style \| Explode \| Primitive value = 5 \| Array = [3, 4, 5] \| Object = {"role": "admin", "firstName": "Alex"} \|
\| ------ \| ------- \| ------------------- \| ----------------- \| ----------------------------------------------- \|
\| simple \| false \| `5 ` \| `3,4,5` \| `role,admin,firstName,Alex` \|
\| simple \| true \| `5` \| `3,4,5` \| `role=admin,firstName=Alex` \| | + +### `HttpPart` {#TypeSpec.Http.HttpPart} + +```typespec +model TypeSpec.Http.HttpPart +``` + +#### Template Parameters + +| Name | Description | +| ------- | ----------- | +| Type | | +| Options | | + +#### Properties + +None + +### `HttpPartOptions` {#TypeSpec.Http.HttpPartOptions} + +```typespec +model TypeSpec.Http.HttpPartOptions +``` + +#### Properties + +| Name | Type | Description | +| ----- | -------- | ------------------------------------------- | +| name? | `string` | Name of the part when using the array form. | + +### `ImplicitFlow` {#TypeSpec.Http.ImplicitFlow} + +Implicit flow + +```typespec +model TypeSpec.Http.ImplicitFlow +``` + +#### Properties + +| Name | Type | Description | +| ---------------- | --------------------------------------- | --------------------------------- | +| type | `TypeSpec.Http.OAuth2FlowType.implicit` | implicit flow | +| authorizationUrl | `string` | the authorization URL | +| refreshUrl? | `string` | the refresh URL | +| scopes? | `string[]` | list of scopes for the credential | + +### `Link` {#TypeSpec.Http.Link} + +```typespec +model TypeSpec.Http.Link +``` + +#### Properties + +| Name | Type | Description | +| ----------- | ----------------- | ----------- | +| target | `url` | | +| rel | `string` | | +| attributes? | `Record` | | + +### `LocationHeader` {#TypeSpec.Http.LocationHeader} + +The Location header contains the URL where the status of the long running operation can be checked. + +```typespec +model TypeSpec.Http.LocationHeader +``` + +#### Properties + +| Name | Type | Description | +| -------- | -------- | --------------------------------------------------------------------------------------------------- | +| location | `string` | The Location header contains the URL where the status of the long running operation can be checked. | + +### `MovedResponse` {#TypeSpec.Http.MovedResponse} + +The URL of the requested resource has been changed permanently. The new URL is given in the response. + +```typespec +model TypeSpec.Http.MovedResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | -------- | --------------------------------------------------------------------------------------------------- | +| statusCode | `301` | The status code. | +| location | `string` | The Location header contains the URL where the status of the long running operation can be checked. | + +### `NoAuth` {#TypeSpec.Http.NoAuth} + +This authentication option signifies that API is not secured at all. +It might be useful when overriding authentication on interface of operation level. + +```typespec +model TypeSpec.Http.NoAuth +``` + +#### Properties + +| Name | Type | Description | +| ---- | ------------------------------- | ----------- | +| type | `TypeSpec.Http.AuthType.noAuth` | | + +### `NoContentResponse` {#TypeSpec.Http.NoContentResponse} + +There is no content to send for this request, but the headers may be useful. + +```typespec +model TypeSpec.Http.NoContentResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `204` | The status code. | + +### `NotFoundResponse` {#TypeSpec.Http.NotFoundResponse} + +The server cannot find the requested resource. + +```typespec +model TypeSpec.Http.NotFoundResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `404` | The status code. | + +### `NotModifiedResponse` {#TypeSpec.Http.NotModifiedResponse} + +The client has made a conditional request and the resource has not been modified. + +```typespec +model TypeSpec.Http.NotModifiedResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `304` | The status code. | + +### `OAuth2Auth` {#TypeSpec.Http.OAuth2Auth} + +OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on a web server. + +OAuth relies on authentication scenarios called flows, which allow the resource owner (user) to share the protected content from the resource server without sharing their credentials. +For that purpose, an OAuth 2.0 server issues access tokens that the client applications can use to access protected resources on behalf of the resource owner. +For more information about OAuth 2.0, see oauth.net and RFC 6749. + +```typespec +model TypeSpec.Http.OAuth2Auth +``` + +#### Template Parameters + +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| Flows | The list of supported OAuth2 flows | +| Scopes | The list of OAuth2 scopes, which are common for every flow from `Flows`. This list is combined with the scopes defined in specific OAuth2 flows. | + +#### Properties + +| Name | Type | Description | +| ------------- | ------------------------------- | ----------- | +| type | `TypeSpec.Http.AuthType.oauth2` | | +| flows | `Flows` | | +| defaultScopes | `Scopes` | | + +### `OkResponse` {#TypeSpec.Http.OkResponse} + +The request has succeeded. + +```typespec +model TypeSpec.Http.OkResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `200` | The status code. | + +### `OpenIdConnectAuth` {#TypeSpec.Http.OpenIdConnectAuth} + +OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol and supported by some OAuth 2.0 providers, such as Google and Azure Active Directory. +It defines a sign-in flow that enables a client application to authenticate a user, and to obtain information (or "claims") about that user, such as the user name, email, and so on. +User identity information is encoded in a secure JSON Web Token (JWT), called ID token. +OpenID Connect defines a discovery mechanism, called OpenID Connect Discovery, where an OpenID server publishes its metadata at a well-known URL, typically + +```http +https://server.com/.well-known/openid-configuration +``` + +```typespec +model TypeSpec.Http.OpenIdConnectAuth +``` + +#### Template Parameters + +| Name | Description | +| ---------- | ----------- | +| ConnectUrl | | + +#### Properties + +| Name | Type | Description | +| ---------------- | -------------------------------------- | ----------------------------------------------------------- | +| type | `TypeSpec.Http.AuthType.openIdConnect` | Auth type | +| openIdConnectUrl | `ConnectUrl` | Connect url. It can be specified relative to the server URL | + +### `PasswordFlow` {#TypeSpec.Http.PasswordFlow} + +Resource Owner Password flow + +```typespec +model TypeSpec.Http.PasswordFlow +``` + +#### Properties + +| Name | Type | Description | +| ----------- | --------------------------------------- | --------------------------------- | +| type | `TypeSpec.Http.OAuth2FlowType.password` | password flow | +| tokenUrl | `string` | the token URL | +| refreshUrl? | `string` | the refresh URL | +| scopes? | `string[]` | list of scopes for the credential | + +### `PatchOptions` {#TypeSpec.Http.PatchOptions} + +Options for PATCH operations. + +```typespec +model TypeSpec.Http.PatchOptions +``` + +#### Properties + +| Name | Type | Description | +| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | +| implicitOptionality? | `boolean` | If set to `false`, disables the implicit transform that makes the body of a
PATCH operation deeply optional. | + +### `PathOptions` {#TypeSpec.Http.PathOptions} + +```typespec +model TypeSpec.Http.PathOptions +``` + +#### Properties + +| Name | Type | Description | +| -------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name? | `string` | Name of the parameter in the uri template. | +| explode? | `boolean` | When interpolating this parameter in the case of array or object expand each value using the given style.
Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) | +| style? | `"simple" \| "label" \| "matrix" \| "fragment" \| "path"` | Different interpolating styles for the path parameter.
- `simple`: No special encoding.
- `label`: Using `.` separator.
- `matrix`: `;` as separator.
- `fragment`: `#` as separator.
- `path`: `/` as separator. | +| allowReserved? | `boolean` | When interpolating this parameter do not encode reserved characters.
Equivalent of adding `+` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) | + +### `PlainData` {#TypeSpec.Http.PlainData} + +Produces a new model with the same properties as T, but with `@query`, +`@header`, `@body`, and `@path` decorators removed from all properties. + +```typespec +model TypeSpec.Http.PlainData +``` + +#### Template Parameters + +| Name | Description | +| ---- | -------------------------------------- | +| Data | The model to spread as the plain data. | + +#### Properties + +None + +### `QueryOptions` {#TypeSpec.Http.QueryOptions} + +Query parameter options. + +```typespec +model TypeSpec.Http.QueryOptions +``` + +#### Properties + +| Name | Type | Description | +| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| name? | `string` | Name of the query when included in the url. | +| explode? | `boolean` | If true send each value in the array/object as a separate query parameter.
Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)

\| Style \| Explode \| Uri Template \| Primitive value id = 5 \| Array id = [3, 4, 5] \| Object id = {"role": "admin", "firstName": "Alex"} \|
\| ------ \| ------- \| -------------- \| ---------------------- \| ----------------------- \| -------------------------------------------------- \|
\| simple \| false \| `/users{?id}` \| `/users?id=5` \| `/users?id=3,4,5` \| `/users?id=role,admin,firstName,Alex` \|
\| simple \| true \| `/users{?id*}` \| `/users?id=5` \| `/users?id=3&id=4&id=5` \| `/users?role=admin&firstName=Alex` \| | + +### `Response` {#TypeSpec.Http.Response} + +Describes an HTTP response. + +```typespec +model TypeSpec.Http.Response +``` + +#### Template Parameters + +| Name | Description | +| ------ | -------------------------------- | +| Status | The status code of the response. | + +#### Properties + +| Name | Type | Description | +| ---------- | -------- | ----------- | +| statusCode | `Status` | | + +### `UnauthorizedResponse` {#TypeSpec.Http.UnauthorizedResponse} + +Access is unauthorized. + +```typespec +model TypeSpec.Http.UnauthorizedResponse +``` + +#### Properties + +| Name | Type | Description | +| ---------- | ----- | ---------------- | +| statusCode | `401` | The status code. | + +### `ApiKeyLocation` {#TypeSpec.Http.ApiKeyLocation} + +Describes the location of the API key + +```typespec +enum TypeSpec.Http.ApiKeyLocation +``` + +| Name | Value | Description | +| ------ | ----- | ---------------------------- | +| header | | API key is a header value | +| query | | API key is a query parameter | +| cookie | | API key is found in a cookie | + +### `AuthType` {#TypeSpec.Http.AuthType} + +Authentication type + +```typespec +enum TypeSpec.Http.AuthType +``` + +| Name | Value | Description | +| ------------- | ----- | -------------- | +| http | | HTTP | +| apiKey | | API key | +| oauth2 | | OAuth2 | +| openIdConnect | | OpenID connect | +| noAuth | | Empty auth | + +### `OAuth2FlowType` {#TypeSpec.Http.OAuth2FlowType} + +Describes the OAuth2 flow type + +```typespec +enum TypeSpec.Http.OAuth2FlowType +``` + +| Name | Value | Description | +| ----------------- | ----- | ----------------------- | +| authorizationCode | | authorization code flow | +| implicit | | implicit flow | +| password | | password flow | +| clientCredentials | | client credential flow | + +### `LinkHeader` {#TypeSpec.Http.LinkHeader} + +```typespec +scalar TypeSpec.Http.LinkHeader +``` + +## TypeSpec.Http.Streams + +### `HttpStream` {#TypeSpec.Http.Streams.HttpStream} + +Defines a model that represents a stream protocol type whose data is described +by `Type`. + +The `ContentType` and `BodyType` describe how the stream is encoded over the wire, +while `Type` describes the data that the stream contains. + +```typespec +model TypeSpec.Http.Streams.HttpStream +``` + +#### Template Parameters + +| Name | Description | +| ----------- | --------------------------------------- | +| Type | The type of the stream's data. | +| ContentType | The content type of the stream. | +| BodyType | The underlying wire type of the stream. | + +#### Properties + +| Name | Type | Description | +| ----------- | ---------- | ----------- | +| contentType | `string` | | +| body | `BodyType` | | + +### `JsonlStream` {#TypeSpec.Http.Streams.JsonlStream} + +Describes a stream of JSON data with one JSON object per line and sets +the content type to `application/jsonl`. + +The JSON data is described by `Type`. + +```typespec +model TypeSpec.Http.Streams.JsonlStream +``` + +#### Template Parameters + +| Name | Description | +| ---- | --------------------------------------------------------- | +| Type | The set of models describing the JSON data in the stream. | + +#### Examples + +```typespec +model Message { + id: string; + text: string; +} + +@TypeSpec.Events.events +union Events { + Message, +} + +op subscribe(): JsonlStream; +``` + +#### Properties + +| Name | Type | Description | +| ----------- | --------------------- | ----------- | +| contentType | `"application/jsonl"` | | +| body | `string` | | diff --git a/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md b/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md new file mode 100644 index 00000000000..787fde8f8f0 --- /dev/null +++ b/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md @@ -0,0 +1,581 @@ +--- +title: "Decorators (streams)" +description: "Decorators exported by @typespec/http/streams" +toc_min_heading_level: 2 +toc_max_heading_level: 3 +llmstxt: true +--- + +## TypeSpec.Http + +### `@body` {#@TypeSpec.Http.body} + +Explicitly specify that this property type will be exactly the HTTP body. + +This means that any properties under `@body` cannot be marked as headers, query parameters, or path parameters. +If wanting to change the resolution of the body but still mix parameters, use `@bodyRoot`. + +```typespec +@TypeSpec.Http.body +``` + +#### Target + +`ModelProperty` + +#### Parameters + +None + +#### Examples + +```typespec +op upload(@body image: bytes): void; +op download(): { + @body image: bytes; +}; +``` + +### `@bodyIgnore` {#@TypeSpec.Http.bodyIgnore} + +Specify that this property shouldn't be included in the HTTP body. +This can be useful when bundling metadata together that would result in an empty property to be included in the body. + +```typespec +@TypeSpec.Http.bodyIgnore +``` + +#### Target + +`ModelProperty` + +#### Parameters + +None + +#### Examples + +```typespec +op upload( + name: string, + @bodyIgnore headers: { + @header id: string; + }, +): void; +``` + +### `@bodyRoot` {#@TypeSpec.Http.bodyRoot} + +Specify that the body resolution should be resolved from that property. +By default the body is resolved by including all properties in the operation request/response that are not metadata. +This allows to nest the body in a property while still allowing to use headers, query parameters, and path parameters in the same model. + +```typespec +@TypeSpec.Http.bodyRoot +``` + +#### Target + +`ModelProperty` + +#### Parameters + +None + +#### Examples + +```typespec +op upload( + @bodyRoot user: { + name: string; + @header id: string; + }, +): void; +op download(): { + @bodyRoot user: { + name: string; + @header id: string; + }; +}; +``` + +### `@cookie` {#@TypeSpec.Http.cookie} + +Specify this property is to be sent or received in the cookie. + +```typespec +@TypeSpec.Http.cookie(cookieNameOrOptions?: valueof string | TypeSpec.Http.CookieOptions) +``` + +#### Target + +`ModelProperty` + +#### Parameters + +| Name | Type | Description | +| ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| cookieNameOrOptions | `valueof string \| TypeSpec.Http.CookieOptions` | Optional name of the cookie in the cookie or cookie options.
By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`) | + +#### Examples + +```typespec +op read(@cookie token: string): { + data: string[]; +}; +op create( + @cookie({ + name: "auth_token", + }) + data: string[], +): void; +``` + +##### Implicit header name + +```typespec +op read(): { + @cookie authToken: string; +}; // headerName: auth_token +op update(@cookie AuthToken: string): void; // headerName: auth_token +``` + +### `@delete` {#@TypeSpec.Http.delete} + +Specify the HTTP verb for the target operation to be `DELETE`. + +```typespec +@TypeSpec.Http.delete +``` + +#### Target + +`Operation` + +#### Parameters + +None + +#### Examples + +```typespec +@delete op set(petId: string): void; +``` + +### `@get` {#@TypeSpec.Http.get} + +Specify the HTTP verb for the target operation to be `GET`. + +```typespec +@TypeSpec.Http.get +``` + +#### Target + +`Operation` + +#### Parameters + +None + +#### Examples + +```typespec +@get op read(): string; +``` + +### `@head` {#@TypeSpec.Http.head} + +Specify the HTTP verb for the target operation to be `HEAD`. + +```typespec +@TypeSpec.Http.head +``` + +#### Target + +`Operation` + +#### Parameters + +None + +#### Examples + +```typespec +@head op ping(petId: string): void; +``` + +### `@header` {#@TypeSpec.Http.header} + +Specify this property is to be sent or received as an HTTP header. + +```typespec +@TypeSpec.Http.header(headerNameOrOptions?: valueof string | TypeSpec.Http.HeaderOptions) +``` + +#### Target + +`ModelProperty` + +#### Parameters + +| Name | Type | Description | +| ------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| headerNameOrOptions | `valueof string \| TypeSpec.Http.HeaderOptions` | Optional name of the header when sent over HTTP or header options.
By default the header name will be the property name converted from camelCase to kebab-case. (e.g. `contentType` -> `content-type`) | + +#### Examples + +```typespec +op read(@header accept: string): { + @header("ETag") eTag: string; +}; +op create( + @header({ + name: "X-Color", + format: "csv", + }) + colors: string[], +): void; +``` + +##### Implicit header name + +```typespec +op read(): { + @header contentType: string; +}; // headerName: content-type +op update(@header ifMatch: string): void; // headerName: if-match +``` + +### `@multipartBody` {#@TypeSpec.Http.multipartBody} + +```typespec +@TypeSpec.Http.multipartBody +``` + +#### Target + +`ModelProperty` + +#### Parameters + +None + +#### Examples + +```tsp +op upload( + @header `content-type`: "multipart/form-data", + @multipartBody body: { + fullName: HttpPart; + headShots: HttpPart[]; + }, +): void; +``` + +### `@patch` {#@TypeSpec.Http.patch} + +Specify the HTTP verb for the target operation to be `PATCH`. + +```typespec +@TypeSpec.Http.patch(options?: valueof TypeSpec.Http.PatchOptions) +``` + +#### Target + +`Operation` + +#### Parameters + +| Name | Type | Description | +| ------- | ------------------------------------ | -------------------------------- | +| options | `valueof TypeSpec.Http.PatchOptions` | Options for the PATCH operation. | + +#### Examples + +```typespec +@patch op update(pet: Pet): void; +``` + +##### Using MergePatch template for proper merge-patch semantics + +```typespec +@patch op update(@body pet: MergePatchUpdate): void; +``` + +### `@path` {#@TypeSpec.Http.path} + +Explicitly specify that this property is to be interpolated as a path parameter. + +```typespec +@TypeSpec.Http.path(paramNameOrOptions?: valueof string | TypeSpec.Http.PathOptions) +``` + +#### Target + +`ModelProperty` + +#### Parameters + +| Name | Type | Description | +| ------------------ | --------------------------------------------- | -------------------------------------------------------------- | +| paramNameOrOptions | `valueof string \| TypeSpec.Http.PathOptions` | Optional name of the parameter in the uri template or options. | + +#### Examples + +```typespec +@route("/read/{explicit}/things/{implicit}") +op read(@path explicit: string, implicit: string): void; +``` + +### `@post` {#@TypeSpec.Http.post} + +Specify the HTTP verb for the target operation to be `POST`. + +```typespec +@TypeSpec.Http.post +``` + +#### Target + +`Operation` + +#### Parameters + +None + +#### Examples + +```typespec +@post op create(pet: Pet): void; +``` + +### `@put` {#@TypeSpec.Http.put} + +Specify the HTTP verb for the target operation to be `PUT`. + +```typespec +@TypeSpec.Http.put +``` + +#### Target + +`Operation` + +#### Parameters + +None + +#### Examples + +```typespec +@put op set(pet: Pet): void; +``` + +### `@query` {#@TypeSpec.Http.query} + +Specify this property is to be sent as a query parameter. + +```typespec +@TypeSpec.Http.query(queryNameOrOptions?: valueof string | TypeSpec.Http.QueryOptions) +``` + +#### Target + +`ModelProperty` + +#### Parameters + +| Name | Type | Description | +| ------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------- | +| queryNameOrOptions | `valueof string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. | + +#### Examples + +```typespec +op read(@query select: string, @query("order-by") orderBy: string): void; +op list(@query(#{ name: "id", explode: true }) ids: string[]): void; +``` + +### `@route` {#@TypeSpec.Http.route} + +Defines the relative route URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) + +`@route` can only be applied to operations, namespaces, and interfaces. + +```typespec +@TypeSpec.Http.route(path: valueof string) +``` + +#### Target + +`Namespace | Interface | Operation` + +#### Parameters + +| Name | Type | Description | +| ---- | ---------------- | ----------- | +| path | `valueof string` | | + +#### Examples + +##### Simple path parameter + +```typespec +@route("/widgets/{id}") op getWidget(@path id: string): Widget; +``` + +##### Reserved characters + +```typespec +@route("/files{+path}") op getFile(@path path: string): bytes; +``` + +##### Query parameter + +```typespec +@route("/files") op list(select?: string, filter?: string): Files[]; +@route("/files{?select,filter}") op listFullUriTemplate(select?: string, filter?: string): Files[]; +``` + +### `@server` {#@TypeSpec.Http.server} + +Specify an endpoint for this service. Multiple `@server` decorators can be used to specify multiple endpoints. + +```typespec +@TypeSpec.Http.server(url: valueof string, description?: valueof string, parameters?: Record) +``` + +#### Target + +`Namespace` + +#### Parameters + +| Name | Type | Description | +| ----------- | ----------------- | ------------------------------------------------------- | +| url | `valueof string` | Server endpoint | +| description | `valueof string` | Description of the endpoint | +| parameters | `Record` | Optional set of parameters used to interpolate the url. | + +#### Examples + +```typespec +@service +@server("https://example.com") +namespace PetStore; +``` + +##### With a description + +```typespec +@service +@server("https://example.com", "Single server endpoint") +namespace PetStore; +``` + +##### Parameterized + +```typespec +@server("https://{region}.foo.com", "Regional endpoint", { + @doc("Region name") + region?: string = "westus", +}) +``` + +##### Multiple + +```typespec +@service +@server("https://example.com", "Standard endpoint") +@server( + "https://{project}.private.example.com", + "Private project endpoint", + { + project: string, + } +) +namespace PetStore; +``` + +### `@sharedRoute` {#@TypeSpec.Http.sharedRoute} + +`@sharedRoute` marks the operation as sharing a route path with other operations. + +When an operation is marked with `@sharedRoute`, it enables other operations to share the same +route path as long as those operations are also marked with `@sharedRoute`. + +`@sharedRoute` can only be applied directly to operations. + +```typespec +@sharedRoute +@route("/widgets") +op getWidget(@path id: string): Widget; +``` + +```typespec +@TypeSpec.Http.sharedRoute +``` + +#### Target + +`Operation` + +#### Parameters + +None + +### `@statusCode` {#@TypeSpec.Http.statusCode} + +Specify the status code for this response. Property type must be a status code integer or a union of status code integer. + +```typespec +@TypeSpec.Http.statusCode +``` + +#### Target + +`ModelProperty` + +#### Parameters + +None + +#### Examples + +```typespec +op read(): { + @statusCode _: 200; + @body pet: Pet; +}; +op create(): { + @statusCode _: 201 | 202; +}; +``` + +### `@useAuth` {#@TypeSpec.Http.useAuth} + +Specify authentication for a whole service or specific methods. See the [documentation in the Http library](https://typespec.io/docs/libraries/http/authentication) for full details. + +```typespec +@TypeSpec.Http.useAuth(auth: {} | Union | {}[]) +``` + +#### Target + +`Namespace | Interface | Operation` + +#### Parameters + +| Name | Type | Description | +| ---- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| auth | `{} \| Union \| {}[]` | Authentication configuration. Can be a single security scheme, a union(either option is valid authentication) or a tuple (must use all authentication together) | + +#### Examples + +```typespec +@service +@useAuth(BasicAuth) +namespace PetStore; +``` From 8f05aac43e8e197ec54667cca9c187997437e5a1 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 11 May 2026 12:42:57 -0400 Subject: [PATCH 2/6] Change changeKind to feature and improve documentation Render documentation for sub exports --- .chronus/changes/tspd-improvements-2026-4-11-16-35-10.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/tspd-improvements-2026-4-11-16-35-10.md diff --git a/.chronus/changes/tspd-improvements-2026-4-11-16-35-10.md b/.chronus/changes/tspd-improvements-2026-4-11-16-35-10.md new file mode 100644 index 00000000000..2826ee57115 --- /dev/null +++ b/.chronus/changes/tspd-improvements-2026-4-11-16-35-10.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/tspd" +--- + +Render documentation for sub exports From e4e3a76fd9773d89de8389c1af2b067591f3297f Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 11 May 2026 12:43:18 -0400 Subject: [PATCH 3/6] Change changeKind to feature and update documentation Improve the rendering of complex emitter options in documentation. --- .../changes/tspd-improvements-2026-4-11-16-35-10-2.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/tspd-improvements-2026-4-11-16-35-10-2.md diff --git a/.chronus/changes/tspd-improvements-2026-4-11-16-35-10-2.md b/.chronus/changes/tspd-improvements-2026-4-11-16-35-10-2.md new file mode 100644 index 00000000000..24165af9323 --- /dev/null +++ b/.chronus/changes/tspd-improvements-2026-4-11-16-35-10-2.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/tspd" +--- + +Improve render of complex emitter options From 44bbee9b972d35968c6cb046c80c4c327527138c Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 11 May 2026 12:43:25 -0400 Subject: [PATCH 4/6] deprecation notice --- .../tspd/src/ref-doc/emitters/markdown.ts | 20 +++++++++---------- packages/tspd/src/ref-doc/extractor.ts | 4 +++- packages/tspd/src/ref-doc/types.ts | 2 ++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/tspd/src/ref-doc/emitters/markdown.ts b/packages/tspd/src/ref-doc/emitters/markdown.ts index 97a14796016..f8d1dcb3fd4 100644 --- a/packages/tspd/src/ref-doc/emitters/markdown.ts +++ b/packages/tspd/src/ref-doc/emitters/markdown.ts @@ -425,19 +425,17 @@ export class MarkdownRenderer { return inlinecode(option.type); } - emitterOptionTypeDisplayInTable(option: { - type: string; - allowedValues?: readonly string[]; - }): string { - if (option.allowedValues && option.allowedValues.length > 0) { - return inlinecode(option.allowedValues.join(" | ")); - } - return inlinecode(option.type); - } + emitterOption(option: EmitterOptionRefDoc): MarkdownDoc { const content: MarkdownDoc = []; + // Deprecation notice + if (option.deprecated !== undefined) { + const message = option.deprecated || "This option is deprecated."; + content.push(this.deprecationNotice({ message }), ""); + } + // Type line content.push(`**Type:** ${this.emitterOptionTypeDisplay(option)}`); @@ -466,7 +464,7 @@ export class MarkdownRenderer { for (const nested of option.nestedOptions) { rows.push([ inlinecode(nested.name), - this.emitterOptionTypeDisplayInTable(nested), + this.emitterOptionTypeDisplay(nested), nested.default !== undefined ? inlinecode(nested.default) : "", nested.doc, ]); @@ -500,7 +498,7 @@ export class MarkdownRenderer { for (const nested of variant.nestedOptions) { rows.push([ inlinecode(nested.name), - this.emitterOptionTypeDisplayInTable(nested), + this.emitterOptionTypeDisplay(nested), nested.default !== undefined ? inlinecode(nested.default) : "", nested.doc, ]); diff --git a/packages/tspd/src/ref-doc/extractor.ts b/packages/tspd/src/ref-doc/extractor.ts index c1cadac9ac4..e466f40c2e5 100644 --- a/packages/tspd/src/ref-doc/extractor.ts +++ b/packages/tspd/src/ref-doc/extractor.ts @@ -760,7 +760,9 @@ function extractEmitterOptionInfo(name: string, prop: any): EmitterOptionRefDoc option.default = JSON.stringify(prop.default); } - // Nested object properties + if (prop.deprecated !== undefined) { + option.deprecated = typeof prop.deprecated === "string" ? prop.deprecated : ""; + } if (prop.type === "object" && prop.properties) { option.nestedOptions = Object.entries(prop.properties).map( ([subName, subProp]: [string, any]) => extractEmitterOptionInfo(subName, subProp), diff --git a/packages/tspd/src/ref-doc/types.ts b/packages/tspd/src/ref-doc/types.ts index 62fd2bbcf22..7929a9a2d4a 100644 --- a/packages/tspd/src/ref-doc/types.ts +++ b/packages/tspd/src/ref-doc/types.ts @@ -80,6 +80,8 @@ export type EmitterOptionRefDoc = { readonly doc: string; readonly default?: string; readonly allowedValues?: readonly string[]; + /** If set, the option is deprecated. Contains the deprecation message or empty string. */ + readonly deprecated?: string; /** Nested options for object-typed options. */ readonly nestedOptions?: readonly EmitterOptionRefDoc[]; /** When present, this option is a union of multiple variants (oneOf). */ From 53b0ea611a1c32e8777526ea1b0dd2bc3f92c952 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 11 May 2026 14:43:05 -0400 Subject: [PATCH 5/6] abc --- packages/tspd/src/ref-doc/emitters/markdown.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/tspd/src/ref-doc/emitters/markdown.ts b/packages/tspd/src/ref-doc/emitters/markdown.ts index f8d1dcb3fd4..4cf748c3497 100644 --- a/packages/tspd/src/ref-doc/emitters/markdown.ts +++ b/packages/tspd/src/ref-doc/emitters/markdown.ts @@ -425,8 +425,6 @@ export class MarkdownRenderer { return inlinecode(option.type); } - - emitterOption(option: EmitterOptionRefDoc): MarkdownDoc { const content: MarkdownDoc = []; From 86a391268bd622089dc0672506ac2f5ac61b2c80 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Tue, 2 Jun 2026 09:53:17 -0400 Subject: [PATCH 6/6] exclude base --- packages/tspd/src/ref-doc/extractor.ts | 57 +- .../http/reference/streams/data-types.md | 744 ------------------ .../http/reference/streams/decorators.md | 581 -------------- 3 files changed, 52 insertions(+), 1330 deletions(-) delete mode 100644 website/src/content/docs/docs/libraries/http/reference/streams/decorators.md diff --git a/packages/tspd/src/ref-doc/extractor.ts b/packages/tspd/src/ref-doc/extractor.ts index e466f40c2e5..55614f5d95a 100644 --- a/packages/tspd/src/ref-doc/extractor.ts +++ b/packages/tspd/src/ref-doc/extractor.ts @@ -100,11 +100,13 @@ export async function extractLibraryRefDocs( getNamedTypeRefDoc: (type) => undefined, }; const tspMain = getExport(pkgJson, ".", "typespec"); + let mainSourceFiles: Set | undefined; if (tspMain) { const main = resolvePath(libraryPath, tspMain); const program = await compile(NodeHost, main, { parseOptions: { comments: true, docs: true }, }); + mainSourceFiles = new Set(program.sourceFiles.keys()); const tspEmitter = diagnostics.pipe(extractRefDocs(program)); Object.assign(refDoc, tspEmitter); for (const diag of program.diagnostics ?? []) { @@ -128,7 +130,7 @@ export async function extractLibraryRefDocs( } // Extract sub-exports - const subExports = await extractSubExports(libraryPath, pkgJson, diagnostics); + const subExports = await extractSubExports(libraryPath, pkgJson, diagnostics, mainSourceFiles); if (subExports.size > 0) { refDoc.subExports = subExports; } @@ -145,6 +147,7 @@ async function extractSubExports( libraryPath: string, pkgJson: PackageJson, diagnostics: { pipe: (result: [T, readonly Diagnostic[]]) => T; add: (d: Diagnostic) => void }, + mainSourceFiles?: Set, ): Promise> { const subExports = new Map(); const exports = (pkgJson as any).exports; @@ -165,7 +168,13 @@ async function extractSubExports( const program = await compile(NodeHost, main, { parseOptions: { comments: true, docs: true }, }); - const subRefDoc = diagnostics.pipe(extractRefDocs(program)); + const subRefDoc = diagnostics.pipe( + extractRefDocs(program, { + sourceFilter: mainSourceFiles + ? (sourcePath) => !mainSourceFiles.has(sourcePath) + : undefined, + }), + ); // Only include if it actually has content if (subRefDoc.namespaces.length > 0) { subExports.set(exportPath, { @@ -189,6 +198,11 @@ export interface ExtractRefDocOptions { include?: string[]; exclude?: string[]; }; + /** + * Filter to restrict which types are included based on their source file path. + * When provided, only types declared in files for which this returns true will be included. + */ + sourceFilter?: (sourcePath: string) => boolean; } function resolveNamespaces( @@ -212,6 +226,12 @@ function resolveNamespaces( if (namespace.name === "Private") { return; } + if (options.sourceFilter && namespace.node) { + const loc = getSourceLocation(namespace.node); + if (!loc.isSynthetic && !options.sourceFilter(loc.file.path)) { + return; + } + } namespaceTypes.push(namespace); }, }); @@ -253,11 +273,20 @@ export function extractRefDocs( typeMapping.set(type, refDoc); (array as any).push(refDoc); } + + function isIncludedBySourceFilter(type: Type): boolean { + if (!options.sourceFilter) return true; + const loc = getSourceLocation(type); + if (loc.isSynthetic) return true; + return options.sourceFilter(loc.file.path); + } + navigateTypesInNamespace( namespace, { decorator(dec) { if (hasInternalModifier(dec)) return; + if (!isIncludedBySourceFilter(dec)) return; collectType(dec, extractDecoratorRefDoc(program, dec), namespaceDoc.decorators); }, operation(operation) { @@ -265,6 +294,7 @@ export function extractRefDocs( if (!isDeclaredType(operation)) { return; } + if (!isIncludedBySourceFilter(operation)) return; if (operation.interface === undefined) { collectType( @@ -279,6 +309,7 @@ export function extractRefDocs( if (!isDeclaredType(iface)) { return; } + if (!isIncludedBySourceFilter(iface)) return; collectType(iface, extractInterfaceRefDocs(program, iface), namespaceDoc.interfaces); }, model(model) { @@ -289,6 +320,7 @@ export function extractRefDocs( if (model.name === "") { return; } + if (!isIncludedBySourceFilter(model)) return; collectType(model, extractModelRefDocs(program, model), namespaceDoc.models); }, enum(e) { @@ -296,6 +328,7 @@ export function extractRefDocs( if (!isDeclaredType(e)) { return; } + if (!isIncludedBySourceFilter(e)) return; collectType(e, extractEnumRefDoc(program, e), namespaceDoc.enums); }, union(union) { @@ -304,11 +337,13 @@ export function extractRefDocs( return; } if (union.name !== undefined) { + if (!isIncludedBySourceFilter(union)) return; collectType(union, extractUnionRefDocs(program, union as any), namespaceDoc.unions); } }, scalar(scalar) { if (hasInternalModifier(scalar)) return; + if (!isIncludedBySourceFilter(scalar)) return; collectType(scalar, extractScalarRefDocs(program, scalar), namespaceDoc.scalars); }, }, @@ -316,8 +351,20 @@ export function extractRefDocs( ); } - sort(namespaces); - for (const namespace of namespaces) { + // Remove namespaces that have no content after filtering + const filteredNamespaces = namespaces.filter( + (ns) => + ns.decorators.length > 0 || + ns.operations.length > 0 || + ns.interfaces.length > 0 || + ns.models.length > 0 || + ns.enums.length > 0 || + ns.unions.length > 0 || + ns.scalars.length > 0, + ); + + sort(filteredNamespaces); + for (const namespace of filteredNamespaces) { sort(namespace.decorators); sort(namespace.enums); sort(namespace.interfaces); @@ -332,7 +379,7 @@ export function extractRefDocs( } return diagnostics.wrap({ - namespaces, + namespaces: filteredNamespaces, getNamedTypeRefDoc: (type) => typeMapping.get(type), }); } diff --git a/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md b/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md index 0ce46791e81..726f2e47aa0 100644 --- a/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md +++ b/website/src/content/docs/docs/libraries/http/reference/streams/data-types.md @@ -4,750 +4,6 @@ description: "Data types exported by @typespec/http/streams" llmstxt: true --- -## TypeSpec.Http - -### `AcceptedResponse` {#TypeSpec.Http.AcceptedResponse} - -The request has been accepted for processing, but processing has not yet completed. - -```typespec -model TypeSpec.Http.AcceptedResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `202` | The status code. | - -### `ApiKeyAuth` {#TypeSpec.Http.ApiKeyAuth} - -An API key is a token that a client provides when making API calls. The key can be sent in the query string: - -``` -GET /something?api_key=abcdef12345 -``` - -or as a request header - -``` -GET /something HTTP/1.1 -X-API-Key: abcdef12345 -``` - -or as a cookie - -``` -GET /something HTTP/1.1 -Cookie: X-API-KEY=abcdef12345 -``` - -```typespec -model TypeSpec.Http.ApiKeyAuth -``` - -#### Template Parameters - -| Name | Description | -| -------- | --------------------------- | -| Location | The location of the API key | -| Name | The name of the API key | - -#### Properties - -| Name | Type | Description | -| ---- | ------------------------------- | ----------- | -| type | `TypeSpec.Http.AuthType.apiKey` | | -| in | `Location` | | -| name | `Name` | | - -### `AuthorizationCodeFlow` {#TypeSpec.Http.AuthorizationCodeFlow} - -Authorization Code flow - -```typespec -model TypeSpec.Http.AuthorizationCodeFlow -``` - -#### Properties - -| Name | Type | Description | -| ---------------- | ------------------------------------------------ | --------------------------------- | -| type | `TypeSpec.Http.OAuth2FlowType.authorizationCode` | authorization code flow | -| authorizationUrl | `string` | the authorization URL | -| tokenUrl | `string` | the token URL | -| refreshUrl? | `string` | the refresh URL | -| scopes? | `string[]` | list of scopes for the credential | - -### `BadRequestResponse` {#TypeSpec.Http.BadRequestResponse} - -The server could not understand the request due to invalid syntax. - -```typespec -model TypeSpec.Http.BadRequestResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `400` | The status code. | - -### `BasicAuth` {#TypeSpec.Http.BasicAuth} - -Basic authentication is a simple authentication scheme built into the HTTP protocol. -The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. -For example, to authorize as demo / `p@55w0rd` the client would send - -``` -Authorization: Basic ZGVtbzpwQDU1dzByZA== -``` - -```typespec -model TypeSpec.Http.BasicAuth -``` - -#### Properties - -| Name | Type | Description | -| ------ | ----------------------------- | ------------------- | -| type | `TypeSpec.Http.AuthType.http` | Http authentication | -| scheme | `"Basic"` | basic auth scheme | - -### `BearerAuth` {#TypeSpec.Http.BearerAuth} - -Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. -The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request. -The client must send this token in the Authorization header when making requests to protected resources: - -``` -Authorization: Bearer -``` - -```typespec -model TypeSpec.Http.BearerAuth -``` - -#### Properties - -| Name | Type | Description | -| ------ | ----------------------------- | ------------------- | -| type | `TypeSpec.Http.AuthType.http` | Http authentication | -| scheme | `"Bearer"` | bearer auth scheme | - -### `Body` {#TypeSpec.Http.Body} - -Defines a model with a single property of the given type, marked with `@body`. - -This can be useful in situations where you cannot use a bare type as the body -and it is awkward to add a property. - -```typespec -model TypeSpec.Http.Body -``` - -#### Template Parameters - -| Name | Description | -| ---- | ---------------------------------------- | -| Type | The type of the model's `body` property. | - -#### Properties - -| Name | Type | Description | -| ---- | ------ | ----------- | -| body | `Type` | | - -### `ClientCredentialsFlow` {#TypeSpec.Http.ClientCredentialsFlow} - -Client credentials flow - -```typespec -model TypeSpec.Http.ClientCredentialsFlow -``` - -#### Properties - -| Name | Type | Description | -| ----------- | ------------------------------------------------ | --------------------------------- | -| type | `TypeSpec.Http.OAuth2FlowType.clientCredentials` | client credential flow | -| tokenUrl | `string` | the token URL | -| refreshUrl? | `string` | the refresh URL | -| scopes? | `string[]` | list of scopes for the credential | - -### `ConflictResponse` {#TypeSpec.Http.ConflictResponse} - -The request conflicts with the current state of the server. - -```typespec -model TypeSpec.Http.ConflictResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `409` | The status code. | - -### `CookieOptions` {#TypeSpec.Http.CookieOptions} - -Cookie Options. - -```typespec -model TypeSpec.Http.CookieOptions -``` - -#### Properties - -| Name | Type | Description | -| ----- | -------- | ------------------- | -| name? | `string` | Name in the cookie. | - -### `CreatedResponse` {#TypeSpec.Http.CreatedResponse} - -The request has succeeded and a new resource has been created as a result. - -```typespec -model TypeSpec.Http.CreatedResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `201` | The status code. | - -### `File` {#TypeSpec.Http.File} - -A file in an HTTP request, response, or multipart payload. - -Files have a special meaning that the HTTP library understands. When the body of an HTTP request, response, -or multipart payload is _effectively_ an instance of `TypeSpec.Http.File` or any type that extends it, the -operation is treated as a file upload or download. - -When using file bodies, the fields of the file model are defined to come from particular locations by default: - -- `contentType`: The `Content-Type` header of the request, response, or multipart payload (CANNOT be overridden or changed). -- `contents`: The body of the request, response, or multipart payload (CANNOT be overridden or changed). -- `filename`: The `filename` parameter value of the `Content-Disposition` header of the response or multipart payload - (MAY be overridden or changed). - -A File may be used as a normal structured JSON object in a request or response, if the request specifies an explicit -`Content-Type` header. In this case, the entire File model is serialized as if it were any other model. In a JSON payload, -it will have a structure like: - -``` -{ - "contentType": , - "filename": , - "contents": -} -``` - -The `contentType` _within_ the file defines what media types the data inside the file can be, but if the specification -defines a `Content-Type` for the payload as HTTP metadata, that `Content-Type` metadata defines _how the file is -serialized_. See the examples below for more information. - -NOTE: The `filename` and `contentType` fields are optional. Furthermore, the default location of `filename` -(`Content-Disposition: ; filename=`) is only valid in HTTP responses and multipart payloads. If -you wish to send the `filename` in a request, you must use HTTP metadata decorators to describe the location of the -`filename` field. You can combine the metadata decorators with `@visibility` to control when the `filename` location -is overridden, as shown in the examples below. - -```typespec -model TypeSpec.Http.File -``` - -#### Template Parameters - -| Name | Description | -| ----------- | ---------------------------------------------------------------------------------------------- | -| ContentType | The allowed media (MIME) types of the file contents. | -| Contents | The type of the file contents. This can be `string`, `bytes`, or any scalar that extends them. | - -#### Examples - -```tsp -// Download a file -@get op download(): File; - -// Upload a file -@post op upload(@bodyRoot file: File): void; -``` - -```tsp -// Upload and download files in a multipart payload -op multipartFormDataUpload( - @multipartBody fields: { - files: HttpPart[]; - }, -): void; - -op multipartFormDataDownload(): { - @multipartBody formFields: { - files: HttpPart[]; - }; -}; -``` - -```tsp -// Declare a custom type of text file, where the filename goes in the path -// in requests. -model SpecFile extends File<"application/json" | "application/yaml", string> { - // Provide a header that contains the name of the file when created or updated - @header("x-filename") - @path - filename: string; -} - -@get op downloadSpec(@path name: string): SpecFile; - -@post op uploadSpec(@bodyRoot spec: SpecFile): void; -``` - -```tsp -// Declare a custom type of binary file -model ImageFile extends File { - contentType: "image/png" | "image/jpeg"; - @path filename: string; -} - -@get op downloadImage(@path name: string): ImageFile; - -@post op uploadImage(@bodyRoot image: ImageFile): void; -``` - -````tsp -// Use a File as a structured JSON object. The HTTP library will warn you that the File will be serialized as JSON, -// so you should suppress the warning if it's really what you want instead of a binary file upload/download. - -// The response body is a JSON object like `{"contentType":,"filename":,"contents":}` -@get op downloadTextFileJson(): { - @header contentType: "application/json", - @body file: File<"text/plain", string>, -}; - -// The request body is a JSON object like `{"contentType":,"filename":,"contents":}` -@post op uploadBinaryFileJson( - @header contentType: "application/json", - @body file: File<"image/png", bytes>, -): void; - -#### Properties -| Name | Type | Description | -|------|------|-------------| -| contentType? | `ContentType` | The allowed media (MIME) types of the file contents.

In file bodies, this value comes from the `Content-Type` header of the request or response. In JSON bodies,
this value is serialized as a field in the response.

NOTE: this is not _necessarily_ the same as the `Content-Type` header of the request or response, but
it will be for file bodies. It may be different if the file is serialized as a JSON object. It always refers to the
_contents_ of the file, and not necessarily the way the file itself is transmitted or serialized. | -| filename? | `string` | The name of the file, if any.

In file bodies, this value comes from the `filename` parameter of the `Content-Disposition` header of the response
or multipart payload. In JSON bodies, this value is serialized as a field in the response.

NOTE: By default, `filename` cannot be sent in request payloads and can only be sent in responses and multipart
payloads, as the `Content-Disposition` header is not valid in requests. If you want to send the `filename` in a request,
you must extend the `File` model and override the `filename` property with a different location defined by HTTP metadata
decorators. | -| contents | `Contents` | The contents of the file.

In file bodies, this value comes from the body of the request, response, or multipart payload. In JSON bodies,
this value is serialized as a field in the response. | - -### `ForbiddenResponse` {#TypeSpec.Http.ForbiddenResponse} - -Access is forbidden. -```typespec -model TypeSpec.Http.ForbiddenResponse -```` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `403` | The status code. | - -### `HeaderOptions` {#TypeSpec.Http.HeaderOptions} - -Header options. - -```typespec -model TypeSpec.Http.HeaderOptions -``` - -#### Properties - -| Name | Type | Description | -| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name? | `string` | Name of the header when sent over HTTP. | -| explode? | `boolean` | Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)

\| Style \| Explode \| Primitive value = 5 \| Array = [3, 4, 5] \| Object = {"role": "admin", "firstName": "Alex"} \|
\| ------ \| ------- \| ------------------- \| ----------------- \| ----------------------------------------------- \|
\| simple \| false \| `5 ` \| `3,4,5` \| `role,admin,firstName,Alex` \|
\| simple \| true \| `5` \| `3,4,5` \| `role=admin,firstName=Alex` \| | - -### `HttpPart` {#TypeSpec.Http.HttpPart} - -```typespec -model TypeSpec.Http.HttpPart -``` - -#### Template Parameters - -| Name | Description | -| ------- | ----------- | -| Type | | -| Options | | - -#### Properties - -None - -### `HttpPartOptions` {#TypeSpec.Http.HttpPartOptions} - -```typespec -model TypeSpec.Http.HttpPartOptions -``` - -#### Properties - -| Name | Type | Description | -| ----- | -------- | ------------------------------------------- | -| name? | `string` | Name of the part when using the array form. | - -### `ImplicitFlow` {#TypeSpec.Http.ImplicitFlow} - -Implicit flow - -```typespec -model TypeSpec.Http.ImplicitFlow -``` - -#### Properties - -| Name | Type | Description | -| ---------------- | --------------------------------------- | --------------------------------- | -| type | `TypeSpec.Http.OAuth2FlowType.implicit` | implicit flow | -| authorizationUrl | `string` | the authorization URL | -| refreshUrl? | `string` | the refresh URL | -| scopes? | `string[]` | list of scopes for the credential | - -### `Link` {#TypeSpec.Http.Link} - -```typespec -model TypeSpec.Http.Link -``` - -#### Properties - -| Name | Type | Description | -| ----------- | ----------------- | ----------- | -| target | `url` | | -| rel | `string` | | -| attributes? | `Record` | | - -### `LocationHeader` {#TypeSpec.Http.LocationHeader} - -The Location header contains the URL where the status of the long running operation can be checked. - -```typespec -model TypeSpec.Http.LocationHeader -``` - -#### Properties - -| Name | Type | Description | -| -------- | -------- | --------------------------------------------------------------------------------------------------- | -| location | `string` | The Location header contains the URL where the status of the long running operation can be checked. | - -### `MovedResponse` {#TypeSpec.Http.MovedResponse} - -The URL of the requested resource has been changed permanently. The new URL is given in the response. - -```typespec -model TypeSpec.Http.MovedResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | -------- | --------------------------------------------------------------------------------------------------- | -| statusCode | `301` | The status code. | -| location | `string` | The Location header contains the URL where the status of the long running operation can be checked. | - -### `NoAuth` {#TypeSpec.Http.NoAuth} - -This authentication option signifies that API is not secured at all. -It might be useful when overriding authentication on interface of operation level. - -```typespec -model TypeSpec.Http.NoAuth -``` - -#### Properties - -| Name | Type | Description | -| ---- | ------------------------------- | ----------- | -| type | `TypeSpec.Http.AuthType.noAuth` | | - -### `NoContentResponse` {#TypeSpec.Http.NoContentResponse} - -There is no content to send for this request, but the headers may be useful. - -```typespec -model TypeSpec.Http.NoContentResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `204` | The status code. | - -### `NotFoundResponse` {#TypeSpec.Http.NotFoundResponse} - -The server cannot find the requested resource. - -```typespec -model TypeSpec.Http.NotFoundResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `404` | The status code. | - -### `NotModifiedResponse` {#TypeSpec.Http.NotModifiedResponse} - -The client has made a conditional request and the resource has not been modified. - -```typespec -model TypeSpec.Http.NotModifiedResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `304` | The status code. | - -### `OAuth2Auth` {#TypeSpec.Http.OAuth2Auth} - -OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on a web server. - -OAuth relies on authentication scenarios called flows, which allow the resource owner (user) to share the protected content from the resource server without sharing their credentials. -For that purpose, an OAuth 2.0 server issues access tokens that the client applications can use to access protected resources on behalf of the resource owner. -For more information about OAuth 2.0, see oauth.net and RFC 6749. - -```typespec -model TypeSpec.Http.OAuth2Auth -``` - -#### Template Parameters - -| Name | Description | -| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| Flows | The list of supported OAuth2 flows | -| Scopes | The list of OAuth2 scopes, which are common for every flow from `Flows`. This list is combined with the scopes defined in specific OAuth2 flows. | - -#### Properties - -| Name | Type | Description | -| ------------- | ------------------------------- | ----------- | -| type | `TypeSpec.Http.AuthType.oauth2` | | -| flows | `Flows` | | -| defaultScopes | `Scopes` | | - -### `OkResponse` {#TypeSpec.Http.OkResponse} - -The request has succeeded. - -```typespec -model TypeSpec.Http.OkResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `200` | The status code. | - -### `OpenIdConnectAuth` {#TypeSpec.Http.OpenIdConnectAuth} - -OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol and supported by some OAuth 2.0 providers, such as Google and Azure Active Directory. -It defines a sign-in flow that enables a client application to authenticate a user, and to obtain information (or "claims") about that user, such as the user name, email, and so on. -User identity information is encoded in a secure JSON Web Token (JWT), called ID token. -OpenID Connect defines a discovery mechanism, called OpenID Connect Discovery, where an OpenID server publishes its metadata at a well-known URL, typically - -```http -https://server.com/.well-known/openid-configuration -``` - -```typespec -model TypeSpec.Http.OpenIdConnectAuth -``` - -#### Template Parameters - -| Name | Description | -| ---------- | ----------- | -| ConnectUrl | | - -#### Properties - -| Name | Type | Description | -| ---------------- | -------------------------------------- | ----------------------------------------------------------- | -| type | `TypeSpec.Http.AuthType.openIdConnect` | Auth type | -| openIdConnectUrl | `ConnectUrl` | Connect url. It can be specified relative to the server URL | - -### `PasswordFlow` {#TypeSpec.Http.PasswordFlow} - -Resource Owner Password flow - -```typespec -model TypeSpec.Http.PasswordFlow -``` - -#### Properties - -| Name | Type | Description | -| ----------- | --------------------------------------- | --------------------------------- | -| type | `TypeSpec.Http.OAuth2FlowType.password` | password flow | -| tokenUrl | `string` | the token URL | -| refreshUrl? | `string` | the refresh URL | -| scopes? | `string[]` | list of scopes for the credential | - -### `PatchOptions` {#TypeSpec.Http.PatchOptions} - -Options for PATCH operations. - -```typespec -model TypeSpec.Http.PatchOptions -``` - -#### Properties - -| Name | Type | Description | -| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | -| implicitOptionality? | `boolean` | If set to `false`, disables the implicit transform that makes the body of a
PATCH operation deeply optional. | - -### `PathOptions` {#TypeSpec.Http.PathOptions} - -```typespec -model TypeSpec.Http.PathOptions -``` - -#### Properties - -| Name | Type | Description | -| -------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name? | `string` | Name of the parameter in the uri template. | -| explode? | `boolean` | When interpolating this parameter in the case of array or object expand each value using the given style.
Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) | -| style? | `"simple" \| "label" \| "matrix" \| "fragment" \| "path"` | Different interpolating styles for the path parameter.
- `simple`: No special encoding.
- `label`: Using `.` separator.
- `matrix`: `;` as separator.
- `fragment`: `#` as separator.
- `path`: `/` as separator. | -| allowReserved? | `boolean` | When interpolating this parameter do not encode reserved characters.
Equivalent of adding `+` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) | - -### `PlainData` {#TypeSpec.Http.PlainData} - -Produces a new model with the same properties as T, but with `@query`, -`@header`, `@body`, and `@path` decorators removed from all properties. - -```typespec -model TypeSpec.Http.PlainData -``` - -#### Template Parameters - -| Name | Description | -| ---- | -------------------------------------- | -| Data | The model to spread as the plain data. | - -#### Properties - -None - -### `QueryOptions` {#TypeSpec.Http.QueryOptions} - -Query parameter options. - -```typespec -model TypeSpec.Http.QueryOptions -``` - -#### Properties - -| Name | Type | Description | -| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| name? | `string` | Name of the query when included in the url. | -| explode? | `boolean` | If true send each value in the array/object as a separate query parameter.
Equivalent of adding `*` in the path parameter as per [RFC-6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)

\| Style \| Explode \| Uri Template \| Primitive value id = 5 \| Array id = [3, 4, 5] \| Object id = {"role": "admin", "firstName": "Alex"} \|
\| ------ \| ------- \| -------------- \| ---------------------- \| ----------------------- \| -------------------------------------------------- \|
\| simple \| false \| `/users{?id}` \| `/users?id=5` \| `/users?id=3,4,5` \| `/users?id=role,admin,firstName,Alex` \|
\| simple \| true \| `/users{?id*}` \| `/users?id=5` \| `/users?id=3&id=4&id=5` \| `/users?role=admin&firstName=Alex` \| | - -### `Response` {#TypeSpec.Http.Response} - -Describes an HTTP response. - -```typespec -model TypeSpec.Http.Response -``` - -#### Template Parameters - -| Name | Description | -| ------ | -------------------------------- | -| Status | The status code of the response. | - -#### Properties - -| Name | Type | Description | -| ---------- | -------- | ----------- | -| statusCode | `Status` | | - -### `UnauthorizedResponse` {#TypeSpec.Http.UnauthorizedResponse} - -Access is unauthorized. - -```typespec -model TypeSpec.Http.UnauthorizedResponse -``` - -#### Properties - -| Name | Type | Description | -| ---------- | ----- | ---------------- | -| statusCode | `401` | The status code. | - -### `ApiKeyLocation` {#TypeSpec.Http.ApiKeyLocation} - -Describes the location of the API key - -```typespec -enum TypeSpec.Http.ApiKeyLocation -``` - -| Name | Value | Description | -| ------ | ----- | ---------------------------- | -| header | | API key is a header value | -| query | | API key is a query parameter | -| cookie | | API key is found in a cookie | - -### `AuthType` {#TypeSpec.Http.AuthType} - -Authentication type - -```typespec -enum TypeSpec.Http.AuthType -``` - -| Name | Value | Description | -| ------------- | ----- | -------------- | -| http | | HTTP | -| apiKey | | API key | -| oauth2 | | OAuth2 | -| openIdConnect | | OpenID connect | -| noAuth | | Empty auth | - -### `OAuth2FlowType` {#TypeSpec.Http.OAuth2FlowType} - -Describes the OAuth2 flow type - -```typespec -enum TypeSpec.Http.OAuth2FlowType -``` - -| Name | Value | Description | -| ----------------- | ----- | ----------------------- | -| authorizationCode | | authorization code flow | -| implicit | | implicit flow | -| password | | password flow | -| clientCredentials | | client credential flow | - -### `LinkHeader` {#TypeSpec.Http.LinkHeader} - -```typespec -scalar TypeSpec.Http.LinkHeader -``` - ## TypeSpec.Http.Streams ### `HttpStream` {#TypeSpec.Http.Streams.HttpStream} diff --git a/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md b/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md deleted file mode 100644 index 787fde8f8f0..00000000000 --- a/website/src/content/docs/docs/libraries/http/reference/streams/decorators.md +++ /dev/null @@ -1,581 +0,0 @@ ---- -title: "Decorators (streams)" -description: "Decorators exported by @typespec/http/streams" -toc_min_heading_level: 2 -toc_max_heading_level: 3 -llmstxt: true ---- - -## TypeSpec.Http - -### `@body` {#@TypeSpec.Http.body} - -Explicitly specify that this property type will be exactly the HTTP body. - -This means that any properties under `@body` cannot be marked as headers, query parameters, or path parameters. -If wanting to change the resolution of the body but still mix parameters, use `@bodyRoot`. - -```typespec -@TypeSpec.Http.body -``` - -#### Target - -`ModelProperty` - -#### Parameters - -None - -#### Examples - -```typespec -op upload(@body image: bytes): void; -op download(): { - @body image: bytes; -}; -``` - -### `@bodyIgnore` {#@TypeSpec.Http.bodyIgnore} - -Specify that this property shouldn't be included in the HTTP body. -This can be useful when bundling metadata together that would result in an empty property to be included in the body. - -```typespec -@TypeSpec.Http.bodyIgnore -``` - -#### Target - -`ModelProperty` - -#### Parameters - -None - -#### Examples - -```typespec -op upload( - name: string, - @bodyIgnore headers: { - @header id: string; - }, -): void; -``` - -### `@bodyRoot` {#@TypeSpec.Http.bodyRoot} - -Specify that the body resolution should be resolved from that property. -By default the body is resolved by including all properties in the operation request/response that are not metadata. -This allows to nest the body in a property while still allowing to use headers, query parameters, and path parameters in the same model. - -```typespec -@TypeSpec.Http.bodyRoot -``` - -#### Target - -`ModelProperty` - -#### Parameters - -None - -#### Examples - -```typespec -op upload( - @bodyRoot user: { - name: string; - @header id: string; - }, -): void; -op download(): { - @bodyRoot user: { - name: string; - @header id: string; - }; -}; -``` - -### `@cookie` {#@TypeSpec.Http.cookie} - -Specify this property is to be sent or received in the cookie. - -```typespec -@TypeSpec.Http.cookie(cookieNameOrOptions?: valueof string | TypeSpec.Http.CookieOptions) -``` - -#### Target - -`ModelProperty` - -#### Parameters - -| Name | Type | Description | -| ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| cookieNameOrOptions | `valueof string \| TypeSpec.Http.CookieOptions` | Optional name of the cookie in the cookie or cookie options.
By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`) | - -#### Examples - -```typespec -op read(@cookie token: string): { - data: string[]; -}; -op create( - @cookie({ - name: "auth_token", - }) - data: string[], -): void; -``` - -##### Implicit header name - -```typespec -op read(): { - @cookie authToken: string; -}; // headerName: auth_token -op update(@cookie AuthToken: string): void; // headerName: auth_token -``` - -### `@delete` {#@TypeSpec.Http.delete} - -Specify the HTTP verb for the target operation to be `DELETE`. - -```typespec -@TypeSpec.Http.delete -``` - -#### Target - -`Operation` - -#### Parameters - -None - -#### Examples - -```typespec -@delete op set(petId: string): void; -``` - -### `@get` {#@TypeSpec.Http.get} - -Specify the HTTP verb for the target operation to be `GET`. - -```typespec -@TypeSpec.Http.get -``` - -#### Target - -`Operation` - -#### Parameters - -None - -#### Examples - -```typespec -@get op read(): string; -``` - -### `@head` {#@TypeSpec.Http.head} - -Specify the HTTP verb for the target operation to be `HEAD`. - -```typespec -@TypeSpec.Http.head -``` - -#### Target - -`Operation` - -#### Parameters - -None - -#### Examples - -```typespec -@head op ping(petId: string): void; -``` - -### `@header` {#@TypeSpec.Http.header} - -Specify this property is to be sent or received as an HTTP header. - -```typespec -@TypeSpec.Http.header(headerNameOrOptions?: valueof string | TypeSpec.Http.HeaderOptions) -``` - -#### Target - -`ModelProperty` - -#### Parameters - -| Name | Type | Description | -| ------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| headerNameOrOptions | `valueof string \| TypeSpec.Http.HeaderOptions` | Optional name of the header when sent over HTTP or header options.
By default the header name will be the property name converted from camelCase to kebab-case. (e.g. `contentType` -> `content-type`) | - -#### Examples - -```typespec -op read(@header accept: string): { - @header("ETag") eTag: string; -}; -op create( - @header({ - name: "X-Color", - format: "csv", - }) - colors: string[], -): void; -``` - -##### Implicit header name - -```typespec -op read(): { - @header contentType: string; -}; // headerName: content-type -op update(@header ifMatch: string): void; // headerName: if-match -``` - -### `@multipartBody` {#@TypeSpec.Http.multipartBody} - -```typespec -@TypeSpec.Http.multipartBody -``` - -#### Target - -`ModelProperty` - -#### Parameters - -None - -#### Examples - -```tsp -op upload( - @header `content-type`: "multipart/form-data", - @multipartBody body: { - fullName: HttpPart; - headShots: HttpPart[]; - }, -): void; -``` - -### `@patch` {#@TypeSpec.Http.patch} - -Specify the HTTP verb for the target operation to be `PATCH`. - -```typespec -@TypeSpec.Http.patch(options?: valueof TypeSpec.Http.PatchOptions) -``` - -#### Target - -`Operation` - -#### Parameters - -| Name | Type | Description | -| ------- | ------------------------------------ | -------------------------------- | -| options | `valueof TypeSpec.Http.PatchOptions` | Options for the PATCH operation. | - -#### Examples - -```typespec -@patch op update(pet: Pet): void; -``` - -##### Using MergePatch template for proper merge-patch semantics - -```typespec -@patch op update(@body pet: MergePatchUpdate): void; -``` - -### `@path` {#@TypeSpec.Http.path} - -Explicitly specify that this property is to be interpolated as a path parameter. - -```typespec -@TypeSpec.Http.path(paramNameOrOptions?: valueof string | TypeSpec.Http.PathOptions) -``` - -#### Target - -`ModelProperty` - -#### Parameters - -| Name | Type | Description | -| ------------------ | --------------------------------------------- | -------------------------------------------------------------- | -| paramNameOrOptions | `valueof string \| TypeSpec.Http.PathOptions` | Optional name of the parameter in the uri template or options. | - -#### Examples - -```typespec -@route("/read/{explicit}/things/{implicit}") -op read(@path explicit: string, implicit: string): void; -``` - -### `@post` {#@TypeSpec.Http.post} - -Specify the HTTP verb for the target operation to be `POST`. - -```typespec -@TypeSpec.Http.post -``` - -#### Target - -`Operation` - -#### Parameters - -None - -#### Examples - -```typespec -@post op create(pet: Pet): void; -``` - -### `@put` {#@TypeSpec.Http.put} - -Specify the HTTP verb for the target operation to be `PUT`. - -```typespec -@TypeSpec.Http.put -``` - -#### Target - -`Operation` - -#### Parameters - -None - -#### Examples - -```typespec -@put op set(pet: Pet): void; -``` - -### `@query` {#@TypeSpec.Http.query} - -Specify this property is to be sent as a query parameter. - -```typespec -@TypeSpec.Http.query(queryNameOrOptions?: valueof string | TypeSpec.Http.QueryOptions) -``` - -#### Target - -`ModelProperty` - -#### Parameters - -| Name | Type | Description | -| ------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------- | -| queryNameOrOptions | `valueof string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. | - -#### Examples - -```typespec -op read(@query select: string, @query("order-by") orderBy: string): void; -op list(@query(#{ name: "id", explode: true }) ids: string[]): void; -``` - -### `@route` {#@TypeSpec.Http.route} - -Defines the relative route URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3) - -`@route` can only be applied to operations, namespaces, and interfaces. - -```typespec -@TypeSpec.Http.route(path: valueof string) -``` - -#### Target - -`Namespace | Interface | Operation` - -#### Parameters - -| Name | Type | Description | -| ---- | ---------------- | ----------- | -| path | `valueof string` | | - -#### Examples - -##### Simple path parameter - -```typespec -@route("/widgets/{id}") op getWidget(@path id: string): Widget; -``` - -##### Reserved characters - -```typespec -@route("/files{+path}") op getFile(@path path: string): bytes; -``` - -##### Query parameter - -```typespec -@route("/files") op list(select?: string, filter?: string): Files[]; -@route("/files{?select,filter}") op listFullUriTemplate(select?: string, filter?: string): Files[]; -``` - -### `@server` {#@TypeSpec.Http.server} - -Specify an endpoint for this service. Multiple `@server` decorators can be used to specify multiple endpoints. - -```typespec -@TypeSpec.Http.server(url: valueof string, description?: valueof string, parameters?: Record) -``` - -#### Target - -`Namespace` - -#### Parameters - -| Name | Type | Description | -| ----------- | ----------------- | ------------------------------------------------------- | -| url | `valueof string` | Server endpoint | -| description | `valueof string` | Description of the endpoint | -| parameters | `Record` | Optional set of parameters used to interpolate the url. | - -#### Examples - -```typespec -@service -@server("https://example.com") -namespace PetStore; -``` - -##### With a description - -```typespec -@service -@server("https://example.com", "Single server endpoint") -namespace PetStore; -``` - -##### Parameterized - -```typespec -@server("https://{region}.foo.com", "Regional endpoint", { - @doc("Region name") - region?: string = "westus", -}) -``` - -##### Multiple - -```typespec -@service -@server("https://example.com", "Standard endpoint") -@server( - "https://{project}.private.example.com", - "Private project endpoint", - { - project: string, - } -) -namespace PetStore; -``` - -### `@sharedRoute` {#@TypeSpec.Http.sharedRoute} - -`@sharedRoute` marks the operation as sharing a route path with other operations. - -When an operation is marked with `@sharedRoute`, it enables other operations to share the same -route path as long as those operations are also marked with `@sharedRoute`. - -`@sharedRoute` can only be applied directly to operations. - -```typespec -@sharedRoute -@route("/widgets") -op getWidget(@path id: string): Widget; -``` - -```typespec -@TypeSpec.Http.sharedRoute -``` - -#### Target - -`Operation` - -#### Parameters - -None - -### `@statusCode` {#@TypeSpec.Http.statusCode} - -Specify the status code for this response. Property type must be a status code integer or a union of status code integer. - -```typespec -@TypeSpec.Http.statusCode -``` - -#### Target - -`ModelProperty` - -#### Parameters - -None - -#### Examples - -```typespec -op read(): { - @statusCode _: 200; - @body pet: Pet; -}; -op create(): { - @statusCode _: 201 | 202; -}; -``` - -### `@useAuth` {#@TypeSpec.Http.useAuth} - -Specify authentication for a whole service or specific methods. See the [documentation in the Http library](https://typespec.io/docs/libraries/http/authentication) for full details. - -```typespec -@TypeSpec.Http.useAuth(auth: {} | Union | {}[]) -``` - -#### Target - -`Namespace | Interface | Operation` - -#### Parameters - -| Name | Type | Description | -| ---- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| auth | `{} \| Union \| {}[]` | Authentication configuration. Can be a single security scheme, a union(either option is valid authentication) or a tuple (must use all authentication together) | - -#### Examples - -```typespec -@service -@useAuth(BasicAuth) -namespace PetStore; -```