Add `getOpenAPI3` to OpenAPI3 emitter (#2950)
This function is useful when you want to work with the OpenAPI 3 directly, or don't want to write it to disk for some reason. --------- Co-authored-by: Timothee Guerin <timothee.guerin@outlook.com>
This commit is contained in:
Родитель
e9b17b384d
Коммит
aa6e53f4ab
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
changeKind: feature
|
||||
packages:
|
||||
- "@typespec/openapi3"
|
||||
---
|
||||
|
||||
Add `getOpenAPI3` function that takes a TypeSpec program and returns the emitted OpenAPI as an object. Useful for other emitters and tools that want to work with emitted OpenAPI directly without writing it to disk.
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
changeKind: feature
|
||||
packages:
|
||||
- "@typespec/versioning"
|
||||
---
|
||||
|
||||
Export the VersionProjections interface.
|
|
@ -243,6 +243,6 @@ export const libDef = {
|
|||
} as const;
|
||||
|
||||
export const $lib = createTypeSpecLibrary(libDef);
|
||||
export const { reportDiagnostic, createStateSymbol } = $lib;
|
||||
export const { createDiagnostic, reportDiagnostic, createStateSymbol } = $lib;
|
||||
|
||||
export type OpenAPILibrary = typeof $lib;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import {
|
||||
compilerAssert,
|
||||
createDiagnosticCollector,
|
||||
Diagnostic,
|
||||
DiagnosticCollector,
|
||||
DiagnosticTarget,
|
||||
EmitContext,
|
||||
emitFile,
|
||||
|
@ -44,7 +47,8 @@ import {
|
|||
TypeNameOptions,
|
||||
} from "@typespec/compiler";
|
||||
|
||||
import { AssetEmitter, EmitEntity } from "@typespec/compiler/emitter-framework";
|
||||
import { AssetEmitter, createAssetEmitter, EmitEntity } from "@typespec/compiler/emitter-framework";
|
||||
import {} from "@typespec/compiler/utils";
|
||||
import {
|
||||
createMetadataInfo,
|
||||
getAuthentication,
|
||||
|
@ -83,10 +87,10 @@ import {
|
|||
resolveOperationId,
|
||||
shouldInline,
|
||||
} from "@typespec/openapi";
|
||||
import { buildVersionProjections } from "@typespec/versioning";
|
||||
import { buildVersionProjections, VersionProjections } from "@typespec/versioning";
|
||||
import { stringify } from "yaml";
|
||||
import { getRef } from "./decorators.js";
|
||||
import { FileType, OpenAPI3EmitterOptions, reportDiagnostic } from "./lib.js";
|
||||
import { createDiagnostic, FileType, OpenAPI3EmitterOptions } from "./lib.js";
|
||||
import { getDefaultValue, OpenAPI3SchemaEmitter } from "./schema-emitter.js";
|
||||
import {
|
||||
OpenAPI3Document,
|
||||
|
@ -99,7 +103,9 @@ import {
|
|||
OpenAPI3SecurityScheme,
|
||||
OpenAPI3Server,
|
||||
OpenAPI3ServerVariable,
|
||||
OpenAPI3ServiceRecord,
|
||||
OpenAPI3StatusCode,
|
||||
OpenAPI3VersionedServiceRecord,
|
||||
Refable,
|
||||
} from "./types.js";
|
||||
import { deepEquals } from "./util.js";
|
||||
|
@ -118,6 +124,37 @@ export async function $onEmit(context: EmitContext<OpenAPI3EmitterOptions>) {
|
|||
await emitter.emitOpenAPI();
|
||||
}
|
||||
|
||||
type IrrelevantOpenAPI3EmitterOptionsForObject = "file-type" | "output-file" | "new-line";
|
||||
|
||||
/**
|
||||
* Get the OpenAPI 3 document records from the given program. The documents are
|
||||
* returned as a JS object.
|
||||
*
|
||||
* @param program The program to emit to OpenAPI 3
|
||||
* @param options OpenAPI 3 emit options
|
||||
* @returns An array of OpenAPI 3 document records.
|
||||
*/
|
||||
export async function getOpenAPI3(
|
||||
program: Program,
|
||||
options: Omit<OpenAPI3EmitterOptions, IrrelevantOpenAPI3EmitterOptionsForObject> = {}
|
||||
): Promise<OpenAPI3ServiceRecord[]> {
|
||||
const context: EmitContext<any> = {
|
||||
program,
|
||||
|
||||
// this value doesn't matter for getting the OpenAPI3 objects
|
||||
emitterOutputDir: "tsp-output",
|
||||
|
||||
options: options,
|
||||
getAssetEmitter(TypeEmitterClass) {
|
||||
return createAssetEmitter(program, TypeEmitterClass, this);
|
||||
},
|
||||
};
|
||||
|
||||
const resolvedOptions = resolveOptions(context);
|
||||
const emitter = createOAPIEmitter(context, resolvedOptions);
|
||||
return emitter.getOpenAPI();
|
||||
}
|
||||
|
||||
function findFileTypeFromFilename(filename: string | undefined): FileType {
|
||||
if (filename === undefined) {
|
||||
return defaultFileType;
|
||||
|
@ -167,6 +204,7 @@ function createOAPIEmitter(
|
|||
let schemaEmitter: AssetEmitter<OpenAPI3Schema, OpenAPI3EmitterOptions>;
|
||||
|
||||
let root: OpenAPI3Document;
|
||||
let diagnostics: DiagnosticCollector;
|
||||
let currentService: Service;
|
||||
// Get the service namespace string for use in name shortening
|
||||
let serviceNamespaceName: string | undefined;
|
||||
|
@ -196,7 +234,45 @@ function createOAPIEmitter(
|
|||
},
|
||||
};
|
||||
|
||||
return { emitOpenAPI };
|
||||
return { emitOpenAPI, getOpenAPI };
|
||||
|
||||
async function emitOpenAPI() {
|
||||
const services = await getOpenAPI();
|
||||
// first, emit diagnostics
|
||||
for (const serviceRecord of services) {
|
||||
if (serviceRecord.versioned) {
|
||||
for (const documentRecord of serviceRecord.versions) {
|
||||
program.reportDiagnostics(documentRecord.diagnostics);
|
||||
}
|
||||
} else {
|
||||
program.reportDiagnostics(serviceRecord.diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if (program.compilerOptions.noEmit || program.hasError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const multipleService = services.length > 1;
|
||||
|
||||
for (const serviceRecord of services) {
|
||||
if (serviceRecord.versioned) {
|
||||
for (const documentRecord of serviceRecord.versions) {
|
||||
await emitFile(program, {
|
||||
path: resolveOutputFile(serviceRecord.service, multipleService, documentRecord.version),
|
||||
content: serializeDocument(documentRecord.document, options.fileType),
|
||||
newLine: options.newLine,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await emitFile(program, {
|
||||
path: resolveOutputFile(serviceRecord.service, multipleService),
|
||||
content: serializeDocument(serviceRecord.document, options.fileType),
|
||||
newLine: options.newLine,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeEmitter(service: Service, version?: string) {
|
||||
currentService = service;
|
||||
|
@ -241,6 +317,7 @@ function createOAPIEmitter(
|
|||
securitySchemes: auth?.securitySchemes ?? {},
|
||||
},
|
||||
};
|
||||
diagnostics = createDiagnosticCollector();
|
||||
const servers = getServers(program, service.type);
|
||||
if (servers) {
|
||||
root.servers = resolveServers(servers);
|
||||
|
@ -282,11 +359,13 @@ function createOAPIEmitter(
|
|||
const isValid = isValidServerVariableType(program, prop.type);
|
||||
|
||||
if (!isValid) {
|
||||
reportDiagnostic(program, {
|
||||
code: "invalid-server-variable",
|
||||
format: { propName: prop.name },
|
||||
target: prop,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "invalid-server-variable",
|
||||
format: { propName: prop.name },
|
||||
target: prop,
|
||||
})
|
||||
);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
@ -324,12 +403,57 @@ function createOAPIEmitter(
|
|||
});
|
||||
}
|
||||
|
||||
async function emitOpenAPI() {
|
||||
async function getOpenAPI(): Promise<OpenAPI3ServiceRecord[]> {
|
||||
const serviceRecords: OpenAPI3ServiceRecord[] = [];
|
||||
const services = listServices(program);
|
||||
if (services.length === 0) {
|
||||
services.push({ type: program.getGlobalNamespaceType() });
|
||||
}
|
||||
for (const service of services) {
|
||||
const versions = buildVersionProjections(program, service.type);
|
||||
if (versions.length === 1 && versions[0].version === undefined) {
|
||||
// non-versioned spec
|
||||
const document = await getProjectedOpenAPIDocument(service, versions[0]);
|
||||
if (document === undefined) {
|
||||
// an error occurred producing this document, so don't return it
|
||||
return serviceRecords;
|
||||
}
|
||||
|
||||
serviceRecords.push({
|
||||
service,
|
||||
versioned: false,
|
||||
document: document[0],
|
||||
diagnostics: document[1],
|
||||
});
|
||||
} else {
|
||||
// versioned spec
|
||||
const serviceRecord: OpenAPI3VersionedServiceRecord = {
|
||||
service,
|
||||
versioned: true,
|
||||
versions: [],
|
||||
};
|
||||
serviceRecords.push(serviceRecord);
|
||||
|
||||
for (const record of versions) {
|
||||
const document = await getProjectedOpenAPIDocument(service, record);
|
||||
if (document === undefined) {
|
||||
// an error occurred producing this document
|
||||
continue;
|
||||
}
|
||||
|
||||
serviceRecord.versions.push({
|
||||
service,
|
||||
version: record.version!,
|
||||
document: document[0],
|
||||
diagnostics: document[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serviceRecords;
|
||||
|
||||
async function getProjectedOpenAPIDocument(service: Service, record: VersionProjections) {
|
||||
const commonProjections: ProjectionApplication[] = [
|
||||
{
|
||||
projectionName: "target",
|
||||
|
@ -337,24 +461,22 @@ function createOAPIEmitter(
|
|||
},
|
||||
];
|
||||
const originalProgram = program;
|
||||
const versions = buildVersionProjections(program, service.type);
|
||||
for (const record of versions) {
|
||||
const projectedProgram = (program = projectProgram(originalProgram, [
|
||||
...commonProjections,
|
||||
...record.projections,
|
||||
]));
|
||||
const projectedServiceNs: Namespace = projectedProgram.projector.projectedTypes.get(
|
||||
service.type
|
||||
) as Namespace;
|
||||
const projectedProgram = (program = projectProgram(originalProgram, [
|
||||
...commonProjections,
|
||||
...record.projections,
|
||||
]));
|
||||
const projectedServiceNs: Namespace = projectedProgram.projector.projectedTypes.get(
|
||||
service.type
|
||||
) as Namespace;
|
||||
|
||||
await emitOpenAPIFromVersion(
|
||||
projectedServiceNs === projectedProgram.getGlobalNamespaceType()
|
||||
? { type: projectedProgram.getGlobalNamespaceType() }
|
||||
: getService(program, projectedServiceNs)!,
|
||||
services.length > 1,
|
||||
record.version
|
||||
);
|
||||
}
|
||||
const document = await getOpenApiFromVersion(
|
||||
projectedServiceNs === projectedProgram.getGlobalNamespaceType()
|
||||
? { type: projectedProgram.getGlobalNamespaceType() }
|
||||
: getService(program, projectedServiceNs)!,
|
||||
record.version
|
||||
);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,11 +600,13 @@ function createOAPIEmitter(
|
|||
diagnosticTarget: DiagnosticTarget
|
||||
): OpenAPI3StatusCode[] {
|
||||
const reportInvalid = () =>
|
||||
reportDiagnostic(program, {
|
||||
code: "unsupported-status-code-range",
|
||||
format: { start: String(range.start), end: String(range.end) },
|
||||
target: diagnosticTarget,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "unsupported-status-code-range",
|
||||
format: { start: String(range.start), end: String(range.end) },
|
||||
target: diagnosticTarget,
|
||||
})
|
||||
);
|
||||
|
||||
const codes: OpenAPI3StatusCode[] = [];
|
||||
let start = range.start;
|
||||
|
@ -601,11 +725,10 @@ function createOAPIEmitter(
|
|||
return result;
|
||||
}
|
||||
|
||||
async function emitOpenAPIFromVersion(
|
||||
async function getOpenApiFromVersion(
|
||||
service: Service,
|
||||
multipleService: boolean,
|
||||
version?: string
|
||||
) {
|
||||
): Promise<[OpenAPI3Document, Readonly<Diagnostic[]>] | undefined> {
|
||||
initializeEmitter(service, version);
|
||||
try {
|
||||
const httpService = ignoreDiagnostics(getHttpService(program, service.type));
|
||||
|
@ -631,15 +754,7 @@ function createOAPIEmitter(
|
|||
}
|
||||
}
|
||||
|
||||
if (!program.compilerOptions.noEmit && !program.hasError()) {
|
||||
// Write out the OpenAPI document to the output path
|
||||
|
||||
await emitFile(program, {
|
||||
path: resolveOutputFile(service, multipleService, version),
|
||||
content: serializeDocument(root, options.fileType),
|
||||
newLine: options.newLine,
|
||||
});
|
||||
}
|
||||
return [root, diagnostics.diagnostics];
|
||||
} catch (err) {
|
||||
if (err instanceof ErrorTypeFoundError) {
|
||||
// Return early, there must be a parse error if an ErrorType was
|
||||
|
@ -707,10 +822,12 @@ function createOAPIEmitter(
|
|||
return resolveRequestVisibility(program, op, verb);
|
||||
});
|
||||
if (visibilities.some((v) => v !== visibilities[0])) {
|
||||
reportDiagnostic(program, {
|
||||
code: "inconsistent-shared-route-request-visibility",
|
||||
target: ops[0],
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "inconsistent-shared-route-request-visibility",
|
||||
target: ops[0],
|
||||
})
|
||||
);
|
||||
}
|
||||
const visibility = resolveRequestVisibility(program, shared.operations[0], verb);
|
||||
emitEndpointParameters(shared.parameters.parameters, visibility);
|
||||
|
@ -734,7 +851,7 @@ function createOAPIEmitter(
|
|||
const { path: fullPath, operation: op, verb, parameters } = operation;
|
||||
// If path contains a query string, issue msg and don't emit this endpoint
|
||||
if (fullPath.indexOf("?") > 0) {
|
||||
reportDiagnostic(program, { code: "path-query", target: op });
|
||||
diagnostics.add(createDiagnostic({ code: "path-query", target: op }));
|
||||
return;
|
||||
}
|
||||
if (!root.paths[fullPath]) {
|
||||
|
@ -844,11 +961,13 @@ function createOAPIEmitter(
|
|||
const existing = obj.headers[key];
|
||||
if (existing) {
|
||||
if (!deepEquals(existing, headerVal)) {
|
||||
reportDiagnostic(program, {
|
||||
code: "duplicate-header",
|
||||
format: { header: key },
|
||||
target: target,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "duplicate-header",
|
||||
format: { header: key },
|
||||
target: target,
|
||||
})
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -916,11 +1035,13 @@ function createOAPIEmitter(
|
|||
case "declaration":
|
||||
return { $ref: `#/components/schemas/${result.name}` };
|
||||
case "circular":
|
||||
reportDiagnostic(program, {
|
||||
code: "inline-cycle",
|
||||
format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
|
||||
target: type,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "inline-cycle",
|
||||
format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
|
||||
target: type,
|
||||
})
|
||||
);
|
||||
return {};
|
||||
case "none":
|
||||
return {};
|
||||
|
@ -935,11 +1056,13 @@ function createOAPIEmitter(
|
|||
case "declaration":
|
||||
return result.value as any;
|
||||
case "circular":
|
||||
reportDiagnostic(program, {
|
||||
code: "inline-cycle",
|
||||
format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
|
||||
target: type,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "inline-cycle",
|
||||
format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
|
||||
target: type,
|
||||
})
|
||||
);
|
||||
return {};
|
||||
case "none":
|
||||
return {};
|
||||
|
@ -1207,14 +1330,16 @@ function createOAPIEmitter(
|
|||
case "simple":
|
||||
return { style: "simple" };
|
||||
default:
|
||||
reportDiagnostic(program, {
|
||||
code: "invalid-format",
|
||||
format: {
|
||||
paramType: "header",
|
||||
value: parameter.format,
|
||||
},
|
||||
target: parameter.param,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "invalid-format",
|
||||
format: {
|
||||
paramType: "header",
|
||||
value: parameter.format,
|
||||
},
|
||||
target: parameter.param,
|
||||
})
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -1238,14 +1363,16 @@ function createOAPIEmitter(
|
|||
return { style: "pipeDelimited", explode: false };
|
||||
|
||||
default:
|
||||
reportDiagnostic(program, {
|
||||
code: "invalid-format",
|
||||
format: {
|
||||
paramType: "query",
|
||||
value: parameter.format,
|
||||
},
|
||||
target: parameter.param,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "invalid-format",
|
||||
format: {
|
||||
paramType: "query",
|
||||
value: parameter.format,
|
||||
},
|
||||
target: parameter.param,
|
||||
})
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -1542,11 +1669,13 @@ function createOAPIEmitter(
|
|||
scopes: [],
|
||||
};
|
||||
default:
|
||||
reportDiagnostic(program, {
|
||||
code: "unsupported-auth",
|
||||
format: { authType: (auth as any).type },
|
||||
target: currentService.type,
|
||||
});
|
||||
diagnostics.add(
|
||||
createDiagnostic({
|
||||
code: "unsupported-auth",
|
||||
format: { authType: (auth as any).type },
|
||||
target: currentService.type,
|
||||
})
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Diagnostic, Service } from "@typespec/compiler";
|
||||
import { ExtensionKey } from "@typespec/openapi";
|
||||
|
||||
export type Extensions = {
|
||||
|
@ -39,6 +40,57 @@ export interface OpenAPI3Document extends Extensions {
|
|||
security?: Record<string, string[]>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A record containing the the OpenAPI 3 documents corresponding to
|
||||
* a particular service definition.
|
||||
*/
|
||||
export type OpenAPI3ServiceRecord =
|
||||
| OpenAPI3UnversionedServiceRecord
|
||||
| OpenAPI3VersionedServiceRecord;
|
||||
|
||||
export interface OpenAPI3UnversionedServiceRecord {
|
||||
/** The service that generated this OpenAPI document */
|
||||
readonly service: Service;
|
||||
|
||||
/** Whether the service is versioned */
|
||||
readonly versioned: false;
|
||||
|
||||
/** The OpenAPI 3 document */
|
||||
readonly document: OpenAPI3Document;
|
||||
|
||||
/** The diagnostics created for this document */
|
||||
readonly diagnostics: readonly Diagnostic[];
|
||||
}
|
||||
|
||||
export interface OpenAPI3VersionedServiceRecord {
|
||||
/** The service that generated this OpenAPI document */
|
||||
readonly service: Service;
|
||||
|
||||
/** Whether the service is versioned */
|
||||
readonly versioned: true;
|
||||
|
||||
/** The OpenAPI 3 document records for each version of this service */
|
||||
readonly versions: OpenAPI3VersionedDocumentRecord[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A record containing an unversioned OpenAPI document and associated metadata.
|
||||
*/
|
||||
|
||||
export interface OpenAPI3VersionedDocumentRecord {
|
||||
/** The OpenAPI document*/
|
||||
readonly document: OpenAPI3Document;
|
||||
|
||||
/** The service that generated this OpenAPI document. */
|
||||
readonly service: Service;
|
||||
|
||||
/** The version of the service. Absent if the service is unversioned. */
|
||||
readonly version: string;
|
||||
|
||||
/** The diagnostics created for this version. */
|
||||
readonly diagnostics: readonly Diagnostic[];
|
||||
}
|
||||
|
||||
export interface OpenAPI3Info extends Extensions {
|
||||
title: string;
|
||||
description?: string;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { expectDiagnostics } from "@typespec/compiler/testing";
|
||||
import { ok, strictEqual } from "assert";
|
||||
import { it } from "vitest";
|
||||
import { getOpenAPI3 } from "../src/openapi.js";
|
||||
import { createOpenAPITestHost } from "./test-host.js";
|
||||
|
||||
it("can get openapi as an object", async () => {
|
||||
const host = await createOpenAPITestHost();
|
||||
host.addTypeSpecFile(
|
||||
"./main.tsp",
|
||||
`import "@typespec/http";
|
||||
import "@typespec/rest";
|
||||
import "@typespec/openapi";
|
||||
import "@typespec/openapi3";
|
||||
using TypeSpec.Rest;
|
||||
using TypeSpec.Http;
|
||||
using TypeSpec.OpenAPI;
|
||||
|
||||
@service
|
||||
namespace Foo;
|
||||
|
||||
@get op get(): Item;
|
||||
|
||||
model Item { x: true }
|
||||
model Bar { }; // unreachable
|
||||
`
|
||||
);
|
||||
await host.compile("main.tsp");
|
||||
const output = await getOpenAPI3(host.program, { "omit-unreachable-types": false });
|
||||
const documentRecord = output[0];
|
||||
ok(!documentRecord.versioned, "should not be versioned");
|
||||
strictEqual(documentRecord.document.components!.schemas!["Item"].type, "object");
|
||||
});
|
||||
|
||||
it("has diagnostics", async () => {
|
||||
const host = await createOpenAPITestHost();
|
||||
host.addTypeSpecFile(
|
||||
"./main.tsp",
|
||||
`import "@typespec/http";
|
||||
import "@typespec/rest";
|
||||
import "@typespec/openapi";
|
||||
import "@typespec/openapi3";
|
||||
using TypeSpec.Rest;
|
||||
using TypeSpec.Http;
|
||||
using TypeSpec.OpenAPI;
|
||||
|
||||
@service
|
||||
namespace Foo;
|
||||
|
||||
op read(): {@minValue(455) @maxValue(495) @statusCode _: int32, content: string};
|
||||
`
|
||||
);
|
||||
await host.compile("main.tsp");
|
||||
const output = await getOpenAPI3(host.program, { "omit-unreachable-types": false });
|
||||
const documentRecord = output[0];
|
||||
ok(!documentRecord.versioned, "should not be versioned");
|
||||
expectDiagnostics(documentRecord.diagnostics, [
|
||||
{
|
||||
code: "@typespec/openapi3/unsupported-status-code-range",
|
||||
message:
|
||||
"Status code range '455 to '495' is not supported. OpenAPI 3.0 can only represent range 1XX, 2XX, 3XX, 4XX and 5XX. Example: `@minValue(400) @maxValue(499)` for 4XX.",
|
||||
},
|
||||
]);
|
||||
});
|
|
@ -589,7 +589,7 @@ export function resolveVersions(program: Program, rootNs: Namespace): VersionRes
|
|||
/**
|
||||
* Represent the set of projections used to project to that version.
|
||||
*/
|
||||
interface VersionProjections {
|
||||
export interface VersionProjections {
|
||||
version: string | undefined;
|
||||
projections: ProjectionApplication[];
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче