Skip to content
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

http-client-java, namespace from client.tsp #5051

Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf032f6
use namespace from code-model
weidongxu-microsoft Nov 11, 2024
734498d
regen test
weidongxu-microsoft Nov 11, 2024
3067bb3
bump tcgc
weidongxu-microsoft Nov 11, 2024
00be87e
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 13, 2024
fba55b5
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 13, 2024
c9238f8
update logic for deduplicate model name
weidongxu-microsoft Nov 13, 2024
3c55a7b
Azure.Core.Foundations.OperationStatus
weidongxu-microsoft Nov 13, 2024
c02a3f6
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 14, 2024
ca91925
regen
weidongxu-microsoft Nov 14, 2024
462e1ba
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 14, 2024
769c6e8
limit the handling of azure-core model in branded
weidongxu-microsoft Nov 14, 2024
e0a57b5
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 14, 2024
3e74447
Merge branch 'http-client-java_namespace' of https://github.com/weido…
weidongxu-microsoft Nov 14, 2024
1190ee0
namespace support
weidongxu-microsoft Nov 14, 2024
e5ed70a
regen and update test
weidongxu-microsoft Nov 14, 2024
4083889
format
weidongxu-microsoft Nov 14, 2024
3013a4e
comment
weidongxu-microsoft Nov 14, 2024
4316119
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 18, 2024
aab062f
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 20, 2024
b75c2fd
move the logic of models and implementation package to java code
weidongxu-microsoft Nov 21, 2024
d09ed65
bump version
weidongxu-microsoft Nov 22, 2024
abbac86
npm audit fix
weidongxu-microsoft Nov 22, 2024
4327d64
comments
weidongxu-microsoft Nov 22, 2024
7ea88f8
Merge branch 'main' into http-client-java_namespace
weidongxu-microsoft Nov 22, 2024
3ac4d25
bump jar
weidongxu-microsoft Nov 22, 2024
9cb7f8d
tcgc 0.48.2
weidongxu-microsoft Nov 22, 2024
ac21b02
restrict the type param to getJavaNamespace
weidongxu-microsoft Nov 22, 2024
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
130 changes: 91 additions & 39 deletions packages/http-client-java/emitter/src/code-model-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ import {
} from "@azure-tools/typespec-client-generator-core";
import {
EmitContext,
Interface,
Model,
ModelProperty,
Namespace,
Expand Down Expand Up @@ -168,11 +167,13 @@ export class CodeModelBuilder {
private program: Program;
private typeNameOptions: TypeNameOptions;
private namespace: string;
private baseJavaNamespace: string = ""; // it will be set at the start of "build" function
private legacyJavaNamespace: boolean = false; // backward-compatible mode, that emitter ignores clientNamespace from TCGC
private sdkContext!: SdkContext;
private options: EmitterOptions;
private codeModel: CodeModel;
private emitterContext: EmitContext<EmitterOptions>;
private serviceNamespace: Namespace | Interface | Operation;
private serviceNamespace: Namespace;

private loggingEnabled: boolean = false;

Expand Down Expand Up @@ -205,8 +206,6 @@ export class CodeModelBuilder {
this.serviceNamespace = service.type;

this.namespace = getNamespaceFullName(this.serviceNamespace) || "Azure.Client";
// java namespace
const javaNamespace = this.getJavaNamespace(this.namespace);

const namespace1 = this.namespace;
this.typeNameOptions = {
Expand All @@ -232,9 +231,7 @@ export class CodeModelBuilder {
summary: this.getSummary(this.serviceNamespace),
namespace: this.namespace,
},
java: {
namespace: javaNamespace,
},
java: {},
},
});
}
Expand All @@ -244,6 +241,21 @@ export class CodeModelBuilder {
versioning: { previewStringRegex: /$/ },
}); // include all versions and do the filter by ourselves

// java namespace
if (this.options.namespace) {
weidongxu-microsoft marked this conversation as resolved.
Show resolved Hide resolved
// legacy mode, clientNamespace from TCGC will be ignored
this.legacyJavaNamespace = true;
this.baseJavaNamespace = this.options.namespace;
} else {
this.legacyJavaNamespace = false;
// baseJavaNamespace is used for model from Azure.Core/Azure.ResourceManager but cannot be mapped to azure-core,
// or some model (e.g. Options, FileDetails) that is created in this emitter.
// otherwise, the clientNamespace from SdkType will be used.
this.baseJavaNamespace = this.getBaseJavaNamespace();
}

this.codeModel.language.java!.namespace = this.baseJavaNamespace;

// TODO: reportDiagnostics from TCGC temporary disabled
// issue https://github.com/Azure/typespec-azure/issues/1675
// this.program.reportDiagnostics(this.sdkContext.diagnostics);
Expand Down Expand Up @@ -502,13 +514,13 @@ export class CodeModelBuilder {

private processClient(client: SdkClientType<SdkHttpOperation>): CodeModelClient {
let clientName = client.name;
let javaNamespace = this.getJavaNamespace(this.namespace);
let javaNamespace = this.getJavaNamespace(client);
const clientFullName = client.name;
const clientNameSegments = clientFullName.split(".");
if (clientNameSegments.length > 1) {
clientName = clientNameSegments.at(-1)!;
const clientSubNamespace = clientNameSegments.slice(0, -1).join(".");
javaNamespace = this.getJavaNamespace(this.namespace + "." + clientSubNamespace);
const clientSubNamespace = clientNameSegments.slice(0, -1).join(".").toLowerCase();
javaNamespace = javaNamespace + "." + clientSubNamespace;
}

const codeModelClient = new CodeModelClient(clientName, client.doc ?? "", {
Expand Down Expand Up @@ -978,7 +990,7 @@ export class CodeModelBuilder {
language: {
java: {
name: "OperationLocationPollingStrategy",
namespace: this.getJavaNamespace(this.namespace) + ".implementation",
namespace: this.baseJavaNamespace + ".implementation",
},
},
});
Expand Down Expand Up @@ -1505,7 +1517,7 @@ export class CodeModelBuilder {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
namespace: this.getJavaNamespace(),
weidongxu-microsoft marked this conversation as resolved.
Show resolved Hide resolved
},
},
}),
Expand Down Expand Up @@ -1975,7 +1987,7 @@ export class CodeModelBuilder {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
namespace: this.getJavaNamespace(type),
},
},
});
Expand Down Expand Up @@ -2075,7 +2087,7 @@ export class CodeModelBuilder {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
namespace: this.getJavaNamespace(type),
},
},
});
Expand Down Expand Up @@ -2165,7 +2177,7 @@ export class CodeModelBuilder {
if (type.kind === "Model") {
const effective = getEffectiveModelType(program, type, isSchemaProperty);
if (this.isArm() && getNamespace(effective as Model)?.startsWith("Azure.ResourceManager")) {
// Catalog is TrackedResource<CatalogProperties>
// e.g. typespec: Catalog is TrackedResource<CatalogProperties>
return type;
} else if (effective.name) {
return effective;
Expand Down Expand Up @@ -2257,7 +2269,7 @@ export class CodeModelBuilder {
namespace: namespace,
},
java: {
namespace: this.getJavaNamespace(namespace),
namespace: this.getJavaNamespace(it),
},
},
});
Expand Down Expand Up @@ -2343,15 +2355,19 @@ export class CodeModelBuilder {

private processMultipartFormDataFilePropertySchema(property: SdkBodyModelPropertyType): Schema {
const processSchemaFunc = (type: SdkType) => this.processSchema(type, "");
if (property.type.kind === "bytes" || property.type.kind === "model") {
const processNamespaceFunc = (type: SdkType) => {
const namespace =
property.type.kind === "model"
? (getNamespace(property.type.__raw) ?? this.namespace)
: this.namespace;
type.kind === "model" ? (getNamespace(type.__raw) ?? this.namespace) : this.namespace;
const javaNamespace = this.getJavaNamespace(type);
return { namespace, javaNamespace };
};

if (property.type.kind === "bytes" || property.type.kind === "model") {
const namespaceTuple = processNamespaceFunc(property.type);
return getFileDetailsSchema(
property,
getNamespace(property.type.__raw) ?? this.namespace,
namespace,
namespaceTuple.namespace,
namespaceTuple.javaNamespace,
weidongxu-microsoft marked this conversation as resolved.
Show resolved Hide resolved
this.codeModel.schemas,
this.binarySchema,
this.stringSchema,
Expand All @@ -2361,17 +2377,14 @@ export class CodeModelBuilder {
property.type.kind === "array" &&
(property.type.valueType.kind === "bytes" || property.type.valueType.kind === "model")
) {
const namespace =
property.type.valueType.kind === "model"
? (getNamespace(property.type.valueType.__raw) ?? this.namespace)
: this.namespace;
const namespaceTuple = processNamespaceFunc(property.type.valueType);
weidongxu-microsoft marked this conversation as resolved.
Show resolved Hide resolved
return new ArraySchema(
property.name,
property.doc ?? "",
getFileDetailsSchema(
property,
namespace,
this.getJavaNamespace(namespace),
namespaceTuple.namespace,
namespaceTuple.javaNamespace,
this.codeModel.schemas,
this.binarySchema,
this.stringSchema,
Expand Down Expand Up @@ -2467,18 +2480,57 @@ export class CodeModelBuilder {
}
}

private getJavaNamespace(namespace: string | undefined): string | undefined {
const tspNamespace = this.namespace;
const baseJavaNamespace = this.emitterContext.options.namespace;
if (!namespace) {
return undefined;
} else if (
baseJavaNamespace &&
(namespace === tspNamespace || namespace.startsWith(tspNamespace + "."))
) {
return baseJavaNamespace + namespace.slice(tspNamespace.length).toLowerCase();
private getBaseJavaNamespace(): string {
// hack, just find the shortest clientNamespace among all clients
// hopefully it is the root namespace of the SDK
let baseJavaNamespace: string | undefined = undefined;
this.sdkContext.sdkPackage.clients
.map((it) => it.clientNamespace)
.forEach((it) => {
if (baseJavaNamespace === undefined || baseJavaNamespace.length > it.length) {
baseJavaNamespace = it;
}
});
// fallback if there is no client
if (!baseJavaNamespace) {
baseJavaNamespace = this.namespace;
}
return baseJavaNamespace.toLowerCase();
}

private getJavaNamespace(
type: SdkType | SdkClientType<SdkHttpOperation> | undefined = undefined,
): string | undefined {
// clientNamespace from TCGC
const clientNamespace: string | undefined =
type && "clientNamespace" in type ? type.clientNamespace : undefined;
weidongxu-microsoft marked this conversation as resolved.
Show resolved Hide resolved

if (this.isBranded() && this.baseJavaNamespace && type && "crossLanguageDefinitionId" in type) {
// special handling for namespace of model that cannot be mapped to azure-core
if (type.crossLanguageDefinitionId === "TypeSpec.Http.File") {
// TypeSpec.Http.File
return this.baseJavaNamespace;
} else if (type.crossLanguageDefinitionId === "Azure.Core.Foundations.OperationState") {
// Azure.Core.OperationState
return this.baseJavaNamespace;
} else if (
type.crossLanguageDefinitionId === "Azure.Core.ResourceOperationStatus" ||
type.crossLanguageDefinitionId === "Azure.Core.Foundations.OperationStatus"
) {
// Azure.Core.ResourceOperationStatus<>
// Azure.Core.Foundations.OperationStatus<>
// usually this model will not be generated, but javadoc of protocol method requires it be in SDK namespace
return this.baseJavaNamespace;
} else if (type.crossLanguageDefinitionId.startsWith("Azure.ResourceManager.")) {
// models in Azure.ResourceManager
return this.baseJavaNamespace;
}
}

if (this.legacyJavaNamespace || !clientNamespace) {
return this.baseJavaNamespace;
} else {
return namespace.toLowerCase();
return clientNamespace.toLowerCase();
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/http-client-java/emitter/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export function pascalCase(name: string): string {
export function getNamespace(type: Type | undefined): string | undefined {
if (
type &&
(type.kind === "Model" ||
(type.kind === "Interface" ||
type.kind === "Model" ||
type.kind === "Enum" ||
type.kind === "Union" ||
type.kind === "Operation")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,17 @@ public final String getPackage() {
* @return The package name with the provided package suffixes appended.
*/
public final String getPackage(String... packageSuffixes) {
return getPackageName(packageName, packageSuffixes);
}

/**
* Get the package name with the provided package suffixes appended.
*
* @param packageName the package name.
* @param packageSuffixes The package suffixes to append to the package name.
* @return The package name with the provided package suffixes appended.
*/
public final String getPackageName(String packageName, String... packageSuffixes) {
StringBuilder packageBuilder = new StringBuilder(packageName);
if (packageSuffixes != null) {
for (String packageSuffix : packageSuffixes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,22 @@ public static IType createEnumType(ChoiceSchema enumType, boolean expandable, bo
if (enumTypeName == null || enumTypeName.isEmpty() || enumTypeName.equals("enum")) {
return ClassType.STRING;
} else {
String enumPackage = settings.getPackage(settings.getModelsSubpackage());
String enumPackage = settings.getPackage();
String[] packageSuffixes;
if (settings.isCustomType(enumTypeName)) {
enumPackage = settings.getPackage(settings.getCustomTypesSubpackage());
packageSuffixes = new String[] { settings.getCustomTypesSubpackage() };
} else if (settings.isDataPlaneClient()
&& (enumType.getUsage() != null && enumType.getUsage().contains(SchemaContext.INTERNAL))) {
// internal type, which is not exposed to user
enumPackage
= settings.getPackage(settings.getImplementationSubpackage(), settings.getModelsSubpackage());
packageSuffixes
= new String[] { settings.getImplementationSubpackage(), settings.getModelsSubpackage() };
} else {
packageSuffixes = new String[] { settings.getModelsSubpackage() };
}
if (!CoreUtils.isNullOrEmpty(enumType.getLanguage().getJava().getNamespace())) {
enumPackage = settings.getPackageName(enumType.getLanguage().getJava().getNamespace(), packageSuffixes);
} else {
enumPackage = settings.getPackage(packageSuffixes);
}

String summary = enumType.getSummary();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ private static boolean hasFlattenedProperty(ObjectSchema compositeType,
* @return Whether the type is predefined.
*/
protected boolean isPredefinedModel(ClassType compositeType) {
if (JavaSettings.getInstance().isDataPlaneClient()) {
if (JavaSettings.getInstance().isDataPlaneClient() && JavaSettings.getInstance().isBranded()) {
// see ObjectMapper.mapPredefinedModel
// this might be too simplified, and Android might require a different implementation
return compositeType.getPackage().startsWith(ExternalPackage.CORE.getPackageName() + ".");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.microsoft.typespec.http.client.generator.core.mapper;

import com.azure.core.util.CoreUtils;
import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.ObjectSchema;
import com.microsoft.typespec.http.client.generator.core.extension.model.codemodel.SchemaContext;
import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings;
Expand Down Expand Up @@ -45,27 +46,38 @@ private ClassType createClassType(ObjectSchema compositeType) {
}

String classPackage;
String[] packageSuffixes;
String className = compositeType.getLanguage().getJava().getName();
if (settings.isCustomType(compositeType.getLanguage().getJava().getName())) {
classPackage = settings.getPackage(settings.getCustomTypesSubpackage());
packageSuffixes = new String[] { settings.getCustomTypesSubpackage() };
} else if (settings.isFluent() && isInnerModel(compositeType)) {
className += "Inner";
classPackage = settings.getPackage(settings.getFluentModelsSubpackage());
packageSuffixes = new String[] { settings.getFluentModelsSubpackage() };
} else if (settings.isFluent() && compositeType.isFlattenedSchema()) {
// put class of flattened type to implementation package
classPackage = settings.getPackage(settings.getFluentModelsSubpackage());
// put class of flattened type to fluent package
packageSuffixes = new String[] { settings.getFluentModelsSubpackage() };
} else if (settings.isDataPlaneClient() && isInternalModel(compositeType)) {
// internal type is not exposed to user
classPackage = settings.getPackage(settings.getImplementationSubpackage(), settings.getModelsSubpackage());
packageSuffixes = new String[] { settings.getImplementationSubpackage(), settings.getModelsSubpackage() };
} else if (isPageModel(compositeType)) {
// put class of Page<> type to implementation package
// for DPG from TypeSpec, these are not generated to class

// this would not affect mgmt from Swagger, as the "usage" from m4 does not have this information.

classPackage = settings.getPackage(settings.getImplementationSubpackage(), settings.getModelsSubpackage());
packageSuffixes = new String[] { settings.getImplementationSubpackage(), settings.getModelsSubpackage() };
} else {
classPackage = settings.getPackage(settings.getModelsSubpackage());
packageSuffixes = new String[] { settings.getModelsSubpackage() };
}
/*
* For models with language.java.namespace, it would be used as the base package name to append suffixes.
* Otherwise, the packageName in JavaSetting is the base package name.
*/
if (!CoreUtils.isNullOrEmpty(compositeType.getLanguage().getJava().getNamespace())) {
classPackage
= settings.getPackageName(compositeType.getLanguage().getJava().getNamespace(), packageSuffixes);
} else {
classPackage = settings.getPackage(packageSuffixes);
}

return new ClassType.Builder().packageName(classPackage)
Expand All @@ -82,7 +94,11 @@ private ClassType createClassType(ObjectSchema compositeType) {
* @return The predefined type.
*/
protected ClassType mapPredefinedModel(ObjectSchema compositeType) {
return SchemaUtil.mapExternalModel(compositeType);
if (JavaSettings.getInstance().isBranded()) {
return SchemaUtil.mapExternalModel(compositeType);
} else {
return null;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"testserver-stop": "cadl-ranch server stop"
},
"dependencies": {
"@azure-tools/cadl-ranch-specs": "0.39.0",
"@typespec/http-client-java": "file:/../../typespec-http-client-java-0.1.0.tgz",
"@azure-tools/cadl-ranch-specs": "0.39.4",
"@typespec/http-client-java": "file:/../../typespec-http-client-java-0.1.1.tgz",
"@typespec/http-client-java-tests": "file:"
},
"overrides": {
Expand Down
Loading
Loading