Skip to content
Merged
1 change: 1 addition & 0 deletions eng/common/pipelines/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pr:
include:
- main
- release/*
- feature/*

extends:
template: /eng/common/pipelines/templates/1es-redirect.yml
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Example Multiple service with versioning
- `openapi.Org1.Service2.v1.0.yaml`
- `openapi.Org1.Service2.v1.1.yaml`

### `openapi-versions`

**Type:** `array`

### `new-line`

**Type:** `"crlf" | "lf"`
Expand Down
12 changes: 12 additions & 0 deletions packages/openapi3/src/attach-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Program, Type } from "@typespec/compiler";
import { getExtensions } from "@typespec/openapi";

export function attachExtensions(program: Program, type: Type, emitObject: any) {
// Attach any OpenAPI extensions
const extensions = getExtensions(program, type);
if (extensions) {
for (const key of extensions.keys()) {
emitObject[key] = extensions.get(key);
}
}
}
4 changes: 2 additions & 2 deletions packages/openapi3/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { type ModelProperty, Program, type Scalar, getEncode } from "@typespec/c
import { ObjectBuilder } from "@typespec/compiler/emitter-framework";
import type { ResolvedOpenAPI3EmitterOptions } from "./openapi.js";
import { getSchemaForStdScalars } from "./std-scalar-schemas.js";
import type { OpenAPI3Schema } from "./types.js";
import type { OpenAPI3Schema, OpenAPISchema3_1 } from "./types.js";

export function applyEncoding(
program: Program,
typespecType: Scalar | ModelProperty,
target: OpenAPI3Schema,
options: ResolvedOpenAPI3EmitterOptions,
): OpenAPI3Schema {
): OpenAPI3Schema & OpenAPISchema3_1 {
const encodeData = getEncode(program, typespecType);
if (encodeData) {
const newTarget = new ObjectBuilder(target);
Expand Down
79 changes: 79 additions & 0 deletions packages/openapi3/src/openapi-spec-mappings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { EmitContext, Namespace, Program } from "@typespec/compiler";
import { AssetEmitter } from "@typespec/compiler/emitter-framework";
import { MetadataInfo } from "@typespec/http";
import { getExternalDocs, resolveInfo } from "@typespec/openapi";
import { OpenAPI3EmitterOptions, OpenAPIVersion } from "./lib.js";
import { ResolvedOpenAPI3EmitterOptions } from "./openapi.js";
import { createSchemaEmitter3_0 } from "./schema-emitter-3-0.js";
import { createSchemaEmitter3_1 } from "./schema-emitter-3-1.js";
import { OpenAPI3Schema, OpenAPISchema3_1, SupportedOpenAPIDocuments } from "./types.js";
import { VisibilityUsageTracker } from "./visibility-usage.js";
import { XmlModule } from "./xml-module.js";

export type CreateSchemaEmitter = (props: {
program: Program;
context: EmitContext<OpenAPI3EmitterOptions>;
metadataInfo: MetadataInfo;
visibilityUsage: VisibilityUsageTracker;
options: ResolvedOpenAPI3EmitterOptions;
xmlModule: XmlModule | undefined;
}) => AssetEmitter<OpenAPI3Schema | OpenAPISchema3_1, OpenAPI3EmitterOptions>;

export interface OpenApiSpecSpecificProps {
createRootDoc: (
program: Program,
serviceType: Namespace,
serviceVersion?: string,
) => SupportedOpenAPIDocuments;

createSchemaEmitter: CreateSchemaEmitter;
}

export function getOpenApiSpecProps(specVersion: OpenAPIVersion): OpenApiSpecSpecificProps {
switch (specVersion) {
case "3.0.0":
return {
createRootDoc(program, serviceType, serviceVersion) {
return createRoot(program, serviceType, specVersion, serviceVersion);
},
createSchemaEmitter: createSchemaEmitter3_0,
};
case "3.1.0":
return {
createRootDoc(program, serviceType, serviceVersion) {
return createRoot(program, serviceType, specVersion, serviceVersion);
},
createSchemaEmitter: createSchemaEmitter3_1,
};
}
}

function createRoot(
program: Program,
serviceType: Namespace,
specVersion: OpenAPIVersion,
serviceVersion?: string,
): SupportedOpenAPIDocuments {
const info = resolveInfo(program, serviceType);

return {
openapi: specVersion,
info: {
title: "(title)",
...info,
version: serviceVersion ?? info?.version ?? "0.0.0",
},
externalDocs: getExternalDocs(program, serviceType),
tags: [],
paths: {},
security: undefined,
components: {
parameters: {},
requestBodies: {},
responses: {},
schemas: {},
examples: {},
securitySchemes: {},
},
};
}
64 changes: 27 additions & 37 deletions packages/openapi3/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ import {
getParameterKey,
getTagsMetadata,
isReadonlyProperty,
resolveInfo,
resolveOperationId,
shouldInline,
} from "@typespec/openapi";
Expand All @@ -90,10 +89,9 @@ import { getRef } from "./decorators.js";
import { applyEncoding } from "./encoding.js";
import { getExampleOrExamples, OperationExamples, resolveOperationExamples } from "./examples.js";
import { createDiagnostic, FileType, OpenAPI3EmitterOptions, OpenAPIVersion } from "./lib.js";
import { getDefaultValue, isBytesKeptRaw, OpenAPI3SchemaEmitter } from "./schema-emitter.js";
import { getOpenApiSpecProps } from "./openapi-spec-mappings.js";
import { getOpenAPI3StatusCodes } from "./status-codes.js";
import {
OpenAPI3Document,
OpenAPI3Encoding,
OpenAPI3Header,
OpenAPI3MediaType,
Expand All @@ -111,9 +109,17 @@ import {
OpenAPI3StatusCode,
OpenAPI3Tag,
OpenAPI3VersionedServiceRecord,
OpenAPISchema3_1,
Refable,
SupportedOpenAPIDocuments,
} from "./types.js";
import { deepEquals, isSharedHttpOperation, SharedHttpOperation } from "./util.js";
import {
deepEquals,
getDefaultValue,
isBytesKeptRaw,
isSharedHttpOperation,
SharedHttpOperation,
} from "./util.js";
import { resolveVisibilityUsage, VisibilityUsageTracker } from "./visibility-usage.js";
import { resolveXmlModule, XmlModule } from "./xml-module.js";

Expand Down Expand Up @@ -223,10 +229,11 @@ function createOAPIEmitter(
options: ResolvedOpenAPI3EmitterOptions,
specVersion: OpenAPIVersion = "3.0.0",
) {
const { createRootDoc, createSchemaEmitter } = getOpenApiSpecProps(specVersion);
let program = context.program;
let schemaEmitter: AssetEmitter<OpenAPI3Schema, OpenAPI3EmitterOptions>;
let schemaEmitter: AssetEmitter<OpenAPI3Schema | OpenAPISchema3_1, OpenAPI3EmitterOptions>;

let root: OpenAPI3Document;
let root: SupportedOpenAPIDocuments;
let diagnostics: DiagnosticCollector;
let currentService: Service;
let serviceAuth: HttpServiceAuthentication;
Expand Down Expand Up @@ -320,40 +327,23 @@ function createOAPIEmitter(
options.omitUnreachableTypes,
);

schemaEmitter = createAssetEmitter(
schemaEmitter = createSchemaEmitter({
program,
class extends OpenAPI3SchemaEmitter {
constructor(emitter: AssetEmitter<Record<string, any>, OpenAPI3EmitterOptions>) {
super(emitter, metadataInfo, visibilityUsage, options, xmlModule);
}
} as any,
context,
);
metadataInfo,
visibilityUsage,
options,
xmlModule,
});

const securitySchemes = getOpenAPISecuritySchemes(allHttpAuthentications);
const security = getOpenAPISecurity(defaultAuth);

const info = resolveInfo(program, service.type);
root = {
openapi: "3.0.0",
info: {
title: "(title)",
...info,
version: version ?? info?.version ?? "0.0.0",
},
externalDocs: getExternalDocs(program, service.type),
tags: [],
paths: {},
security: security.length > 0 ? security : undefined,
components: {
parameters: {},
requestBodies: {},
responses: {},
schemas: {},
examples: {},
securitySchemes: securitySchemes,
},
};
root = createRootDoc(program, service.type, version);
if (security.length > 0) {
root.security = security;
}
root.components!.securitySchemes = securitySchemes;

const servers = getServers(program, service.type);
if (servers) {
Expand Down Expand Up @@ -642,7 +632,7 @@ function createOAPIEmitter(
async function getOpenApiFromVersion(
service: Service,
version?: string,
): Promise<[OpenAPI3Document, Readonly<Diagnostic[]>] | undefined> {
): Promise<[SupportedOpenAPIDocuments, Readonly<Diagnostic[]>] | undefined> {
try {
const httpService = ignoreDiagnostics(getHttpService(program, service.type));
const auth = (serviceAuth = resolveAuthentication(httpService));
Expand Down Expand Up @@ -1827,7 +1817,7 @@ function createOAPIEmitter(
}
}

function serializeDocument(root: OpenAPI3Document, fileType: FileType): string {
function serializeDocument(root: SupportedOpenAPIDocuments, fileType: FileType): string {
sortOpenAPIDocument(root);
switch (fileType) {
case "json":
Expand Down Expand Up @@ -1860,7 +1850,7 @@ function sortObjectByKeys<T extends Record<string, unknown>>(obj: T): T {
}, {});
}

function sortOpenAPIDocument(doc: OpenAPI3Document): void {
function sortOpenAPIDocument(doc: SupportedOpenAPIDocuments): void {
doc.paths = sortObjectByKeys(doc.paths);
if (doc.components?.schemas) {
doc.components.schemas = sortObjectByKeys(doc.components.schemas);
Expand Down
Loading