Skip to content

Remove the dependency of stringify-with-refs #7455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions packages/http-client-csharp/emitter/src/code-model-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import { UsageFlags } from "@azure-tools/typespec-client-generator-core";
import { resolvePath } from "@typespec/compiler";
import { PreserveType, stringifyRefs } from "json-serialize-refs";
import { configurationFileName, tspOutputFileName } from "./constants.js";
import { CSharpEmitterContext } from "./sdk-context.js";
import { CodeModel } from "./type/code-model.js";
Expand All @@ -23,10 +22,89 @@ export async function writeCodeModel(
) {
await context.program.host.writeFile(
resolvePath(outputFolder, tspOutputFileName),
prettierOutput(stringifyRefs(codeModel, transformJSONProperties, 1, PreserveType.Objects)),
prettierOutput(JSON.stringify(buildJson(context, codeModel), transformJSONProperties, 2)),
);
}

/**
* This function builds a json from code model with refs and ids in it.
* @param context - The CSharp emitter context
* @param codeModel - The code model to build
*/
function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any {
const typesToRef = new Set<any>([
...context.__typeCache.clients.values(),
...context.__typeCache.methods.values(),
...context.__typeCache.operations.values(),
...context.__typeCache.responses.values(),
...context.__typeCache.properties.values(),
...context.__typeCache.types.values(),
]);
const objectsIds = new Map<any, string>();
const stack: any[] = [];

return doBuildJson(codeModel, stack);

function doBuildJson(obj: any, stack: any[]): any {
// check if this is a primitive type or null or undefined
if (!obj || typeof obj !== "object") {
return obj;
}
// we switch here for object, arrays and primitives
if (Array.isArray(obj)) {
// array types
return obj.map((item) => doBuildJson(item, stack));
} else {
// this is an object
if (shouldHaveRef(obj)) {
// we will add the $id property to the object if this is the first time we see it
// or returns a $ref if we have seen it before
let id = objectsIds.get(obj);
if (id) {
// we have seen this object before
return {
$ref: id,
};
} else {
// this is the first time we see this object
id = (objectsIds.size + 1).toString();
objectsIds.set(obj, id);
return handleObject(obj, id, stack);
}
} else {
// this is not an object to ref
return handleObject(obj, undefined, stack);
}
}
}

function handleObject(obj: any, id: string | undefined, stack: any[]): any {
if (stack.includes(obj)) {
// we have a cyclical reference, we should not continue
context.logger.warn(`Cyclical reference detected in the code model (id: ${id}).`);
return undefined;
}

const result: any = id === undefined ? {} : { $id: id };
stack.push(obj);

for (const property in obj) {
if (property === "__raw") {
continue; // skip __raw property
}
const v = obj[property];
result[property] = doBuildJson(v, stack);
}

stack.pop();
return result;
}

function shouldHaveRef(obj: any): boolean {
return typesToRef.has(obj);
}
}

export async function writeConfiguration(
context: CSharpEmitterContext,
configurations: Configuration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { UsageFlags } from "@azure-tools/typespec-client-generator-core";
import { CSharpEmitterContext } from "../sdk-context.js";
import { CodeModel } from "../type/code-model.js";
import { fromSdkClients } from "./client-converter.js";
import { navigateModels } from "./model.js";
import { processServiceAuthentication } from "./service-authentication.js";
import { fromSdkEnums, fromSdkModels } from "./type-converter.js";
import { firstLetterToUpperCase, getClientNamespaceString } from "./utils.js";

/**
Expand All @@ -18,8 +18,6 @@ import { firstLetterToUpperCase, getClientNamespaceString } from "./utils.js";
export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
const sdkPackage = sdkContext.sdkPackage;

navigateModels(sdkContext);

const sdkApiVersionEnums = sdkPackage.enums.filter((e) => e.usage === UsageFlags.ApiVersionEnum);

const rootClients = sdkPackage.clients;
Expand All @@ -31,6 +29,11 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {

const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions);

const enums = fromSdkEnums(sdkContext, sdkPackage.enums);
const models = fromSdkModels(sdkContext, sdkPackage.models);
// TODO -- TCGC now does not have constants field in its sdkPackage, they might add it in the future.
const constants = Array.from(sdkContext.__typeCache.constants.values());

// TODO - TCGC has two issues which come from the same root cause: the name determination algorithm based on the typespec node of the constant.
// typespec itself will always use the same node/Type instance for the same value constant, therefore a lot of names are not correct.
// issues:
Expand Down Expand Up @@ -69,9 +72,9 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
// if the typespec is changed.
name: getClientNamespaceString(sdkContext)!,
apiVersions: rootApiVersions,
enums: Array.from(sdkContext.__typeCache.enums.values()),
constants: Array.from(sdkContext.__typeCache.constants.values()),
models: Array.from(sdkContext.__typeCache.models.values()),
enums: enums,
constants: constants,
models: models,
clients: inputClients,
auth: processServiceAuthentication(sdkContext, sdkPackage),
};
Expand Down
52 changes: 0 additions & 52 deletions packages/http-client-csharp/emitter/src/lib/model.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -48,67 +48,86 @@ import { parseHttpRequestMethod } from "../type/request-method.js";
import { ResponseLocation } from "../type/response-location.js";
import { getExternalDocs, getOperationId } from "./decorators.js";
import { fromSdkHttpExamples } from "./example-converter.js";
import { fromSdkModelType, fromSdkType } from "./type-converter.js";
import { fromSdkType } from "./type-converter.js";
import { getClientNamespaceString } from "./utils.js";

export function fromSdkServiceMethod(
sdkContext: CSharpEmitterContext,
method: SdkServiceMethod<SdkHttpOperation>,
sdkMethod: SdkServiceMethod<SdkHttpOperation>,
uri: string,
rootApiVersions: string[],
): InputServiceMethod | undefined {
const methodKind = method.kind;
let method = sdkContext.__typeCache.methods.get(sdkMethod);
if (method) {
return method;
}
const methodKind = sdkMethod.kind;

switch (methodKind) {
case "basic":
return createServiceMethod<InputBasicServiceMethod>(sdkContext, method, uri, rootApiVersions);
method = createServiceMethod<InputBasicServiceMethod>(
sdkContext,
sdkMethod,
uri,
rootApiVersions,
);
break;
case "paging":
const pagingServiceMethod = createServiceMethod<InputPagingServiceMethod>(
sdkContext,
method,
sdkMethod,
uri,
rootApiVersions,
);
pagingServiceMethod.pagingMetadata = loadPagingServiceMetadata(
sdkContext,
method,
sdkMethod,
rootApiVersions,
uri,
);
return pagingServiceMethod;
method = pagingServiceMethod;
break;
case "lro":
const lroServiceMethod = createServiceMethod<InputLongRunningServiceMethod>(
sdkContext,
method,
sdkMethod,
uri,
rootApiVersions,
);
lroServiceMethod.lroMetadata = loadLongRunningMetadata(sdkContext, method);
return lroServiceMethod;
lroServiceMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod);
method = lroServiceMethod;
break;
case "lropaging":
const lroPagingMethod = createServiceMethod<InputLongRunningPagingServiceMethod>(
sdkContext,
method,
sdkMethod,
uri,
rootApiVersions,
);
lroPagingMethod.lroMetadata = loadLongRunningMetadata(sdkContext, method);
lroPagingMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod);
lroPagingMethod.pagingMetadata = loadPagingServiceMetadata(
sdkContext,
method,
sdkMethod,
rootApiVersions,
uri,
);
return lroPagingMethod;

method = lroPagingMethod;
break;
default:
sdkContext.logger.reportDiagnostic({
code: "unsupported-service-method",
format: { methodKind: methodKind },
target: NoTarget,
});
return undefined;
method = undefined;
break;
}

if (method) {
sdkContext.__typeCache.updateSdkMethodReferences(sdkMethod, method);
}

return method;
}

export function fromSdkServiceMethodOperation(
Expand All @@ -117,6 +136,11 @@ export function fromSdkServiceMethodOperation(
uri: string,
rootApiVersions: string[],
): InputOperation {
let operation = sdkContext.__typeCache.operations.get(method.operation);
if (operation) {
return operation;
}

let generateConvenience = shouldGenerateConvenient(sdkContext, method.operation.__raw.operation);
if (method.operation.verb === "patch" && generateConvenience) {
sdkContext.logger.reportDiagnostic({
Expand All @@ -129,7 +153,7 @@ export function fromSdkServiceMethodOperation(
generateConvenience = false;
}

return {
operation = {
name: method.name,
resourceName:
getResourceOperation(sdkContext.program, method.operation.__raw.operation)?.resourceType
Expand All @@ -155,6 +179,10 @@ export function fromSdkServiceMethodOperation(
? fromSdkHttpExamples(sdkContext, method.operation.examples)
: undefined,
};

sdkContext.__typeCache.updateSdkOperationReferences(method.operation, operation);

return operation;
}

export function getParameterDefaultValue(
Expand Down Expand Up @@ -365,7 +393,7 @@ function loadLongRunningMetadata(
statusCodes: method.operation.verb === "delete" ? [204] : [200],
bodyType:
method.lroMetadata.finalResponse?.envelopeResult !== undefined
? fromSdkModelType(sdkContext, method.lroMetadata.finalResponse.envelopeResult)
? fromSdkType(sdkContext, method.lroMetadata.finalResponse.envelopeResult)
: undefined,
} as OperationResponse,
resultPath: method.lroMetadata.finalResponse?.resultPath,
Expand Down
Loading
Loading