Skip to content

Commit

Permalink
Merge branch 'main' into arm-migration
Browse files Browse the repository at this point in the history
  • Loading branch information
markcowl committed Jan 29, 2024
2 parents 930fd46 + f699d8e commit 624a802
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-foxes-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@azure-tools/typespec-client-generator-core": patch
---

add MultipartFile type
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/rgopenapi/providers/Microsoft.Contoso/employees/le-8MU--J3W6q8D386p3-iT3",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down Expand Up @@ -59,7 +59,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/rgopenapi/providers/Microsoft.Contoso/employees/9KF-f-8b",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/rgopenapi/providers/Microsoft.Contoso/employees/le-8MU--J3W6q8D386p3-iT3",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/rgopenapi/providers/Microsoft.Contoso/employees/test",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/rgopenapi/providers/Microsoft.Contoso/employees/test",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"key2913": "urperxmkkhhkp"
},
"location": "itajgxyqozseoygnl",
"id": "dnkyotqlrefuwxribpzbl",
"id": "/subscriptions/11809CA1-E126-4017-945E-AA795CD5C5A9/resourceGroups/contoso/providers/Microsoft.Contoso/employees/test",
"name": "xepyxhpb",
"type": "svvamxrdnnv",
"systemData": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"actionType": "Internal"
}
],
"nextLink": "bamebrbqkebjwevbq"
"nextLink": "https://sample.com/nextLink"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e-tests/cadl-ranch-specs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@azure-tools/typespec-e2e-cadl-ranch-specs",
"dependencies": {
"@azure-tools/cadl-ranch-specs": "0.28.7"
"@azure-tools/cadl-ranch-specs": "0.29.0"
},
"type": "module",
"private": true
Expand Down
12 changes: 10 additions & 2 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export type SdkType =
| SdkEnumValueType
| SdkConstantType
| SdkUnionType
| SdkModelType;
| SdkModelType
| SdkMultipartFileType;

export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
Expand All @@ -110,7 +111,8 @@ export type SdkBuiltInKinds =
| "armId"
| "ipAddress"
| "azureLocation"
| "etag";
| "etag"
| "multipartFile";

const SdkDatetimeEncodingsConst = ["rfc3339", "rfc7231", "unixTimestamp"] as const;

Expand All @@ -130,6 +132,11 @@ export interface SdkDurationType extends SdkTypeBase {
wireType: SdkBuiltInType;
}

export interface SdkMultipartFileType extends SdkTypeBase {
kind: "multipartFile";
encode: "binary";
}

export interface SdkArrayType extends SdkTypeBase {
kind: "array";
valueType: SdkType;
Expand Down Expand Up @@ -188,6 +195,7 @@ export interface SdkModelType extends SdkTypeBase {
kind: "model";
properties: SdkModelPropertyType[];
name: string;
isFormDataType: boolean;
generatedName?: string;
description?: string;
details?: string;
Expand Down
7 changes: 7 additions & 0 deletions packages/typespec-client-generator-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export const $lib = createTypeSpecLibrary({
wrongType: paramMessage`Encoding '${"encoding"}' cannot be used on type '${"type"}'`,
},
},
"conflicting-multipart-model-usage": {
severity: "error",
messages: {
default: "Invalid encoding",
wrongType: paramMessage`Model '${"modelName"}' cannot be used as both multipart/form-data input and regular body input. You can create a separate model with name 'model ${"modelName"}FormData' extends ${"modelName"} {}`,
},
},
"discriminator-not-constant": {
severity: "error",
messages: {
Expand Down
34 changes: 34 additions & 0 deletions packages/typespec-client-generator-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
SdkEnumValueType,
SdkModelPropertyTypeBase,
SdkModelType,
SdkMultipartFileType,
SdkTupleType,
SdkType,
} from "./interfaces.js";
Expand Down Expand Up @@ -266,6 +267,13 @@ export function getSdkDurationType(context: SdkContext, type: Scalar): SdkDurati
};
}

function getSdkMultipartFileType(context: SdkContext, type: Scalar): SdkMultipartFileType {
return {
...getSdkTypeBaseHelper(context, type, "multipartFile"),
encode: "binary",
};
}

export function getSdkArrayOrDict(
context: SdkContext,
type: Model,
Expand Down Expand Up @@ -433,8 +441,24 @@ function addDiscriminatorToModelType(
export function getSdkModel(context: SdkContext, type: Model, operation?: Operation): SdkModelType {
type = getEffectivePayloadType(context, type);
let sdkType = context.modelsMap?.get(type) as SdkModelType | undefined;
const httpOperation = operation
? ignoreDiagnostics(getHttpOperation(context.program, operation))
: undefined;
const isFormDataType = httpOperation
? Boolean(httpOperation.parameters.body?.contentTypes.includes("multipart/form-data"))
: false;
if (sdkType) {
updateModelsMap(context, type, sdkType, operation);
if (isFormDataType !== sdkType.isFormDataType) {
// This means we have a model that is used both for formdata input and for regular body input
reportDiagnostic(context.program, {
code: "conflicting-multipart-model-usage",
target: type,
format: {
modelName: sdkType.name,
},
});
}
} else {
const docWrapper = getDocHelper(context, type);
sdkType = {
Expand All @@ -448,6 +472,7 @@ export function getSdkModel(context: SdkContext, type: Model, operation?: Operat
access: undefined, // dummy value since we need to update models map before we can set this
usage: UsageFlags.None, // dummy value since we need to update models map before we can set this
crossLanguageDefinitionId: getCrossLanguageDefinitionId(type),
isFormDataType,
};

updateModelsMap(context, type, sdkType, operation);
Expand Down Expand Up @@ -650,6 +675,15 @@ export function getClientType(context: SdkContext, type: Type, operation?: Opera
if (type.name === "duration") {
return getSdkDurationType(context, type);
}
const httpOperation = operation
? ignoreDiagnostics(getHttpOperation(context.program, operation))
: undefined;
const hasMultipartInput =
httpOperation &&
httpOperation.parameters.body?.contentTypes.includes("multipart/form-data");
if (type.name === "bytes" && hasMultipartInput) {
return getSdkMultipartFileType(context, type);
}
const scalarType = getSdkBuiltInType(context, type);
// just add default encode, normally encode is on extended scalar and model property
addEncodeInfo(context, type, scalarType);
Expand Down
78 changes: 78 additions & 0 deletions packages/typespec-client-generator-core/test/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing";
import { Enum, UsageFlags } from "@typespec/compiler";
import { expectDiagnostics } from "@typespec/compiler/testing";
import { deepEqual, deepStrictEqual, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import {
Expand Down Expand Up @@ -1983,6 +1984,83 @@ describe("typespec-client-generator-core: types", () => {
strictEqual(models.length, 2);
});
});
describe("SdkMultipartFormType", () => {
it("multipart form basic", async function () {
await runner.compileWithBuiltInService(`
model MultiPartRequest {
id: string;
profileImage: bytes;
}
op basic(@header contentType: "multipart/form-data", @body body: MultiPartRequest): NoContentResponse;
`);

const models = Array.from(getAllModels(runner.context));
strictEqual(models.length, 1);
const model = models[0] as SdkModelType;
strictEqual(model.kind, "model");
strictEqual(model.isFormDataType, true);
strictEqual(model.name, "MultiPartRequest");
strictEqual(model.properties.length, 2);
const id = model.properties.find((x) => x.nameInClient === "id")!;
strictEqual(id.kind, "property");
strictEqual(id.type.kind, "string");
const profileImage = model.properties.find((x) => x.nameInClient === "profileImage")!;
strictEqual(profileImage.kind, "property");
strictEqual(profileImage.type.kind, "multipartFile");
});
it("multipart conflicting model usage", async function () {
const diagnostics = await runner.diagnose(
`
@service({title: "Test Service"}) namespace TestService;
model MultiPartRequest {
id: string;
profileImage: bytes;
}
@post op multipartUse(@header contentType: "multipart/form-data", @body body: MultiPartRequest): NoContentResponse;
@put op jsonUse(@body body: MultiPartRequest): NoContentResponse;
`
);
getAllModels(runner.context);
expectDiagnostics(diagnostics, {
code: "@azure-tools/typespec-client-generator-core/conflicting-multipart-model-usage",
});

// expectDiagnostics(getAllModels(runner.context), {
// code: "@azure-tools/typespec-client-generator-core/conflicting-multipart-model-usage",
// });
});
it("multipart resolving conflicting model usage with spread", async function () {
await runner.compileWithBuiltInService(
`
model B {
doc: bytes
}
model A {
...B
}
@put op multipartOperation(@header contentType: "multipart/form-data", ...A): void;
@post op normalOperation(...B): void;
`
);
const models = Array.from(getAllModels(runner.context));
strictEqual(models.length, 2);
const modelA = models.find((x) => x.name === "A")!;
strictEqual(modelA.kind, "model");
strictEqual(modelA.isFormDataType, true);
strictEqual(modelA.properties.length, 1);
strictEqual(modelA.properties[0].type.kind, "multipartFile");

const modelB = models.find((x) => x.name === "B")!;
strictEqual(modelB.kind, "model");
strictEqual(modelB.isFormDataType, false);
strictEqual(modelB.properties.length, 1);
strictEqual(modelB.properties[0].type.kind, "bytes");
});
});
describe("SdkTupleType", () => {
it("model with tupled properties", async function () {
await runner.compileAndDiagnose(`
Expand Down

0 comments on commit 624a802

Please sign in to comment.