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

fix: implements serialization of enum collections #1578

Merged
merged 10 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 7 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
Expand Up @@ -93,6 +93,13 @@ export interface SerializationWriter {
* @param values the value to write to the stream.
*/
writeEnumValue<T>(key?: string, ...values: (T | null | undefined)[]): void;

/**
* Writes the specified collection of enum values to the stream with an optional given key.
* @param key the key to write the value with.
* @param values the value to write to the stream.
*/
writeCollectionOfEnumValue<T>(key?: string, values?: (T | null | undefined)[]): void;
/**
* Writes a null value for the specified key.
* @param key the key to write the value with.
Expand Down
11 changes: 11 additions & 0 deletions packages/serialization/form/src/formSerializationWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ export class FormSerializationWriter implements SerializationWriter {
}
}
};
public writeCollectionOfEnumValue = <T>(key?: string, values?: (T | null | undefined)[]): void => {
if (key && values && values.length > 0) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
const rawValues = values.filter((x) => x !== undefined).map((x) => `${x}`);
if (rawValues.length > 0) {
rawValues.forEach((val) => {
this.writeStringValue(key, val);
});
}
}
};
public getSerializedContent = (): ArrayBuffer => {
return this.convertStringToArrayBuffer(this.writer.join(``));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DateOnly, Duration, TimeOnly } from "@microsoft/kiota-abstractions";
import { assert, describe, it } from "vitest";

import { FormSerializationWriter } from "../../src";
import { serializeTestEntity, type TestEntity } from "../testEntity";
import { LongRunningOperationStatusObject, serializeTestEntity, type TestEntity } from "../testEntity";

describe("FormSerializationWriter", () => {
it("writesSampleObjectValue", () => {
Expand All @@ -34,6 +34,8 @@ describe("FormSerializationWriter", () => {
testEntity.additionalData["jobTitle"] = "Author";
testEntity.additionalData["createdDateTime"] = new Date(0);
testEntity.deviceNames = ["device1", "device2"];
testEntity.status = LongRunningOperationStatusObject.NotStarted;
//testEntity.nextStatuses = [LongRunningOperationStatusObject.Running, LongRunningOperationStatusObject.Succeeded];
const formSerializationWriter = new FormSerializationWriter();
formSerializationWriter.writeObjectValue(undefined, testEntity, serializeTestEntity);
const formContent = formSerializationWriter.getSerializedContent();
Expand All @@ -51,6 +53,7 @@ describe("FormSerializationWriter", () => {
"deviceNames=device2", // Serializes collections
"officeLocation=null", // Serializes null values
"endWorkTime=null", // Serializes null values
"status=notStarted", // Serializes enum values
];
const arr = form.split("&");
let count = 0;
Expand All @@ -65,6 +68,15 @@ describe("FormSerializationWriter", () => {
assert.equal(arr.length, 0);
});

it("writeCollectionOfEnumValues", () => {
const enums = [LongRunningOperationStatusObject.Running, LongRunningOperationStatusObject.Succeeded];
const formSerializationWriter = new FormSerializationWriter();
formSerializationWriter.writeCollectionOfEnumValue("nextStatuses", enums);
const formContent = formSerializationWriter.getSerializedContent();
const form = new TextDecoder().decode(formContent);
assert.equal("nextStatuses=running&nextStatuses=succeeded&", form);
});

it("writesSampleCollectionOfObjectValues", () => {
const testEntity = {} as TestEntity;
testEntity.id = "48d31887-5fad-4d73-a9f5-3c356e68a038";
Expand Down
21 changes: 21 additions & 0 deletions packages/serialization/form/test/testEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ export interface TestEntity extends Parsable, AdditionalDataHolder {
endWorkTime?: TimeOnly | null;
officeLocation?: string | null;
deviceNames?: string[] | null;
status?: LongRunningOperationStatus | null;
nextStatuses?: LongRunningOperationStatus[] | null;
}

export const LongRunningOperationStatusObject = {
NotStarted: "notStarted",
Running: "running",
Succeeded: "succeeded",
Failed: "failed",
UnknownFutureValue: "unknownFutureValue",
} as const;
export type LongRunningOperationStatus = (typeof LongRunningOperationStatusObject)[keyof typeof LongRunningOperationStatusObject];

export function createTestParserFromDiscriminatorValue(parseNode: ParseNode | undefined) {
if (!parseNode) throw new Error("parseNode cannot be undefined");
return deserializeTestEntity;
Expand Down Expand Up @@ -48,6 +60,12 @@ export function deserializeTestEntity(testEntity: TestEntity | undefined = {}):
deviceNames: (n) => {
testEntity.deviceNames = n.getCollectionOfPrimitiveValues();
},
status: (n) => {
testEntity.status = n.getEnumValue<LongRunningOperationStatus>(LongRunningOperationStatusObject);
},
nextStatuses: (n) => {
testEntity.nextStatuses = n.getCollectionOfEnumValues<LongRunningOperationStatus>(LongRunningOperationStatusObject);
},
};
}

Expand All @@ -61,4 +79,7 @@ export function serializeTestEntity(writer: SerializationWriter, testEntity: Tes
writer.writeStringValue("officeLocation", testEntity.officeLocation);
writer.writeAdditionalData(testEntity.additionalData);
writer.writeCollectionOfPrimitiveValues("deviceNames", testEntity.deviceNames);
writer.writeEnumValue<LongRunningOperationStatus>("status", testEntity.status);
// enable this line after publishing writeCollectionOfEnumValues
//writer.writeCollectionOfEnumValues<LongRunningOperationStatus>("nextStatuses", testEntity.nextStatuses);
}
16 changes: 16 additions & 0 deletions packages/serialization/json/src/jsonSerializationWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,22 @@ export class JsonSerializationWriter implements SerializationWriter {
}
}
};

public writeCollectionOfEnumValue = <T>(key?: string, values?: (T | undefined | null)[]): void => {
if (values && values.length > 0) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
const rawValues = values.filter((x) => x !== undefined).map((x) => `${x}`);

if (rawValues.length === 0) {
return;
}

key && this.writePropertyName(key);
this.writer.push(JSON.stringify(rawValues));
key && this.writer.push(JsonSerializationWriter.propertySeparator);
}
};

public getSerializedContent = (): ArrayBuffer => {
return this.convertStringToArrayBuffer(this.writer.join(``));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { assert, describe, it, beforeEach } from "vitest";

import { JsonParseNode, JsonSerializationWriter } from "../../src/index";
import { createTestBackedModelFromDiscriminatorValue, createTestParserFromDiscriminatorValue, serializeTestParser, TestBackedModel, type TestParser } from "./testEntity";
import { createTestBackedModelFromDiscriminatorValue, createTestParserFromDiscriminatorValue, LongRunningOperationStatusObject, serializeTestParser, TestBackedModel, type TestParser } from "./testEntity";
import { UntypedTestEntity, serializeUntypedTestEntity } from "./untypedTestEntiy";
import { BackingStore, BackingStoreFactorySingleton, createBackedModelProxyHandler, createUntypedArray, createUntypedBoolean, createUntypedNull, createUntypedNumber, createUntypedObject, createUntypedString } from "@microsoft/kiota-abstractions";

Expand Down Expand Up @@ -59,6 +59,37 @@ describe("JsonParseNode", () => {
assert.deepEqual(stringValueResult, expectedObject);
});

it("Test enum serialization", async () => {
const inputObject: TestParser = {
status: LongRunningOperationStatusObject.NotStarted,
//nextStatuses: [LongRunningOperationStatusObject.Succeeded, LongRunningOperationStatusObject.Failed],
};
const expectedObject: TestParser = {
status: LongRunningOperationStatusObject.NotStarted,
//nextStatuses: [LongRunningOperationStatusObject.Succeeded, LongRunningOperationStatusObject.Failed],
};

const writer = new JsonSerializationWriter();
writer.writeObjectValue("", inputObject, serializeTestParser);
const serializedContent = writer.getSerializedContent();
const decoder = new TextDecoder();
const contentAsStr = decoder.decode(serializedContent);
const result = JSON.parse(contentAsStr);
const stringValueResult = new JsonParseNode(result).getObjectValue(createTestParserFromDiscriminatorValue) as TestParser;
assert.deepEqual(stringValueResult, expectedObject);
});

it("Test collection of enum serialization", async () => {
const writer = new JsonSerializationWriter();
const enums = [LongRunningOperationStatusObject.NotStarted, LongRunningOperationStatusObject.Succeeded];

writer.writeCollectionOfEnumValue("enum", enums);
const serializedContent = writer.getSerializedContent();
const decoder = new TextDecoder();
const contentAsStr = decoder.decode(serializedContent);
assert.equal(contentAsStr, '"enum":["notStarted","succeeded"],');
});

it("encodes characters properly", async () => {
const inputObject: TestParser = {
testCollection: ["2", "3"],
Expand Down
20 changes: 20 additions & 0 deletions packages/serialization/json/test/common/testEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@ export interface TestParser {
testNumber?: number | null | undefined;
testGuid?: Guid | null | undefined;
testUnionObject?: TestUnionObject | null | undefined;
status?: LongRunningOperationStatus | null;
nextStatuses?: LongRunningOperationStatus[] | null;
}

export const LongRunningOperationStatusObject = {
NotStarted: "notStarted",
Running: "running",
Succeeded: "succeeded",
Failed: "failed",
UnknownFutureValue: "unknownFutureValue",
} as const;
export type LongRunningOperationStatus = (typeof LongRunningOperationStatusObject)[keyof typeof LongRunningOperationStatusObject];

export interface TestBackedModel extends TestParser, BackedModel {
backingStoreEnabled?: boolean | undefined;
}
Expand Down Expand Up @@ -89,6 +101,12 @@ export function deserializeTestParser(testParser: TestParser | undefined = {}):
testUnionObject: (n) => {
testParser.testUnionObject = n.getStringValue() ?? n.getNumberValue() ?? n.getObjectValue(createTestUnionObjectFromDiscriminatorValue);
},
status: (n) => {
testParser.status = n.getEnumValue<LongRunningOperationStatus>(LongRunningOperationStatusObject);
},
nextStatuses: (n) => {
testParser.nextStatuses = n.getCollectionOfEnumValues<LongRunningOperationStatus>(LongRunningOperationStatusObject);
},
};
}

Expand Down Expand Up @@ -148,6 +166,8 @@ export function serializeTestParser(writer: SerializationWriter, entity: TestPar
} else {
writer.writeObjectValue("testUnionObject", entity.testUnionObject as any, serializeTestUnionObject);
}
writer.writeEnumValue("status", entity.status);
writer.writeCollectionOfEnumValue("nextStatuses", entity.nextStatuses);
}

export function serializeFoo(writer: SerializationWriter, entity: FooResponse | undefined = {}): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ export class MultipartSerializationWriter implements SerializationWriter {
): void => {
throw new Error(`serialization of enum values is not supported with multipart`);
};
public writeCollectionOfEnumValue = <T>(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
key?: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
values?: (T | null | undefined)[],
): void => {
throw new Error(`serialization of collection of enum values is not supported with multipart`);
};
public getSerializedContent = (): ArrayBuffer => {
return this.writer;
};
Expand Down
3 changes: 3 additions & 0 deletions packages/serialization/text/src/textSerializationWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export class TextSerializationWriter implements SerializationWriter {
}
}
};
public writeCollectionOfEnumValue = <T>(key?: string, values?: T[] | null): void => {
this.writeEnumValue(key, values);
};
public getSerializedContent = (): ArrayBuffer => {
return this.convertStringToArrayBuffer(this.writer.join(``));
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { assert, describe, it } from "vitest";
import { TextSerializationWriter } from "../../src";

describe("TextSerializationWriter", () => {
it("writeEnumValue", () => {
const textSerializationWriter = new TextSerializationWriter();

const statuses = [LongRunningOperationStatusObject.NotStarted, LongRunningOperationStatusObject.Running];
textSerializationWriter.writeEnumValue("", ...statuses);
const formContent = textSerializationWriter.getSerializedContent();
const form = new TextDecoder().decode(formContent);
const expectedString = "notStarted, running";
assert.equal(form, expectedString);
});
it("writeCollectionOfEnumValue", () => {
const textSerializationWriter = new TextSerializationWriter();
const statuses = [LongRunningOperationStatusObject.NotStarted, LongRunningOperationStatusObject.Running];
textSerializationWriter.writeCollectionOfEnumValue("", statuses);
const formContent = textSerializationWriter.getSerializedContent();
const form = new TextDecoder().decode(formContent);
const expectedString = "notStarted,running";
assert.equal(form, expectedString);
});
});

export const LongRunningOperationStatusObject = {
NotStarted: "notStarted",
Running: "running",
Succeeded: "succeeded",
Failed: "failed",
UnknownFutureValue: "unknownFutureValue",
} as const;
Loading