diff --git a/.chronus/changes/diagnostic-docs-openapi3-2026-6-9.md b/.chronus/changes/diagnostic-docs-openapi3-2026-6-9.md new file mode 100644 index 00000000000..d0f48cdd763 --- /dev/null +++ b/.chronus/changes/diagnostic-docs-openapi3-2026-6-9.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/openapi3" +--- + +Provide extended documentation for several diagnostics (`path-query`, `duplicate-header`, `inline-cycle`, `invalid-schema`, `invalid-server-variable`, `union-null`) via co-located markdown files. diff --git a/.chronus/changes/rule-diagnostic-docs-tspd-2026-6-9.md b/.chronus/changes/rule-diagnostic-docs-tspd-2026-6-9.md new file mode 100644 index 00000000000..394dff1e736 --- /dev/null +++ b/.chronus/changes/rule-diagnostic-docs-tspd-2026-6-9.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/tspd" +--- + +`tspd doc` now generates a documentation page per linter rule (`reference/rules/.md`) and per diagnostic (`reference/diagnostics/.md`) plus a diagnostics index, sourced from the `docs` field on the definitions. A `documentation-missing` warning is now also reported for linter rules and diagnostics without documentation. diff --git a/.chronus/changes/rule-docs-http-2026-6-9.md b/.chronus/changes/rule-docs-http-2026-6-9.md new file mode 100644 index 00000000000..d36a6ffc4c8 --- /dev/null +++ b/.chronus/changes/rule-docs-http-2026-6-9.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http" +--- + +Provide extended documentation for the `op-reference-container-route` linter rule via a co-located markdown file. diff --git a/packages/http/README.md b/packages/http/README.md index 3600a1dfb10..5f7dc9e0d3c 100644 --- a/packages/http/README.md +++ b/packages/http/README.md @@ -26,9 +26,9 @@ Available ruleSets: ## Rules -| Name | Description | -| --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| [`@typespec/http/op-reference-container-route`](https://typespec.io/docs/libraries/http/rules/op-reference-container-route) | Check for referenced (`op is`) operations which have a @route on one of their containers. | +| Name | Description | +| ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| [`@typespec/http/op-reference-container-route`](https://typespec.io/docs/libraries/http/reference/rules/op-reference-container-route) | Check for referenced (`op is`) operations which have a @route on one of their containers. | ## Decorators diff --git a/packages/http/src/rules/op-reference-container-route.md b/packages/http/src/rules/op-reference-container-route.md new file mode 100644 index 00000000000..509246c8c54 --- /dev/null +++ b/packages/http/src/rules/op-reference-container-route.md @@ -0,0 +1,38 @@ +When referencing an operation with `op is`, only the data on the operation itself is carried over; anything on the parent container is lost. +This results in unexpected behavior where information is lost. +As a best practice the route should be provided on the operation itself. + +#### ❌ Incorrect + +```tsp +namespace Library { + @route("/pets") + interface Pets { + @route("/read") read(): string; + } +} + +@service +namespace Service { + interface PetStore { + readPet is Library.Pets.read; + } +} +``` + +#### ✅ Correct + +```tsp +namespace Library { + interface Pets { + @route("/pets/read") read(): string; + } +} + +@service +namespace Service { + interface PetStore { + readPet is Library.Pets.read; + } +} +``` diff --git a/packages/http/src/rules/op-reference-container-route.ts b/packages/http/src/rules/op-reference-container-route.ts index 2f59f779efd..fa948897352 100644 --- a/packages/http/src/rules/op-reference-container-route.ts +++ b/packages/http/src/rules/op-reference-container-route.ts @@ -7,7 +7,7 @@ export const opReferenceContainerRouteRule = createRule({ severity: "warning", description: "Check for referenced (`op is`) operations which have a @route on one of their containers.", - url: "https://typespec.io/docs/libraries/http/rules/op-reference-container-route", + url: "https://typespec.io/docs/libraries/http/reference/rules/op-reference-container-route", messages: { default: paramMessage`Operation ${"opName"} references an operation which has a @route prefix on its namespace or interface: "${"routePrefix"}". This operation will not carry forward the route prefix so the final route may be different than the referenced operation.`, }, diff --git a/packages/openapi3/src/diagnostics/duplicate-header.md b/packages/openapi3/src/diagnostics/duplicate-header.md new file mode 100644 index 00000000000..0ddd10ecde7 --- /dev/null +++ b/packages/openapi3/src/diagnostics/duplicate-header.md @@ -0,0 +1,22 @@ +This diagnostic is issued when a response header is defined more than once for a response of a specific status code. + +To fix this issue, ensure that each response header is defined only once for each status code. + +### Example + +```yaml +responses: + "200": + description: Successful response + headers: + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer +``` + +In this example, the `X-Rate-Limit` header is defined twice for the `200` status code. To fix this issue, remove the duplicate header definition. diff --git a/packages/openapi3/src/diagnostics/inline-cycle.md b/packages/openapi3/src/diagnostics/inline-cycle.md new file mode 100644 index 00000000000..4e4c3b635b5 --- /dev/null +++ b/packages/openapi3/src/diagnostics/inline-cycle.md @@ -0,0 +1,19 @@ +This diagnostic is issued when a cyclic reference is detected within inline schemas. + +To fix this issue, refactor the schemas to remove the cyclic reference. + +### Example + +```yaml +components: + schemas: + Node: + type: object + properties: + value: + type: string + next: + $ref: "#/components/schemas/Node" +``` + +In this example, the `Node` schema references itself, creating a cyclic reference. To fix this issue, refactor the schema to remove the cyclic reference. diff --git a/packages/openapi3/src/diagnostics/invalid-schema.md b/packages/openapi3/src/diagnostics/invalid-schema.md new file mode 100644 index 00000000000..1e42f170432 --- /dev/null +++ b/packages/openapi3/src/diagnostics/invalid-schema.md @@ -0,0 +1,20 @@ +This diagnostic is issued when a schema is invalid according to the OpenAPI v3 specification. + +To fix this issue, review your TypeSpec definitions to ensure they map to valid OpenAPI schemas. + +### Example + +```yaml +components: + schemas: + User: + type: object + properties: + id: + type: string + age: + type: integer + format: "int" # Invalid format +``` + +In this example, the `format` value for the `age` property is invalid. To fix this issue, provide a valid format value such as `int32` or `int64`. diff --git a/packages/openapi3/src/diagnostics/invalid-server-variable.md b/packages/openapi3/src/diagnostics/invalid-server-variable.md new file mode 100644 index 00000000000..fe1c6d6d453 --- /dev/null +++ b/packages/openapi3/src/diagnostics/invalid-server-variable.md @@ -0,0 +1,14 @@ +This diagnostic is issued when a variable in the `@server` decorator is not defined as a string type. +Since server variables are substituted into the server URL which is a string, all variables must have string values. + +To fix this issue, make sure all server variables are of a type that is assignable to `string`. + +### Example + +```typespec +@server("{protocol}://{host}/api/{version}", "Custom endpoint", { + protocol: "http" | "https", + host: string, + version: 1, // Should be a string: "1" +}) +``` diff --git a/packages/openapi3/src/diagnostics/path-query.md b/packages/openapi3/src/diagnostics/path-query.md new file mode 100644 index 00000000000..38e8aee9194 --- /dev/null +++ b/packages/openapi3/src/diagnostics/path-query.md @@ -0,0 +1,26 @@ +This diagnostic is issued when the OpenAPI emitter finds an `@route` decorator that specifies a path that contains a query parameter. This is not permitted by the OpenAPI v3 specification, which requires query parameters to be defined separately. + +To fix this issue, redesign the API to only use paths without query parameters, and define query parameters using the `@query` decorator. + +### Example + +Instead of: + +```typespec +@route("/users?filter={filter}") +op getUsers(filter: string): User[]; +``` + +Use: + +```typespec +@route("/users") +op getUsers(@query filter?: string): User[]; +``` + +Alternatively, you can leverage TypeSpec's support for URI templates: + +```typespec +@route("/users{?filter}") +op getUsers(filter?: string): User[]; +``` diff --git a/packages/openapi3/src/diagnostics/union-null.md b/packages/openapi3/src/diagnostics/union-null.md new file mode 100644 index 00000000000..f75fc66f844 --- /dev/null +++ b/packages/openapi3/src/diagnostics/union-null.md @@ -0,0 +1,3 @@ +This diagnostic is issued when the result of model composition is effectively a `null` schema which cannot be represented in OpenAPI. + +To fix this issue, review your model compositions to ensure they produce valid schemas with actual properties or types. diff --git a/packages/tspd/src/ref-doc/emitters/starlight.ts b/packages/tspd/src/ref-doc/emitters/starlight.ts index f43142108ea..96d88252daa 100644 --- a/packages/tspd/src/ref-doc/emitters/starlight.ts +++ b/packages/tspd/src/ref-doc/emitters/starlight.ts @@ -1,5 +1,6 @@ import { DeprecationNotice, + DiagnosticRefDoc, LinterRuleRefDoc, NamedTypeRefDoc, RefDocEntity, @@ -57,6 +58,17 @@ export function renderToAstroStarlightMarkdown( files["linter.md"] = linter; } + for (const rule of refDoc.linter?.rules ?? []) { + files[`rules/${rule.rule.name}.md`] = renderRule(rule); + } + + // Generate one page per documented diagnostic, under `diagnostics/`. No index page. + for (const diagnostic of refDoc.diagnostics ?? []) { + if (diagnostic.doc) { + files[`diagnostics/${diagnostic.name}.md`] = renderDiagnostic(diagnostic); + } + } + // Render sub-exports if (refDoc.subExports) { for (const [exportPath, subExport] of refDoc.subExports) { @@ -277,7 +289,38 @@ function renderLinter( "---", renderer.linterUsage(refDoc), ]; + return renderMarkdowDoc(content, 2); +} + +function renderRule(rule: LinterRuleRefDoc): string { + const content: MarkdownDoc = [ + "---", + `title: "${rule.rule.name}"`, + "---", + "", + codeblock(rule.name, 'text title="Id"'), + "", + rule.rule.description, + ]; + if (rule.doc) { + content.push("", rule.doc); + } + return renderMarkdowDoc(content, 2); +} +function renderDiagnostic(diagnostic: DiagnosticRefDoc): string { + const content: MarkdownDoc = [ + "---", + `title: "${diagnostic.name}"`, + "---", + "", + codeblock(diagnostic.id, 'text title="Id"'), + "", + `**Severity:** ${diagnostic.severity}`, + ]; + if (diagnostic.doc) { + content.push("", diagnostic.doc); + } return renderMarkdowDoc(content, 2); } @@ -430,7 +473,7 @@ export class StarlightRenderer extends MarkdownRenderer { } linterRuleLink(rule: LinterRuleRefDoc) { - return `../rules/${rule.rule.name}.md`; + return `./rules/${rule.rule.name}.md`; } deprecationNotice(notice: DeprecationNotice): MarkdownDoc { diff --git a/packages/tspd/src/ref-doc/extractor.ts b/packages/tspd/src/ref-doc/extractor.ts index 55614f5d95a..5e1527cc2c6 100644 --- a/packages/tspd/src/ref-doc/extractor.ts +++ b/packages/tspd/src/ref-doc/extractor.ts @@ -4,6 +4,7 @@ import { createDiagnosticCollector, Decorator, Diagnostic, + DiagnosticDefinition, DocContent, Enum, EnumMember, @@ -40,12 +41,14 @@ import { type PackageJson, } from "@typespec/compiler"; import { SyntaxKind, type DocUnknownTagNode } from "@typespec/compiler/ast"; +import { readFileSync } from "fs"; import { readFile } from "fs/promises"; import { pathToFileURL } from "url"; -import { reportDiagnostic } from "./lib.js"; +import { createDiagnostic, reportDiagnostic } from "./lib.js"; import { DecoratorRefDoc, DeprecationNotice, + DiagnosticRefDoc, EmitterOptionRefDoc, EmitterOptionVariantRefDoc, EnumMemberRefDoc, @@ -125,7 +128,47 @@ export async function extractLibraryRefDocs( } const linter = entrypoint.$linter; if (lib && linter) { - refDoc.linter = extractLinterRefDoc(lib.name, resolveLinterDefinition(lib.name, linter)); + const resolved = resolveLinterDefinition(lib.name, linter); + refDoc.linter = extractLinterRefDoc(lib.name, resolved, libraryPath); + // Only nag about missing docs for libraries that have started documenting their + // rules, to avoid noise for libraries that haven't opted in yet. + if (refDoc.linter.rules.some((r) => r.doc)) { + for (const r of refDoc.linter.rules) { + if (!r.doc) { + diagnostics.add( + createDiagnostic({ + code: "documentation-missing", + messageId: "rule", + format: { name: r.id }, + target: NoTarget, + }), + ); + } + } + } + } + if (lib?.diagnostics) { + refDoc.diagnostics = extractDiagnosticsRefDoc( + lib.name, + lib.diagnostics as Record>, + libraryPath, + ); + // Only nag about missing docs for libraries that have started documenting their + // diagnostics, to avoid noise for libraries that haven't opted in yet. + if (refDoc.diagnostics.some((diag) => diag.doc)) { + for (const diag of refDoc.diagnostics) { + if (!diag.doc) { + diagnostics.add( + createDiagnostic({ + code: "documentation-missing", + messageId: "diagnostic", + format: { name: diag.id }, + target: NoTarget, + }), + ); + } + } + } } } @@ -904,13 +947,38 @@ function resolveDescription(description: string | string[] | undefined): string return Array.isArray(description) ? description.join("\n") : description; } -function extractLinterRefDoc(libName: string, linter: LinterResolvedDefinition): LinterRefDoc { +function tryReadDoc(libraryPath: string, relativePath: string): string | undefined { + try { + return readFileSync(joinPaths(libraryPath, relativePath), "utf-8"); + } catch { + return undefined; + } +} + +function extractLinterRefDoc( + libName: string, + linter: LinterResolvedDefinition, + libraryPath: string, +): LinterRefDoc { return { ruleSets: linter.ruleSets && extractLinterRuleSetsRefDoc(libName, linter.ruleSets), - rules: linter.rules.map((rule) => extractLinterRuleRefDoc(libName, rule)), + rules: linter.rules.map((rule) => extractLinterRuleRefDoc(libName, rule, libraryPath)), }; } +function extractDiagnosticsRefDoc( + libName: string, + diagnostics: Record>, + libraryPath: string, +): DiagnosticRefDoc[] { + return Object.entries(diagnostics).map(([name, def]) => ({ + id: `${libName}/${name}`, + name, + severity: def.severity, + doc: tryReadDoc(libraryPath, `src/diagnostics/${name}.md`), + })); +} + function extractLinterRuleSetsRefDoc( libName: string, ruleSets: Record, @@ -928,6 +996,7 @@ function extractLinterRuleSetsRefDoc( function extractLinterRuleRefDoc( libName: string, rule: LinterRuleDefinition, + libraryPath: string, ): LinterRuleRefDoc { const fullName = `${libName}/${rule.name}`; return { @@ -935,5 +1004,6 @@ function extractLinterRuleRefDoc( id: fullName, name: fullName, rule, + doc: tryReadDoc(libraryPath, `src/rules/${rule.name}.md`), }; } diff --git a/packages/tspd/src/ref-doc/lib.ts b/packages/tspd/src/ref-doc/lib.ts index 482e4ce199d..2bab7b16011 100644 --- a/packages/tspd/src/ref-doc/lib.ts +++ b/packages/tspd/src/ref-doc/lib.ts @@ -23,6 +23,8 @@ export const libDef = { interfaceOperation: paramMessage`Missing documentation for interface operation '${"name"}'.`, operation: paramMessage`Missing documentation for operation '${"name"}'.`, scalar: paramMessage`Missing documentation for scalar '${"name"}'.`, + rule: paramMessage`Missing documentation for linter rule '${"name"}'.`, + diagnostic: paramMessage`Missing documentation for diagnostic '${"name"}'.`, }, }, }, diff --git a/packages/tspd/src/ref-doc/types.ts b/packages/tspd/src/ref-doc/types.ts index 7929a9a2d4a..d9beb409e58 100644 --- a/packages/tspd/src/ref-doc/types.ts +++ b/packages/tspd/src/ref-doc/types.ts @@ -39,6 +39,9 @@ export type TypeSpecLibraryRefDoc = TypeSpecRefDocBase & { /** Documentation about the linter rules and ruleset provided in this library. */ readonly linter?: LinterRefDoc; + /** Documentation about the diagnostics that this library can report. */ + readonly diagnostics?: readonly DiagnosticRefDoc[]; + /** Documentation for sub-exports (e.g., "./streams", "./testing"). Keyed by export path. */ readonly subExports?: ReadonlyMap; }; @@ -72,6 +75,14 @@ export type LinterRuleSetRefDoc = ReferencableElement & { export type LinterRuleRefDoc = ReferencableElement & { readonly kind: "rule"; readonly rule: LinterRuleDefinition; + /** Extended documentation as raw markdown, read from a co-located `.md` file. */ + readonly doc?: string; +}; + +export type DiagnosticRefDoc = ReferencableElement & { + readonly severity: "warning" | "error"; + /** Extended documentation as raw markdown, read from a co-located `.md` file. */ + readonly doc?: string; }; export type EmitterOptionRefDoc = { diff --git a/website/src/content/current-sidebar.ts b/website/src/content/current-sidebar.ts index b6e8393692f..7e5b68baf66 100644 --- a/website/src/content/current-sidebar.ts +++ b/website/src/content/current-sidebar.ts @@ -124,7 +124,7 @@ const sidebar: SidebarItem[] = [ { label: "📚 Libraries", items: [ - createLibraryReferenceStructure("libraries/http", "Http", true, [ + createLibraryReferenceStructure("libraries/http", "Http", false, [ "libraries/http/cheat-sheet", "libraries/http/authentication", "libraries/http/operations", @@ -168,7 +168,6 @@ const sidebar: SidebarItem[] = [ createLibraryReferenceStructure("emitters/openapi3", "OpenAPI3", false, [ "emitters/openapi3/openapi", "emitters/openapi3/cli", - "emitters/openapi3/diagnostics", ]), createLibraryReferenceStructure( "emitters/protobuf", diff --git a/website/src/content/docs/docs/emitters/openapi3/diagnostics.md b/website/src/content/docs/docs/emitters/openapi3/diagnostics.md deleted file mode 100644 index e642ce30029..00000000000 --- a/website/src/content/docs/docs/emitters/openapi3/diagnostics.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -title: Diagnostics ---- - -The OpenAPI emitter may produce any of the following diagnostic messages. - - - -## duplicate-header - -This diagnostic is issued when a response header is defined more than once for a response of a specific status code. - -To fix this issue, ensure that each response header is defined only once for each status code. - -### Example - -```yaml -responses: - "200": - description: Successful response - headers: - X-Rate-Limit: - description: The number of allowed requests in the current period - schema: - type: integer - X-Rate-Limit: - description: The number of allowed requests in the current period - schema: - type: integer -``` - -In this example, the `X-Rate-Limit` header is defined twice for the `200` status code. To fix this issue, remove the duplicate header definition. - -## duplicate-type-name - -This diagnostic is issued when a schema or parameter name is a duplicate of another schema or parameter. This generally happens when a model or parameter is renamed with the `@friendlyName` decorator, resulting in two different TypeSpec types getting the same name in the OpenAPI output. - -To fix this issue, change the name or friendly-name of one of the models or parameters. - -### Example - -```typespec -@friendlyName("User") -model Customer { - id: string; -} - -model User { - id: string; -} -``` - -In this example, both `Customer` and `User` would appear as `User` in the OpenAPI output, causing a conflict. - -## inline-cycle - -This diagnostic is issued when a cyclic reference is detected within inline schemas. - -To fix this issue, refactor the schemas to remove the cyclic reference. - -### Example - -```yaml -components: - schemas: - Node: - type: object - properties: - value: - type: string - next: - $ref: "#/components/schemas/Node" -``` - -In this example, the `Node` schema references itself, creating a cyclic reference. To fix this issue, refactor the schema to remove the cyclic reference. - -## invalid-default - -This diagnostic is issued when a default value is invalid for the specified schema type. - -To fix this issue, ensure that the default value is valid for the schema type. - -### Example - -```yaml -components: - schemas: - User: - type: object - properties: - age: - type: integer - default: "twenty" -``` - -In this example, the `default` value for the `age` property is invalid because it is a string instead of an integer. To fix this issue, provide a valid default value, such as `20`. - -## invalid-extension-key - -This diagnostic is issued by the `@extension` decorator when the extension key does not start with "x-" as -required by the OpenAPI v3 specification. - -To fix this issue, change the extension name to start with "x-". - -### Example - -```typespec -@extension("invalid-name", "value") -model User { - id: string; -} -``` - -Should be changed to: - -```typespec -@extension("x-valid-name", "value") -model User { - id: string; -} -``` - -## invalid-schema - -This diagnostic is issued when a schema is invalid according to the OpenAPI v3 specification. - -To fix this issue, review your TypeSpec definitions to ensure they map to valid OpenAPI schemas. - -### Example - -```yaml -components: - schemas: - User: - type: object - properties: - id: - type: string - age: - type: integer - format: "int" # Invalid format -``` - -In this example, the `format` value for the `age` property is invalid. To fix this issue, provide a valid format value such as `int32` or `int64`. - -## invalid-server-variable - -This diagnostic is issued when a variable in the `@server` decorator is not defined as a string type. -Since server variables are substituted into the server URL which is a string, all variables must have string values. - -To fix this issue, make sure all server variables are of a type that is assignable to `string`. - -### Example - -```typespec -@server("{protocol}://{host}/api/{version}", "Custom endpoint", { - protocol: "http" | "https", - host: string, - version: 1, // Should be a string: "1" -}) -``` - -## path-query - -This diagnostic is issued when the OpenAPI emitter finds an `@route` decorator that specifies a path that contains a query parameter. This is not permitted by the OpenAPI v3 specification, which requires query parameters to be defined separately. - -To fix this issue, redesign the API to only use paths without query parameters, and define query parameters using the `@query` decorator. - -### Example - -Instead of: - -```typespec -@route("/users?filter={filter}") -op getUsers(filter: string): User[]; -``` - -Use: - -```typespec -@route("/users") -op getUsers(@query filter?: string): User[]; -``` - -Alternatively, you can leverage TypeSpec's support for URI templates: - -```typespec -@route("/users{?filter}") -op getUsers(filter?: string): User[]; -``` - -## union-null - -This diagnostic is issued when the result of model composition is effectively a `null` schema which cannot be -represented in OpenAPI. - -To fix this issue, review your model compositions to ensure they produce valid schemas with actual properties or types. - -## union-unsupported - -This diagnostic is issued when the OpenAPI emitter finds a union of two incompatible types that cannot be represented in OpenAPI. OpenAPI has limited support for union types, and some combinations cannot be expressed. - -To fix this issue, consider restructuring your types to avoid incompatible unions, or split the operation into multiple operations with different return types. diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/duplicate-header.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/duplicate-header.md new file mode 100644 index 00000000000..935f3dda65c --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/duplicate-header.md @@ -0,0 +1,32 @@ +--- +title: "duplicate-header" +--- + +```text title="Id" +@typespec/openapi3/duplicate-header +``` + +**Severity:** error + +This diagnostic is issued when a response header is defined more than once for a response of a specific status code. + +To fix this issue, ensure that each response header is defined only once for each status code. + +### Example + +```yaml +responses: + "200": + description: Successful response + headers: + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + X-Rate-Limit: + description: The number of allowed requests in the current period + schema: + type: integer +``` + +In this example, the `X-Rate-Limit` header is defined twice for the `200` status code. To fix this issue, remove the duplicate header definition. diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/inline-cycle.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/inline-cycle.md new file mode 100644 index 00000000000..62e392e1c08 --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/inline-cycle.md @@ -0,0 +1,29 @@ +--- +title: "inline-cycle" +--- + +```text title="Id" +@typespec/openapi3/inline-cycle +``` + +**Severity:** error + +This diagnostic is issued when a cyclic reference is detected within inline schemas. + +To fix this issue, refactor the schemas to remove the cyclic reference. + +### Example + +```yaml +components: + schemas: + Node: + type: object + properties: + value: + type: string + next: + $ref: "#/components/schemas/Node" +``` + +In this example, the `Node` schema references itself, creating a cyclic reference. To fix this issue, refactor the schema to remove the cyclic reference. diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-schema.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-schema.md new file mode 100644 index 00000000000..ad052ec03b8 --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-schema.md @@ -0,0 +1,30 @@ +--- +title: "invalid-schema" +--- + +```text title="Id" +@typespec/openapi3/invalid-schema +``` + +**Severity:** error + +This diagnostic is issued when a schema is invalid according to the OpenAPI v3 specification. + +To fix this issue, review your TypeSpec definitions to ensure they map to valid OpenAPI schemas. + +### Example + +```yaml +components: + schemas: + User: + type: object + properties: + id: + type: string + age: + type: integer + format: "int" # Invalid format +``` + +In this example, the `format` value for the `age` property is invalid. To fix this issue, provide a valid format value such as `int32` or `int64`. diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-server-variable.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-server-variable.md new file mode 100644 index 00000000000..26c49e3aee1 --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/invalid-server-variable.md @@ -0,0 +1,24 @@ +--- +title: "invalid-server-variable" +--- + +```text title="Id" +@typespec/openapi3/invalid-server-variable +``` + +**Severity:** error + +This diagnostic is issued when a variable in the `@server` decorator is not defined as a string type. +Since server variables are substituted into the server URL which is a string, all variables must have string values. + +To fix this issue, make sure all server variables are of a type that is assignable to `string`. + +### Example + +```typespec +@server("{protocol}://{host}/api/{version}", "Custom endpoint", { + protocol: "http" | "https", + host: string, + version: 1, // Should be a string: "1" +}) +``` diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/path-query.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/path-query.md new file mode 100644 index 00000000000..22a9107204f --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/path-query.md @@ -0,0 +1,36 @@ +--- +title: "path-query" +--- + +```text title="Id" +@typespec/openapi3/path-query +``` + +**Severity:** error + +This diagnostic is issued when the OpenAPI emitter finds an `@route` decorator that specifies a path that contains a query parameter. This is not permitted by the OpenAPI v3 specification, which requires query parameters to be defined separately. + +To fix this issue, redesign the API to only use paths without query parameters, and define query parameters using the `@query` decorator. + +### Example + +Instead of: + +```typespec +@route("/users?filter={filter}") +op getUsers(filter: string): User[]; +``` + +Use: + +```typespec +@route("/users") +op getUsers(@query filter?: string): User[]; +``` + +Alternatively, you can leverage TypeSpec's support for URI templates: + +```typespec +@route("/users{?filter}") +op getUsers(filter?: string): User[]; +``` diff --git a/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/union-null.md b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/union-null.md new file mode 100644 index 00000000000..4d1113c7785 --- /dev/null +++ b/website/src/content/docs/docs/emitters/openapi3/reference/diagnostics/union-null.md @@ -0,0 +1,13 @@ +--- +title: "union-null" +--- + +```text title="Id" +@typespec/openapi3/union-null +``` + +**Severity:** error + +This diagnostic is issued when the result of model composition is effectively a `null` schema which cannot be represented in OpenAPI. + +To fix this issue, review your model compositions to ensure they produce valid schemas with actual properties or types. diff --git a/website/src/content/docs/docs/libraries/http/reference/linter.md b/website/src/content/docs/docs/libraries/http/reference/linter.md index d51c365da51..f6e795cd91e 100644 --- a/website/src/content/docs/docs/libraries/http/reference/linter.md +++ b/website/src/content/docs/docs/libraries/http/reference/linter.md @@ -20,6 +20,6 @@ Available ruleSets: ## Rules -| Name | Description | -| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| [`@typespec/http/op-reference-container-route`](../rules/op-reference-container-route.md) | Check for referenced (`op is`) operations which have a @route on one of their containers. | +| Name | Description | +| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| [`@typespec/http/op-reference-container-route`](./rules/op-reference-container-route.md) | Check for referenced (`op is`) operations which have a @route on one of their containers. | diff --git a/website/src/content/docs/docs/libraries/http/rules/op-reference-container-route.md b/website/src/content/docs/docs/libraries/http/reference/rules/op-reference-container-route.md similarity index 67% rename from website/src/content/docs/docs/libraries/http/rules/op-reference-container-route.md rename to website/src/content/docs/docs/libraries/http/reference/rules/op-reference-container-route.md index 1574664af43..2a746e9f825 100644 --- a/website/src/content/docs/docs/libraries/http/rules/op-reference-container-route.md +++ b/website/src/content/docs/docs/libraries/http/reference/rules/op-reference-container-route.md @@ -6,10 +6,10 @@ title: "op-reference-container-route" @typespec/http/op-reference-container-route ``` -Check for referenced (`op is`) operations which have a `@route` on one of their containers. +Check for referenced (`op is`) operations which have a @route on one of their containers. -When referencing an operation with `op is` only the data on the operation itself is carried over anything on parent container is lost. -This result in unexpected behavior where information is lost. +When referencing an operation with `op is`, only the data on the operation itself is carried over; anything on the parent container is lost. +This results in unexpected behavior where information is lost. As a best practice the route should be provided on the operation itself. #### ❌ Incorrect