Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/http-client-java"
---

Support File in multipart and request body.
36 changes: 24 additions & 12 deletions packages/http-client-java/emitter/src/code-model-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1982,16 +1982,28 @@ export class CodeModelBuilder {
// set contentTypes to mediaTypes
op.requests![0].protocol.http!.mediaTypes = sdkBody.contentTypes;

const unknownRequestBody =
op.requests![0].protocol.http!.mediaTypes &&
op.requests![0].protocol.http!.mediaTypes.length > 0 &&
!isKnownContentType(op.requests![0].protocol.http!.mediaTypes);

const sdkType: SdkType = sdkBody.type;

let requestBodyIsFile: boolean = false;
if (
sdkType &&
sdkType.kind === "model" &&
sdkType.serializationOptions.binary &&
sdkType.serializationOptions.binary.isFile
) {
// check for File
requestBodyIsFile = true;
} else if (sdkType && sdkType.kind === "bytes") {
// check for bytes + unknown content-type
const mediaTypes = op.requests![0].protocol.http!.mediaTypes;
const unknownRequestBody =
mediaTypes && mediaTypes.length > 0 && !isKnownContentType(mediaTypes);
requestBodyIsFile = Boolean(unknownRequestBody);
}

let schema: Schema;
if (unknownRequestBody && sdkType.kind === "bytes") {
// if it's unknown request body, handle binary request body
if (requestBodyIsFile) {
// binary/file
schema = this.processBinarySchema(sdkType);
} else {
schema = this.processSchema(getNonNullSdkType(sdkType), sdkBody.name);
Expand Down Expand Up @@ -2226,26 +2238,26 @@ export class CodeModelBuilder {
const bodyType: SdkType | undefined = sdkResponse.type;
let trackConvenienceApi: boolean = Boolean(op.convenienceApi);

let responseIsFile: boolean = false;
let responseBodyIsFile: boolean = false;
if (
bodyType &&
bodyType.kind === "model" &&
bodyType.serializationOptions.binary &&
bodyType.serializationOptions.binary.isFile
) {
// check for File
responseIsFile = true;
responseBodyIsFile = true;
} else if (bodyType && bodyType.kind === "bytes") {
// check for bytes + unknown content-type
const unknownResponseBody =
sdkResponse.contentTypes &&
sdkResponse.contentTypes.length > 0 &&
!isKnownContentType(sdkResponse.contentTypes);
responseIsFile = Boolean(unknownResponseBody);
responseBodyIsFile = Boolean(unknownResponseBody);
}

let response: Response;
if (responseIsFile) {
if (responseBodyIsFile) {
// binary/file
response = new BinaryResponse({
protocol: {
Expand Down Expand Up @@ -2978,7 +2990,7 @@ export class CodeModelBuilder {
return this.codeModel.schemas.add(unionSchema);
}

private processBinarySchema(type: SdkBuiltInType): BinarySchema {
private processBinarySchema(type: SdkType): BinarySchema {
return this.codeModel.schemas.add(
new BinarySchema(type.doc ?? "", {
summary: type.summary,
Expand Down
37 changes: 24 additions & 13 deletions packages/http-client-java/emitter/src/external-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const fileDetailsMap: Map<string, ObjectSchema> = new Map();
function getFileSchemaName(baseName: string, sdkModelType?: SdkModelType): string {
// If the TypeSpec Model exists and is not TypeSpec.Http.File, directly use its name
if (sdkModelType && sdkModelType.crossLanguageDefinitionId !== "TypeSpec.Http.File") {
return baseName;
return sdkModelType.name;
}

// make sure suffix "FileDetails"
Expand Down Expand Up @@ -250,17 +250,21 @@ function addFilenameProperty(
filenameProperty?: SdkModelPropertyType,
processSchemaFunc?: (type: SdkType) => Schema,
) {
const isRequired = filenameProperty ? !filenameProperty.optional : false;
const isConstant = filenameProperty?.type.kind === "constant" && isRequired;
// If the type is constant but not required, treat the type as non-constant String but its value as the default.
const clientDefaultValue =
filenameProperty?.type.kind === "constant" ? String(filenameProperty.type.value) : undefined;
fileDetailsSchema.addProperty(
new Property(
"filename",
"The filename of the file.",
filenameProperty?.type.kind === "constant" && processSchemaFunc
? processSchemaFunc(filenameProperty.type)
: stringSchema,
isConstant && processSchemaFunc ? processSchemaFunc(filenameProperty.type) : stringSchema,
{
required: filenameProperty ? !filenameProperty.optional : false,
required: isRequired,
nullable: false,
readOnly: false,
clientDefaultValue: clientDefaultValue,
},
),
);
Expand All @@ -272,19 +276,27 @@ function addContentTypeProperty(
contentTypeProperty?: SdkModelPropertyType,
processSchemaFunc?: (type: SdkType) => Schema,
) {
const isRequired = contentTypeProperty ? !contentTypeProperty.optional : false;
const isConstant = contentTypeProperty?.type.kind === "constant" && isRequired;
// If the type is constant but not required, treat the type as non-constant String but its value as the default.
/*
* TypeSpec 'TypeSpec.Http.File<"image/png">' is such case.
* Feels that it is not user-friendly to create a single value enum for FileContentType that user probably had to set for the request.
*/
const clientDefaultValue =
contentTypeProperty?.type.kind === "constant"
? String(contentTypeProperty.type.value)
: "application/octet-stream";
fileDetailsSchema.addProperty(
new Property(
"contentType",
"The content-type of the file.",
contentTypeProperty?.type.kind === "constant" && processSchemaFunc
? processSchemaFunc(contentTypeProperty.type)
: stringSchema,
isConstant && processSchemaFunc ? processSchemaFunc(contentTypeProperty.type) : stringSchema,
{
required: contentTypeProperty ? !contentTypeProperty.optional : false,
required: isRequired,
nullable: false,
readOnly: false,
clientDefaultValue:
contentTypeProperty?.type.kind === "constant" ? undefined : "application/octet-stream",
clientDefaultValue: clientDefaultValue,
},
),
);
Expand Down Expand Up @@ -317,8 +329,7 @@ export function getFileDetailsSchema(
- Allow required for "filename" and "contentType"
*/
const filePropertyName = property.name;
const fileSchemaName = fileSdkType.name;
const schemaName = getFileSchemaName(fileSchemaName, fileSdkType);
const schemaName = getFileSchemaName(filePropertyName, fileSdkType);
let fileDetailsSchema = fileDetailsMap.get(schemaName);
if (!fileDetailsSchema) {
const typeNamespace = getNamespace(property.type.__raw) ?? namespace;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* is overridden, as shown in the examples below.
*/
@Fluent
public final class FileDetails {
public final class FileData2FileDetails {
/*
* The content of the file.
*/
Expand All @@ -72,12 +72,12 @@ public final class FileDetails {
private String contentType = "application/octet-stream";

/**
* Creates an instance of FileDetails class.
* Creates an instance of FileData2FileDetails class.
*
* @param content the content value to set.
*/
@Generated
public FileDetails(BinaryData content) {
public FileData2FileDetails(BinaryData content) {
this.content = content;
}

Expand Down Expand Up @@ -105,10 +105,10 @@ public String getFilename() {
* Set the filename property: The filename of the file.
*
* @param filename the filename value to set.
* @return the FileDetails object itself.
* @return the FileData2FileDetails object itself.
*/
@Generated
public FileDetails setFilename(String filename) {
public FileData2FileDetails setFilename(String filename) {
this.filename = filename;
return this;
}
Expand All @@ -127,10 +127,10 @@ public String getContentType() {
* Set the contentType property: The content-type of the file.
*
* @param contentType the contentType value to set.
* @return the FileDetails object itself.
* @return the FileData2FileDetails object itself.
*/
@Generated
public FileDetails setContentType(String contentType) {
public FileData2FileDetails setContentType(String contentType) {
this.contentType = contentType;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public final class UploadHttpPartRequest {
* The file_data2 property.
*/
@Generated
private final FileDetails fileData2;
private final FileData2FileDetails fileData2;

/*
* The size property.
Expand All @@ -38,7 +38,7 @@ public final class UploadHttpPartRequest {
* @param size the size value to set.
*/
@Generated
public UploadHttpPartRequest(InheritFileData fileData1, FileDetails fileData2, Size size) {
public UploadHttpPartRequest(InheritFileData fileData1, FileData2FileDetails fileData2, Size size) {
this.fileData1 = fileData1;
this.fileData2 = fileData2;
this.size = size;
Expand All @@ -60,7 +60,7 @@ public InheritFileData getFileData1() {
* @return the fileData2 value.
*/
@Generated
public FileDetails getFileData2() {
public FileData2FileDetails getFileData2() {
return this.fileData2;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"tsptest.multipart.MultipartClient.uploadHttpPartWithResponse": "TspTest.Multipart.uploadHttpPart",
"tsptest.multipart.MultipartClient.uploadWithResponse": "TspTest.Multipart.upload",
"tsptest.multipart.MultipartClientBuilder": "TspTest.Multipart",
"tsptest.multipart.models.FileData2FileDetails": "TypeSpec.Http.File",
"tsptest.multipart.models.FileDataFileDetails": null,
"tsptest.multipart.models.FileDetails": "TypeSpec.Http.File",
"tsptest.multipart.models.FormData": "TspTest.Multipart.FormData",
"tsptest.multipart.models.ImageFileDetails": null,
"tsptest.multipart.models.ImageType": "TspTest.Multipart.ImageType",
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"flavor":"Azure","crossLanguageDefinitions":{"tsptest.multipart.MultipartAsyncClient":"TspTest.Multipart","tsptest.multipart.MultipartAsyncClient.upload":"TspTest.Multipart.upload","tsptest.multipart.MultipartAsyncClient.uploadHttpPart":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartAsyncClient.uploadHttpPartWithResponse":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartAsyncClient.uploadWithResponse":"TspTest.Multipart.upload","tsptest.multipart.MultipartClient":"TspTest.Multipart","tsptest.multipart.MultipartClient.upload":"TspTest.Multipart.upload","tsptest.multipart.MultipartClient.uploadHttpPart":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartClient.uploadHttpPartWithResponse":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartClient.uploadWithResponse":"TspTest.Multipart.upload","tsptest.multipart.MultipartClientBuilder":"TspTest.Multipart","tsptest.multipart.models.FileDataFileDetails":null,"tsptest.multipart.models.FileDetails":"TypeSpec.Http.File","tsptest.multipart.models.FormData":"TspTest.Multipart.FormData","tsptest.multipart.models.ImageFileDetails":null,"tsptest.multipart.models.ImageType":"TspTest.Multipart.ImageType","tsptest.multipart.models.InheritFileData":"TspTest.Multipart.Inherit2File","tsptest.multipart.models.Size":"TspTest.Multipart.Size","tsptest.multipart.models.UploadHttpPartRequest":"TspTest.Multipart.uploadHttpPart.Request.anonymous"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/multipart/MultipartAsyncClient.java","src/main/java/tsptest/multipart/MultipartClient.java","src/main/java/tsptest/multipart/MultipartClientBuilder.java","src/main/java/tsptest/multipart/implementation/MultipartClientImpl.java","src/main/java/tsptest/multipart/implementation/MultipartFormDataHelper.java","src/main/java/tsptest/multipart/implementation/package-info.java","src/main/java/tsptest/multipart/models/FileDataFileDetails.java","src/main/java/tsptest/multipart/models/FileDetails.java","src/main/java/tsptest/multipart/models/FormData.java","src/main/java/tsptest/multipart/models/ImageFileDetails.java","src/main/java/tsptest/multipart/models/ImageType.java","src/main/java/tsptest/multipart/models/InheritFileData.java","src/main/java/tsptest/multipart/models/Size.java","src/main/java/tsptest/multipart/models/UploadHttpPartRequest.java","src/main/java/tsptest/multipart/models/package-info.java","src/main/java/tsptest/multipart/package-info.java"]}
{"flavor":"Azure","crossLanguageDefinitions":{"tsptest.multipart.MultipartAsyncClient":"TspTest.Multipart","tsptest.multipart.MultipartAsyncClient.upload":"TspTest.Multipart.upload","tsptest.multipart.MultipartAsyncClient.uploadHttpPart":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartAsyncClient.uploadHttpPartWithResponse":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartAsyncClient.uploadWithResponse":"TspTest.Multipart.upload","tsptest.multipart.MultipartClient":"TspTest.Multipart","tsptest.multipart.MultipartClient.upload":"TspTest.Multipart.upload","tsptest.multipart.MultipartClient.uploadHttpPart":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartClient.uploadHttpPartWithResponse":"TspTest.Multipart.uploadHttpPart","tsptest.multipart.MultipartClient.uploadWithResponse":"TspTest.Multipart.upload","tsptest.multipart.MultipartClientBuilder":"TspTest.Multipart","tsptest.multipart.models.FileData2FileDetails":"TypeSpec.Http.File","tsptest.multipart.models.FileDataFileDetails":null,"tsptest.multipart.models.FormData":"TspTest.Multipart.FormData","tsptest.multipart.models.ImageFileDetails":null,"tsptest.multipart.models.ImageType":"TspTest.Multipart.ImageType","tsptest.multipart.models.InheritFileData":"TspTest.Multipart.Inherit2File","tsptest.multipart.models.Size":"TspTest.Multipart.Size","tsptest.multipart.models.UploadHttpPartRequest":"TspTest.Multipart.uploadHttpPart.Request.anonymous"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/multipart/MultipartAsyncClient.java","src/main/java/tsptest/multipart/MultipartClient.java","src/main/java/tsptest/multipart/MultipartClientBuilder.java","src/main/java/tsptest/multipart/implementation/MultipartClientImpl.java","src/main/java/tsptest/multipart/implementation/MultipartFormDataHelper.java","src/main/java/tsptest/multipart/implementation/package-info.java","src/main/java/tsptest/multipart/models/FileData2FileDetails.java","src/main/java/tsptest/multipart/models/FileDataFileDetails.java","src/main/java/tsptest/multipart/models/FormData.java","src/main/java/tsptest/multipart/models/ImageFileDetails.java","src/main/java/tsptest/multipart/models/ImageType.java","src/main/java/tsptest/multipart/models/InheritFileData.java","src/main/java/tsptest/multipart/models/Size.java","src/main/java/tsptest/multipart/models/UploadHttpPartRequest.java","src/main/java/tsptest/multipart/models/package-info.java","src/main/java/tsptest/multipart/package-info.java"]}

This file was deleted.

Loading