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": {
|
||||
"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",
|
||||
"gen-extern-signature": "tspd --enable-experimental gen-extern-signature .",
|
||||
"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",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"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": [
|
||||
"lib/*.tsp",
|
||||
|
|
|
@ -18,10 +18,9 @@ import {
|
|||
ExternalDocsDecorator,
|
||||
InfoDecorator,
|
||||
OperationIdDecorator,
|
||||
TypeSpecOpenAPIDecorators,
|
||||
} from "../generated-defs/TypeSpec.OpenAPI.js";
|
||||
import { createStateSymbol, reportDiagnostic } from "./lib.js";
|
||||
import { AdditionalInfo, ExtensionKey } from "./types.js";
|
||||
import { AdditionalInfo, ExtensionKey, ExternalDocs } from "./types.js";
|
||||
|
||||
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 {
|
||||
return program.stateMap(operationIdsKey).get(entity);
|
||||
|
@ -47,6 +46,7 @@ export function getOperationId(program: Program, entity: Operation): string | un
|
|||
|
||||
const openApiExtensionKey = createStateSymbol("openApiExtension");
|
||||
|
||||
/** {@inheritdoc ExtensionDecorator} */
|
||||
export const $extension: ExtensionDecorator = (
|
||||
context: DecoratorContext,
|
||||
entity: Type,
|
||||
|
@ -68,6 +68,12 @@ export const $extension: ExtensionDecorator = (
|
|||
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(
|
||||
program: Program,
|
||||
entity: Namespace,
|
||||
|
@ -76,6 +82,13 @@ export function setInfo(
|
|||
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(
|
||||
program: Program,
|
||||
entity: Type,
|
||||
|
@ -88,6 +101,11 @@ export function setExtension(
|
|||
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> {
|
||||
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");
|
||||
/** {@inheritdoc DefaultResponseDecorator} */
|
||||
export const $defaultResponse: DefaultResponseDecorator = (
|
||||
context: DecoratorContext,
|
||||
entity: Model
|
||||
|
@ -121,10 +140,6 @@ export function isDefaultResponse(program: Program, entity: Type): boolean {
|
|||
return program.stateSet(defaultResponseKey).has(entity);
|
||||
}
|
||||
|
||||
export interface ExternalDocs {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
const externalDocsKey = createStateSymbol("externalDocs");
|
||||
|
||||
/**
|
||||
|
@ -145,11 +160,18 @@ export const $externalDocs: ExternalDocsDecorator = (
|
|||
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 {
|
||||
return program.stateMap(externalDocsKey).get(entity);
|
||||
}
|
||||
|
||||
const infoKey = createStateSymbol("info");
|
||||
|
||||
/** {@inheritdoc InfoDecorator} */
|
||||
export const $info: InfoDecorator = (
|
||||
context: DecoratorContext,
|
||||
entity: Namespace,
|
||||
|
@ -166,6 +188,11 @@ export const $info: InfoDecorator = (
|
|||
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 {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
program: Program,
|
||||
type: Type,
|
||||
|
@ -114,7 +121,7 @@ export function getParameterKey(
|
|||
|
||||
/**
|
||||
* 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>`
|
||||
* - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
|
||||
*
|
||||
|
|
|
@ -1,3 +1,33 @@
|
|||
export * from "./decorators.js";
|
||||
export * from "./helpers.js";
|
||||
export * from "./types.js";
|
||||
export type {
|
||||
DefaultResponseDecorator,
|
||||
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}`;
|
||||
|
||||
/**
|
||||
|
@ -26,6 +30,9 @@ export interface AdditionalInfo {
|
|||
license?: License;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contact information
|
||||
*/
|
||||
export interface Contact {
|
||||
/** The identifying name of the contact person/organization. */
|
||||
name?: string;
|
||||
|
@ -37,6 +44,9 @@ export interface Contact {
|
|||
email?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* License information
|
||||
*/
|
||||
export interface License {
|
||||
/** The license name used for the API. */
|
||||
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. */
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* External Docs info
|
||||
*/
|
||||
export interface ExternalDocs {
|
||||
/** Documentation url */
|
||||
url: string;
|
||||
/** Optional description */
|
||||
description?: string;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче