Skip to content

Commit

Permalink
Merge branch 'main' into arm-lro-opt
Browse files Browse the repository at this point in the history
  • Loading branch information
markcowl authored Jun 7, 2024
2 parents 843e60b + d179b2a commit d36bb1d
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-autorest"
---

Add API to programmatically get all the OpenAPI2 documents for all services at all versions in a spec
2 changes: 1 addition & 1 deletion core
Submodule core updated 24 files
+8 −0 .chronus/changes/fix-playground-styles-overflow-2024-5-6-19-45-49.md
+8 −0 .chronus/changes/versioning-fixBug-2024-5-5-20-59-9.md
+1 −0 .vscode/launch.json
+9 −11 packages/http-client-csharp/emitter/src/lib/converter.ts
+1 −1 packages/http-client-csharp/emitter/src/lib/operation.ts
+0 −1 packages/http-client-csharp/emitter/src/type/input-type-kind.ts
+4 −4 packages/http-client-csharp/emitter/src/type/input-type.ts
+1 −1 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ScmTypeFactory.cs
+2 −2 ...ges/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/ExampleMockValueBuilder.cs
+3 −3 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputUnionType.cs
+3 −13 .../generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelTypeConverter.cs
+1 −1 ...sharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputTypeConverter.cs
+21 −41 .../generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputUnionTypeConverter.cs
+4 −0 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpGen.cs
+167 −0 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/OptionalProvider.cs
+103 −0 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/OptionalTests.cs
+55 −0 packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Internal/Optional.cs
+3 −3 packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/tspCodeModel.json
+1 −1 packages/playground/src/react/editor.tsx
+1 −0 packages/playground/src/react/output-tabs/output-tabs.module.css
+5 −0 packages/playground/src/react/playground.module.css
+1 −1 packages/playground/src/react/playground.tsx
+42 −44 packages/versioning/src/versioning.ts
+31 −0 packages/versioning/test/versioning.test.ts
191 changes: 137 additions & 54 deletions packages/typespec-autorest/src/emit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SdkContext, createSdkContext } from "@azure-tools/typespec-client-generator-core";
import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
import {
EmitContext,
Namespace,
Expand All @@ -21,6 +21,11 @@ import {
getOpenAPIForService,
sortOpenAPIDocument,
} from "./openapi.js";
import type {
AutorestEmitterResult,
AutorestServiceRecord,
AutorestVersionedServiceRecord,
} from "./types.js";
import { AutorestEmitterContext } from "./utils.js";

/**
Expand Down Expand Up @@ -48,17 +53,34 @@ const defaultOptions = {
} as const;

export async function $onEmit(context: EmitContext<AutorestEmitterOptions>) {
const resolvedOptions = { ...defaultOptions, ...context.options };
const tracer = getTracer(context.program);

const options = resolveAutorestOptions(
context.program,
context.emitterOutputDir,
context.options
);
tracer.trace("options", JSON.stringify(options, null, 2));

await emitAllServiceAtAllVersions(context.program, options);
}

export function resolveAutorestOptions(
program: Program,
emitterOutputDir: string,
options: AutorestEmitterOptions
): ResolvedAutorestEmitterOptions {
const resolvedOptions = { ...defaultOptions, ...options };
const armTypesDir = interpolatePath(
resolvedOptions["arm-types-dir"] ?? "{project-root}/../../common-types/resource-management",
{
"project-root": context.program.projectRoot,
"emitter-output-dir": context.emitterOutputDir,
"project-root": program.projectRoot,
"emitter-output-dir": emitterOutputDir,
}
);
const options: ResolvedAutorestEmitterOptions = {
return {
outputFile: resolvedOptions["output-file"],
outputDir: context.emitterOutputDir,
outputDir: emitterOutputDir,
azureResourceProviderFolder: resolvedOptions["azure-resource-provider-folder"],
examplesDirectory: resolvedOptions["examples-directory"],
version: resolvedOptions["version"],
Expand All @@ -70,37 +92,37 @@ export async function $onEmit(context: EmitContext<AutorestEmitterOptions>) {
useReadOnlyStatusSchema: resolvedOptions["use-read-only-status-schema"],
emitLroOptions: resolvedOptions["emit-lro-options"],
};
const tracer = getTracer(context.program);
tracer.trace("options", JSON.stringify(options, null, 2));

const tcgcSdkContext = createSdkContext(context, "@azure-tools/typespec-autorest", {
versionStrategy: "ignore",
});

await emitAllServiceAtAllVersions(context.program, tcgcSdkContext, options);
}

export async function emitAllServiceAtAllVersions(
export async function getAllServicesAtAllVersions(
program: Program,
tcgcSdkContext: SdkContext,
options: ResolvedAutorestEmitterOptions
) {
): Promise<AutorestServiceRecord[]> {
const tcgcSdkContext = createSdkContext(
{ program, options: {} } as any,
"@azure-tools/typespec-autorest",
{
versionStrategy: "ignore",
}
);

const services = listServices(program);
if (services.length === 0) {
services.push({ type: program.getGlobalNamespaceType() });
}

const serviceRecords: AutorestServiceRecord[] = [];
for (const service of services) {
const originalProgram = program;
const versions = buildVersionProjections(program, service.type).filter(
(v) => !options.version || options.version === v.version
);
for (const record of versions) {

if (versions.length === 1 && versions[0].version === undefined) {
let projectedProgram;
if (record.projections.length > 0) {
projectedProgram = program = projectProgram(originalProgram, record.projections);
if (versions[0].projections.length > 0) {
projectedProgram = program = projectProgram(originalProgram, versions[0].projections);
}

const projectedServiceNs: Namespace = projectedProgram
? (projectedProgram.projector.projectedTypes.get(service.type) as Namespace)
: service.type;
Expand All @@ -110,50 +132,111 @@ export async function emitAllServiceAtAllVersions(
: getService(program, projectedServiceNs)!;
const context: AutorestEmitterContext = {
program,
outputFile: resolveOutputFile(
program,
projectedService,
services.length > 1,
options,
record.version
),
outputFile: resolveOutputFile(program, service, services.length > 1, options),
service: projectedService,
version: record.version,
tcgcSdkContext,
};

const result = await getOpenAPIForService(context, options);
if (!program.compilerOptions.noEmit && !program.hasError()) {
// Sort the document
const sortedDocument = sortOpenAPIDocument(result.document);

// Write out the OpenAPI document to the output path
await emitFile(program, {
path: context.outputFile,
content: prettierOutput(JSON.stringify(sortedDocument, null, 2)),
newLine: options.newLine,
serviceRecords.push({
service,
versioned: false,
...result,
});
} else {
const serviceRecord: AutorestVersionedServiceRecord = {
service,
versioned: true,
versions: [],
};
serviceRecords.push(serviceRecord);

for (const record of versions) {
const projectedProgram = (program = projectProgram(originalProgram, record.projections));

const projectedServiceNs: Namespace = projectedProgram
? (projectedProgram.projector.projectedTypes.get(service.type) as Namespace)
: service.type;
const projectedService =
projectedServiceNs === program.getGlobalNamespaceType()
? { type: program.getGlobalNamespaceType() }
: getService(program, projectedServiceNs)!;
const context: AutorestEmitterContext = {
program,
outputFile: resolveOutputFile(
program,
projectedService,
services.length > 1,
options,
record.version
),
service: projectedService,
version: record.version,
tcgcSdkContext,
};
const result = await getOpenAPIForService(context, options);
serviceRecord.versions.push({
...result,
service: projectedService,
version: record.version!,
});
}
}
}

// Copy examples to the output directory
if (options.examplesDirectory && result.operationExamples.length > 0) {
const examplesPath = resolvePath(getDirectoryPath(context.outputFile), "examples");
await program.host.mkdirp(examplesPath);
for (const { examples } of result.operationExamples) {
if (examples) {
for (const { relativePath, file } of Object.values(examples)) {
await emitFile(program, {
path: resolvePath(examplesPath, relativePath),
content: file.text,
newLine: options.newLine,
});
}
}
}
}
return serviceRecords;
}

async function emitAllServiceAtAllVersions(
program: Program,
options: ResolvedAutorestEmitterOptions
) {
const services = await getAllServicesAtAllVersions(program, options);
if (program.compilerOptions.noEmit || program.hasError()) {
return;
}
for (const serviceRecord of services) {
if (serviceRecord.versioned) {
for (const documentRecord of serviceRecord.versions) {
await emitOutput(program, documentRecord, options);
}
} else {
await emitOutput(program, serviceRecord, options);
}
}
}

async function emitOutput(
program: Program,
result: AutorestEmitterResult,
options: ResolvedAutorestEmitterOptions
) {
const sortedDocument = sortOpenAPIDocument(result.document);

// Write out the OpenAPI document to the output path
await emitFile(program, {
path: result.outputFile,
content: prettierOutput(JSON.stringify(sortedDocument, null, 2)),
newLine: options.newLine,
});

// Copy examples to the output directory
if (options.examplesDirectory && result.operationExamples.length > 0) {
const examplesPath = resolvePath(getDirectoryPath(result.outputFile), "examples");
await program.host.mkdirp(examplesPath);
for (const { examples } of result.operationExamples) {
if (examples) {
for (const { relativePath, file } of Object.values(examples)) {
await emitFile(program, {
path: resolvePath(examplesPath, relativePath),
content: file.text,
newLine: options.newLine,
});
}
}
}
}
}
function prettierOutput(output: string) {
return output + "\n";
}
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-autorest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./decorators.js";
export { $onEmit } from "./emit.js";
export { $onEmit, getAllServicesAtAllVersions, resolveAutorestOptions } from "./emit.js";
export { $lib, AutorestEmitterOptions } from "./lib.js";
export {
getOpenAPIForService,
Expand Down
18 changes: 2 additions & 16 deletions packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
Operation,
Program,
Scalar,
SourceFile,
StringLiteral,
StringTemplate,
SyntaxKind,
Expand Down Expand Up @@ -149,6 +148,7 @@ import {
Refable,
XMSLongRunningFinalState,
} from "./openapi2-document.js";
import type { AutorestEmitterResult, LoadedExample } from "./types.js";
import { AutorestEmitterContext, getClientName, resolveOperationId } from "./utils.js";

interface SchemaContext {
Expand Down Expand Up @@ -239,16 +239,6 @@ interface ProcessedSchema extends PendingSchema {
schema: OpenAPI2Schema | undefined;
}

export interface OperationExamples {
readonly operationId: string;
readonly examples: LoadedExample[];
}

export interface AutorestEmitterResult {
readonly document: OpenAPI2Document;
readonly operationExamples: OperationExamples[];
}

export async function getOpenAPIForService(
context: AutorestEmitterContext,
options: AutorestDocumentEmitterOptions
Expand Down Expand Up @@ -370,6 +360,7 @@ export async function getOpenAPIForService(
}
})
.filter((x) => x) as any,
outputFile: context.outputFile,
};

function resolveHost(
Expand Down Expand Up @@ -2369,11 +2360,6 @@ export function sortOpenAPIDocument(doc: OpenAPI2Document): OpenAPI2Document {
return sorted;
}

interface LoadedExample {
readonly relativePath: string;
readonly file: SourceFile;
readonly data: any;
}
async function loadExamples(
host: CompilerHost,
options: AutorestDocumentEmitterOptions,
Expand Down
62 changes: 62 additions & 0 deletions packages/typespec-autorest/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Service, SourceFile } from "@typespec/compiler";
import type { OpenAPI2Document } from "./openapi2-document.js";

/**
* A record containing the the OpenAPI 3 documents corresponding to
* a particular service definition.
*/
export type AutorestServiceRecord =
| AutorestUnversionedServiceRecord
| AutorestVersionedServiceRecord;

export interface AutorestUnversionedServiceRecord extends AutorestEmitterResult {
/** The service that generated this OpenAPI document */
readonly service: Service;

/** Whether the service is versioned */
readonly versioned: false;
}

export interface AutorestVersionedServiceRecord {
/** 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: AutorestVersionedDocumentRecord[];
}

/**
* A record containing an unversioned OpenAPI document and associated metadata.
*/
export interface AutorestVersionedDocumentRecord extends AutorestEmitterResult {
/** The service that generated this OpenAPI document. */
readonly service: Service;

/** The version of the service. Absent if the service is unversioned. */
readonly version: string;
}

export interface OperationExamples {
readonly operationId: string;
readonly examples: LoadedExample[];
}

export interface AutorestEmitterResult {
/** The OpenAPI document*/
readonly document: OpenAPI2Document;

/** The examples */
readonly operationExamples: OperationExamples[];

/** Output file used */
readonly outputFile: string;
}

export interface LoadedExample {
readonly relativePath: string;
readonly file: SourceFile;
readonly data: any;
}

0 comments on commit d36bb1d

Please sign in to comment.