Add api extractor for openapi package and fix issues (#4354)
This commit is contained in:
Родитель
86cc361bf0
Коммит
dde8dc0ca7
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
changeKind: internal
|
||||||
|
packages:
|
||||||
|
- "@typespec/openapi"
|
||||||
|
---
|
||||||
|
|
||||||
|
Add api extractor setup
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||||
|
"extends": "../../api-extractor.base.json"
|
||||||
|
}
|
|
@ -34,7 +34,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./dist ./temp",
|
"clean": "rimraf ./dist ./temp",
|
||||||
"build": "npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library",
|
"build": "pnpm gen-extern-signature && tsc -p . && pnpm lint-typespec-library && pnpm api-extractor",
|
||||||
"watch": "tsc -p . --watch",
|
"watch": "tsc -p . --watch",
|
||||||
"gen-extern-signature": "tspd --enable-experimental gen-extern-signature .",
|
"gen-extern-signature": "tspd --enable-experimental gen-extern-signature .",
|
||||||
"lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit",
|
"lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit",
|
||||||
|
@ -44,7 +44,8 @@
|
||||||
"test:ci": "vitest run --coverage --reporter=junit --reporter=default",
|
"test:ci": "vitest run --coverage --reporter=junit --reporter=default",
|
||||||
"lint": "eslint . --max-warnings=0",
|
"lint": "eslint . --max-warnings=0",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"regen-docs": "tspd doc . --enable-experimental --output-dir ../../docs/libraries/openapi/reference"
|
"regen-docs": "tspd doc . --enable-experimental --output-dir ../../docs/libraries/openapi/reference",
|
||||||
|
"api-extractor": "api-extractor run --local --verbose"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib/*.tsp",
|
"lib/*.tsp",
|
||||||
|
|
|
@ -18,10 +18,9 @@ import {
|
||||||
ExternalDocsDecorator,
|
ExternalDocsDecorator,
|
||||||
InfoDecorator,
|
InfoDecorator,
|
||||||
OperationIdDecorator,
|
OperationIdDecorator,
|
||||||
TypeSpecOpenAPIDecorators,
|
|
||||||
} from "../generated-defs/TypeSpec.OpenAPI.js";
|
} from "../generated-defs/TypeSpec.OpenAPI.js";
|
||||||
import { createStateSymbol, reportDiagnostic } from "./lib.js";
|
import { createStateSymbol, reportDiagnostic } from "./lib.js";
|
||||||
import { AdditionalInfo, ExtensionKey } from "./types.js";
|
import { AdditionalInfo, ExtensionKey, ExternalDocs } from "./types.js";
|
||||||
|
|
||||||
const operationIdsKey = createStateSymbol("operationIds");
|
const operationIdsKey = createStateSymbol("operationIds");
|
||||||
/**
|
/**
|
||||||
|
@ -39,7 +38,7 @@ export const $operationId: OperationIdDecorator = (
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns operationId set via the @operationId decorator or `undefined`
|
* Returns operationId set via the `@operationId` decorator or `undefined`
|
||||||
*/
|
*/
|
||||||
export function getOperationId(program: Program, entity: Operation): string | undefined {
|
export function getOperationId(program: Program, entity: Operation): string | undefined {
|
||||||
return program.stateMap(operationIdsKey).get(entity);
|
return program.stateMap(operationIdsKey).get(entity);
|
||||||
|
@ -47,6 +46,7 @@ export function getOperationId(program: Program, entity: Operation): string | un
|
||||||
|
|
||||||
const openApiExtensionKey = createStateSymbol("openApiExtension");
|
const openApiExtensionKey = createStateSymbol("openApiExtension");
|
||||||
|
|
||||||
|
/** {@inheritdoc ExtensionDecorator} */
|
||||||
export const $extension: ExtensionDecorator = (
|
export const $extension: ExtensionDecorator = (
|
||||||
context: DecoratorContext,
|
context: DecoratorContext,
|
||||||
entity: Type,
|
entity: Type,
|
||||||
|
@ -68,6 +68,12 @@ export const $extension: ExtensionDecorator = (
|
||||||
setExtension(context.program, entity, extensionName as ExtensionKey, data);
|
setExtension(context.program, entity, extensionName as ExtensionKey, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the OpenAPI info node on for the given service namespace.
|
||||||
|
* @param program Program
|
||||||
|
* @param entity Service namespace
|
||||||
|
* @param data OpenAPI Info object
|
||||||
|
*/
|
||||||
export function setInfo(
|
export function setInfo(
|
||||||
program: Program,
|
program: Program,
|
||||||
entity: Namespace,
|
entity: Namespace,
|
||||||
|
@ -76,6 +82,13 @@ export function setInfo(
|
||||||
program.stateMap(infoKey).set(entity, data);
|
program.stateMap(infoKey).set(entity, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set OpenAPI extension on the given type. Equivalent of using `@extension` decorator
|
||||||
|
* @param program Program
|
||||||
|
* @param entity Type to annotate
|
||||||
|
* @param extensionName Extension key
|
||||||
|
* @param data Extension value
|
||||||
|
*/
|
||||||
export function setExtension(
|
export function setExtension(
|
||||||
program: Program,
|
program: Program,
|
||||||
entity: Type,
|
entity: Type,
|
||||||
|
@ -88,6 +101,11 @@ export function setExtension(
|
||||||
openApiExtensions.set(entity, typeExtensions);
|
openApiExtensions.set(entity, typeExtensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get extensions set for the given type.
|
||||||
|
* @param program Program
|
||||||
|
* @param entity Type
|
||||||
|
*/
|
||||||
export function getExtensions(program: Program, entity: Type): ReadonlyMap<ExtensionKey, any> {
|
export function getExtensions(program: Program, entity: Type): ReadonlyMap<ExtensionKey, any> {
|
||||||
return program.stateMap(openApiExtensionKey).get(entity) ?? new Map<ExtensionKey, any>();
|
return program.stateMap(openApiExtensionKey).get(entity) ?? new Map<ExtensionKey, any>();
|
||||||
}
|
}
|
||||||
|
@ -102,6 +120,7 @@ function isOpenAPIExtensionKey(key: string): key is ExtensionKey {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const defaultResponseKey = createStateSymbol("defaultResponse");
|
const defaultResponseKey = createStateSymbol("defaultResponse");
|
||||||
|
/** {@inheritdoc DefaultResponseDecorator} */
|
||||||
export const $defaultResponse: DefaultResponseDecorator = (
|
export const $defaultResponse: DefaultResponseDecorator = (
|
||||||
context: DecoratorContext,
|
context: DecoratorContext,
|
||||||
entity: Model
|
entity: Model
|
||||||
|
@ -121,10 +140,6 @@ export function isDefaultResponse(program: Program, entity: Type): boolean {
|
||||||
return program.stateSet(defaultResponseKey).has(entity);
|
return program.stateSet(defaultResponseKey).has(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExternalDocs {
|
|
||||||
url: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
const externalDocsKey = createStateSymbol("externalDocs");
|
const externalDocsKey = createStateSymbol("externalDocs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,11 +160,18 @@ export const $externalDocs: ExternalDocsDecorator = (
|
||||||
context.program.stateMap(externalDocsKey).set(target, doc);
|
context.program.stateMap(externalDocsKey).set(target, doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return external doc info set via the `@externalDocs` decorator.
|
||||||
|
* @param program Program
|
||||||
|
* @param entity Type
|
||||||
|
*/
|
||||||
export function getExternalDocs(program: Program, entity: Type): ExternalDocs | undefined {
|
export function getExternalDocs(program: Program, entity: Type): ExternalDocs | undefined {
|
||||||
return program.stateMap(externalDocsKey).get(entity);
|
return program.stateMap(externalDocsKey).get(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoKey = createStateSymbol("info");
|
const infoKey = createStateSymbol("info");
|
||||||
|
|
||||||
|
/** {@inheritdoc InfoDecorator} */
|
||||||
export const $info: InfoDecorator = (
|
export const $info: InfoDecorator = (
|
||||||
context: DecoratorContext,
|
context: DecoratorContext,
|
||||||
entity: Namespace,
|
entity: Namespace,
|
||||||
|
@ -166,6 +188,11 @@ export const $info: InfoDecorator = (
|
||||||
setInfo(context.program, entity, data);
|
setInfo(context.program, entity, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the info entry for the given service namespace.
|
||||||
|
* @param program Program
|
||||||
|
* @param entity Service namespace
|
||||||
|
*/
|
||||||
export function getInfo(program: Program, entity: Namespace): AdditionalInfo | undefined {
|
export function getInfo(program: Program, entity: Namespace): AdditionalInfo | undefined {
|
||||||
return program.stateMap(infoKey).get(entity);
|
return program.stateMap(infoKey).get(entity);
|
||||||
}
|
}
|
||||||
|
@ -187,14 +214,3 @@ export function resolveInfo(program: Program, entity: Namespace): AdditionalInfo
|
||||||
function omitUndefined<T extends Record<string, unknown>>(data: T): T {
|
function omitUndefined<T extends Record<string, unknown>>(data: T): T {
|
||||||
return Object.fromEntries(Object.entries(data).filter(([k, v]) => v !== undefined)) as any;
|
return Object.fromEntries(Object.entries(data).filter(([k, v]) => v !== undefined)) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const $decorators = {
|
|
||||||
"TypeSpec.OpenAPI": {
|
|
||||||
defaultResponse: $defaultResponse,
|
|
||||||
extension: $extension,
|
|
||||||
externalDocs: $externalDocs,
|
|
||||||
info: $info,
|
|
||||||
operationId: $operationId,
|
|
||||||
} satisfies TypeSpecOpenAPIDecorators,
|
|
||||||
};
|
|
||||||
|
|
|
@ -64,6 +64,13 @@ export function getOpenAPITypeName(
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the given name is not already specific in the existing map. Report a diagnostic if it is.
|
||||||
|
* @param program Program
|
||||||
|
* @param type Type with the name to check
|
||||||
|
* @param name Name to check
|
||||||
|
* @param existing Existing map of name
|
||||||
|
*/
|
||||||
export function checkDuplicateTypeName(
|
export function checkDuplicateTypeName(
|
||||||
program: Program,
|
program: Program,
|
||||||
type: Type,
|
type: Type,
|
||||||
|
@ -114,7 +121,7 @@ export function getParameterKey(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the OpenAPI operation ID for the given operation using the following logic:
|
* Resolve the OpenAPI operation ID for the given operation using the following logic:
|
||||||
* - If @operationId was specified use that value
|
* - If `@operationId` was specified use that value
|
||||||
* - If operation is defined at the root or under the service namespace return `<operation.name>`
|
* - If operation is defined at the root or under the service namespace return `<operation.name>`
|
||||||
* - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
|
* - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,3 +1,33 @@
|
||||||
export * from "./decorators.js";
|
export type {
|
||||||
export * from "./helpers.js";
|
DefaultResponseDecorator,
|
||||||
export * from "./types.js";
|
ExtensionDecorator,
|
||||||
|
ExternalDocsDecorator,
|
||||||
|
InfoDecorator,
|
||||||
|
} from "../generated-defs/TypeSpec.OpenAPI.js";
|
||||||
|
export {
|
||||||
|
$defaultResponse,
|
||||||
|
$extension,
|
||||||
|
$externalDocs,
|
||||||
|
$info,
|
||||||
|
$operationId,
|
||||||
|
getExtensions,
|
||||||
|
getExternalDocs,
|
||||||
|
getInfo,
|
||||||
|
getOperationId,
|
||||||
|
isDefaultResponse,
|
||||||
|
resolveInfo,
|
||||||
|
setExtension,
|
||||||
|
setInfo,
|
||||||
|
} from "./decorators.js";
|
||||||
|
export {
|
||||||
|
checkDuplicateTypeName,
|
||||||
|
getOpenAPITypeName,
|
||||||
|
getParameterKey,
|
||||||
|
isReadonlyProperty,
|
||||||
|
resolveOperationId,
|
||||||
|
shouldInline,
|
||||||
|
} from "./helpers.js";
|
||||||
|
export { AdditionalInfo, Contact, ExtensionKey, ExternalDocs, License } from "./types.js";
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export { $decorators } from "./tsp-index.js";
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Pattern for extension keys.
|
||||||
|
* In OpenAPI only unknown properties starting with `x-` are allowed.
|
||||||
|
*/
|
||||||
export type ExtensionKey = `x-${string}`;
|
export type ExtensionKey = `x-${string}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +30,9 @@ export interface AdditionalInfo {
|
||||||
license?: License;
|
license?: License;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information
|
||||||
|
*/
|
||||||
export interface Contact {
|
export interface Contact {
|
||||||
/** The identifying name of the contact person/organization. */
|
/** The identifying name of the contact person/organization. */
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -37,6 +44,9 @@ export interface Contact {
|
||||||
email?: string;
|
email?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* License information
|
||||||
|
*/
|
||||||
export interface License {
|
export interface License {
|
||||||
/** The license name used for the API. */
|
/** The license name used for the API. */
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -44,3 +54,13 @@ export interface License {
|
||||||
/** A URL to the license used for the API. MUST be in the format of a URL. */
|
/** A URL to the license used for the API. MUST be in the format of a URL. */
|
||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External Docs info
|
||||||
|
*/
|
||||||
|
export interface ExternalDocs {
|
||||||
|
/** Documentation url */
|
||||||
|
url: string;
|
||||||
|
/** Optional description */
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче