Add api extractor for openapi package and fix issues (#4354)

This commit is contained in:
Timothee Guerin 2024-09-06 16:18:53 -07:00 коммит произвёл GitHub
Родитель 86cc361bf0
Коммит dde8dc0ca7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 109 добавлений и 24 удалений

Просмотреть файл

@ -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;
}